-1

I'm migrating a project from Spring 5 (Spring Boot 2.x) to Spring 6 (Spring Boot 3.1) and upgrading to Java 17. This was originally done in Spring 4, and the application connects to two different databases, each configured in XML following the pattern in this post

Configuring Multiple Databases with Multiple EntityManagerFactory in Spring Data

I'm running into a problem on start with the error like this one

Cannot resolve reference to bean 'entityManagerFactory' while setting constructor argument

But I'm stumped on how to correct this. My config looks something like this

    <context:property-placeholder
            location="file:${fvis.home}/conf/fvisconf.properties"
            ignore-unresolvable="false" ignore-resource-not-found="true" />

    <bean id="firstDataSource"
         class="org.apache.tomcat.jdbc.pool.DataSource" destroy-method="close">
        <property name="driverClassName" value="oracle.jdbc.OracleDriver" />
        <property name="url"
            value="jdbc:oracle:thin:@(description=(address_list=(address=(host=${fvisbt.drpg.host})(protocol=tcp)(port=1521))(load_balance=yes)(failover=yes))(connect_data=(service_name=${fvisbt.drpg.sid})))">
        </property>
        <property name="username" value="firstUser" />
        <property name="password" value="${second.password}" />
    </bean>

    <bean id="secondDataSource"
         class="org.apache.tomcat.jdbc.pool.DataSource" destroy-method="close">
        <property name="driverClassName" value="oracle.jdbc.OracleDriver" />
        <property name="url"
            value="jdbc:oracle:thin:@(description=(address_list=(address=(host=${fvisbt.drpg.host})(protocol=tcp)(port=1521))(load_balance=yes)(failover=yes))(connect_data=(service_name=${fvisbt.jobdb.sid})))">
        </property>
        <property name="username" value="secondUser" />
        <property name="password" value="${second.password}" />
    </bean>

    <!-- EntityManager injection is by package -->
    <bean id="firstEMF"
          class="org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean">
        <property name="dataSource" ref="firstDataSource" />
        <property name="jpaVendorAdapter">
            <bean class="org.springframework.orm.jpa.vendor.HibernateJpaVendorAdapter" />
        </property>
        <property name="jpaProperties">
            <props>
                <prop key="hibernate.dialect">org.hibernate.dialect.Oracle12cDialect</prop>
                <prop key="hibernate.ejb.entitymanager_factory_name">firstEMF</prop>
            </props>
        </property>
        <property name="packagesToScan">
            <list>
                <value>com.example.entity.first</value>
            </list>
        </property>
    </bean>

    <bean id="secondEMF"
          class="org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean">
        <property name="dataSource" ref="secondDataSource" />
        <property name="jpaVendorAdapter">
            <bean class="org.springframework.orm.jpa.vendor.HibernateJpaVendorAdapter" />
        </property>
        <property name="jpaProperties">
            <props>
                <prop key="hibernate.dialect">org.hibernate.dialect.Oracle12cDialect</prop>
                <prop key="hibernate.ejb.entitymanager_factory_name">secondEMF</prop>
            </props>
        </property>
        <property name="packagesToScan">
            <list>
                <value>com.example.entity.second</value>
            </list>
        </property>
    </bean>

The launch log looks like this

...
INFO  c.e.w.MyApp                              : No active profile set, falling back to 1 default profile: "default"
INFO  .s.d.r.c.RepositoryConfigurationDelegate : Bootstrapping Spring Data JPA repositories in DEFAULT mode.
INFO  .s.d.r.c.RepositoryConfigurationDelegate : Finished Spring Data repository scanning in 17 ms. Found 0 JPA repository interfaces.
INFO  o.s.b.w.e.t.TomcatWebServer              : Tomcat initialized with port(s): 8080 (http)
INFO  o.a.c.c.StandardService                  : Starting service [Tomcat]
INFO  o.a.c.c.StandardEngine                   : Starting Servlet engine: [Apache Tomcat/10.1.10]
INFO  o.a.j.s.TldScanner                       : At least one JAR was scanned for TLDs yet contained no TLDs. Enable debug logging for this logger for a complete list of JARs that were scanned but no TLDs were found in them. Skipping unneeded JARs during scanning can improve startup time and JSP compilation time.
INFO  o.a.c.c.C.[.[.[/]                        : Initializing Spring embedded WebApplicationContext
INFO  w.s.c.ServletWebServerApplicationContext : Root WebApplicationContext: initialization completed in 2636 ms
INFO  o.h.j.i.u.LogHelper                      : HHH000204: Processing PersistenceUnitInfo [name: default]
INFO  o.h.Version                              : HHH000412: Hibernate ORM core version 6.2.5.Final
INFO  o.h.c.Environment                        : HHH000406: Using bytecode reflection optimizer
INFO  o.h.b.i.BytecodeProviderInitiator        : HHH000021: Bytecode provider name : bytebuddy
INFO  o.s.o.j.p.SpringPersistenceUnitInfo      : No LoadTimeWeaver setup: ignoring JPA class transformer
WARN  o.h.e.j.c.i.ConnectionProviderInitiator  : HHH000181: No appropriate connection provider encountered, assuming application will be supplying connections
WARN  o.h.e.j.e.i.JdbcEnvironmentInitiator     : HHH000342: Could not obtain connection to query metadata

java.lang.UnsupportedOperationException: The application must supply JDBC connections
    at org.hibernate.engine.jdbc.connections.internal.UserSuppliedConnectionProviderImpl.getConnection(UserSuppliedConnectionProviderImpl.java:44) ~[hibernate-core-6.2.5.Final.jar:6.2.5.Final]
    at org.hibernate.engine.jdbc.env.internal.JdbcEnvironmentInitiator$ConnectionProviderJdbcConnectionAccess.obtainConnection(JdbcEnvironmentInitiator.java:316) ~[hibernate-core-6.2.5.Final.jar:6.2.5.Final]
    at org.hibernate.engine.jdbc.env.internal.JdbcEnvironmentInitiator.initiateService(JdbcEnvironmentInitiator.java:152) ~[hibernate-core-6.2.5.Final.jar:6.2.5.Final]
    at org.hibernate.engine.jdbc.env.internal.JdbcEnvironmentInitiator.initiateService(JdbcEnvironmentInitiator.java:34) ~[hibernate-core-6.2.5.Final.jar:6.2.5.Final]
    at org.hibernate.boot.registry.internal.StandardServiceRegistryImpl.initiateService(StandardServiceRegistryImpl.java:119) ~[hibernate-core-6.2.5.Final.jar:6.2.5.Final]
    at org.hibernate.service.internal.AbstractServiceRegistryImpl.createService(AbstractServiceRegistryImpl.java:264) ~[hibernate-core-6.2.5.Final.jar:6.2.5.Final]
    at org.hibernate.service.internal.AbstractServiceRegistryImpl.initializeService(AbstractServiceRegistryImpl.java:239) ~[hibernate-core-6.2.5.Final.jar:6.2.5.Final]
    at org.hibernate.service.internal.AbstractServiceRegistryImpl.getService(AbstractServiceRegistryImpl.java:216) ~[hibernate-core-6.2.5.Final.jar:6.2.5.Final]
    at org.hibernate.boot.model.relational.Database.<init>(Database.java:45) ~[hibernate-core-6.2.5.Final.jar:6.2.5.Final]
    at org.hibernate.boot.internal.InFlightMetadataCollectorImpl.getDatabase(InFlightMetadataCollectorImpl.java:230) ~[hibernate-core-6.2.5.Final.jar:6.2.5.Final]
    at org.hibernate.boot.internal.InFlightMetadataCollectorImpl.<init>(InFlightMetadataCollectorImpl.java:198) ~[hibernate-core-6.2.5.Final.jar:6.2.5.Final]
    at org.hibernate.boot.model.process.spi.MetadataBuildingProcess.complete(MetadataBuildingProcess.java:166) ~[hibernate-core-6.2.5.Final.jar:6.2.5.Final]
    at org.hibernate.jpa.boot.internal.EntityManagerFactoryBuilderImpl.metadata(EntityManagerFactoryBuilderImpl.java:1380) ~[hibernate-core-6.2.5.Final.jar:6.2.5.Final]
    at org.hibernate.jpa.boot.internal.EntityManagerFactoryBuilderImpl.build(EntityManagerFactoryBuilderImpl.java:1451) ~[hibernate-core-6.2.5.Final.jar:6.2.5.Final]
    at org.springframework.orm.jpa.vendor.SpringHibernateJpaPersistenceProvider.createContainerEntityManagerFactory(SpringHibernateJpaPersistenceProvider.java:75) ~[spring-orm-6.0.10.jar:6.0.10]
    at org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean.createNativeEntityManagerFactory(LocalContainerEntityManagerFactoryBean.java:376) ~[spring-orm-6.0.10.jar:6.0.10]
    at org.springframework.orm.jpa.AbstractEntityManagerFactoryBean.buildNativeEntityManagerFactory(AbstractEntityManagerFactoryBean.java:409) ~[spring-orm-6.0.10.jar:6.0.10]
    at org.springframework.orm.jpa.AbstractEntityManagerFactoryBean.afterPropertiesSet(AbstractEntityManagerFactoryBean.java:396) ~[spring-orm-6.0.10.jar:6.0.10]
    at org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean.afterPropertiesSet(LocalContainerEntityManagerFactoryBean.java:352) ~[spring-orm-6.0.10.jar:6.0.10]
    at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.invokeInitMethods(AbstractAutowireCapableBeanFactory.java:1816) ~[spring-beans-6.0.10.jar:6.0.10]
    at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.initializeBean(AbstractAutowireCapableBeanFactory.java:1766) ~[spring-beans-6.0.10.jar:6.0.10]
    at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.doCreateBean(AbstractAutowireCapableBeanFactory.java:598) ~[spring-beans-6.0.10.jar:6.0.10]
    at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBean(AbstractAutowireCapableBeanFactory.java:520) ~[spring-beans-6.0.10.jar:6.0.10]
    at org.springframework.beans.factory.support.AbstractBeanFactory.lambda$doGetBean$0(AbstractBeanFactory.java:326) ~[spring-beans-6.0.10.jar:6.0.10]
    at org.springframework.beans.factory.support.DefaultSingletonBeanRegistry.getSingleton(DefaultSingletonBeanRegistry.java:234) [spring-beans-6.0.10.jar:6.0.10]
    at org.springframework.beans.factory.support.AbstractBeanFactory.doGetBean(AbstractBeanFactory.java:324) [spring-beans-6.0.10.jar:6.0.10]
    at org.springframework.beans.factory.support.AbstractBeanFactory.getBean(AbstractBeanFactory.java:200) [spring-beans-6.0.10.jar:6.0.10]
    at org.springframework.context.support.AbstractApplicationContext.getBean(AbstractApplicationContext.java:1154) [spring-context-6.0.10.jar:6.0.10]
    at org.springframework.context.support.AbstractApplicationContext.finishBeanFactoryInitialization(AbstractApplicationContext.java:931) [spring-context-6.0.10.jar:6.0.10]
    at org.springframework.context.support.AbstractApplicationContext.refresh(AbstractApplicationContext.java:608) [spring-context-6.0.10.jar:6.0.10]
    at org.springframework.boot.web.servlet.context.ServletWebServerApplicationContext.refresh(ServletWebServerApplicationContext.java:146) [spring-boot-3.1.1.jar:3.1.1]
    at org.springframework.boot.SpringApplication.refresh(SpringApplication.java:734) [spring-boot-3.1.1.jar:3.1.1]
    at org.springframework.boot.SpringApplication.refreshContext(SpringApplication.java:436) [spring-boot-3.1.1.jar:3.1.1]
    at org.springframework.boot.SpringApplication.run(SpringApplication.java:312) [spring-boot-3.1.1.jar:3.1.1]
        ...

INFO  o.h.b.i.BytecodeProviderInitiator        : HHH000021: Bytecode provider name : bytebuddy
INFO  o.h.e.t.j.p.i.JtaPlatformInitiator       : HHH000490: Using JtaPlatform implementation: [org.hibernate.engine.transaction.jta.platform.internal.NoJtaPlatform]
INFO  j.LocalContainerEntityManagerFactoryBean : Initialized JPA EntityManagerFactory for persistence unit 'default'
INFO  o.h.j.i.u.LogHelper                      : HHH000204: Processing PersistenceUnitInfo [name: default]
INFO  o.h.b.i.BytecodeProviderInitiator        : HHH000021: Bytecode provider name : bytebuddy
INFO  o.s.o.j.p.SpringPersistenceUnitInfo      : No LoadTimeWeaver setup: ignoring JPA class transformer
INFO  o.h.b.i.BytecodeProviderInitiator        : HHH000021: Bytecode provider name : bytebuddy
INFO  o.h.e.t.j.p.i.JtaPlatformInitiator       : HHH000490: Using JtaPlatform implementation: [org.hibernate.engine.transaction.jta.platform.internal.NoJtaPlatform]
INFO  j.LocalContainerEntityManagerFactoryBean : Initialized JPA EntityManagerFactory for persistence unit 'default'
INFO  c.e.w.MyApp                              : adding properties resource, /META-INF/MANIFEST.MF
WARN  ConfigServletWebServerApplicationContext : Exception encountered during context initialization - cancelling refresh attempt: org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'jpaSharedEM_entityManagerFactory': Cannot resolve reference to bean 'entityManagerFactory' while setting constructor argument
INFO  j.LocalContainerEntityManagerFactoryBean : Closing JPA EntityManagerFactory for persistence unit 'default'
INFO  j.LocalContainerEntityManagerFactoryBean : Closing JPA EntityManagerFactory for persistence unit 'default'

I have DAO objects which implement some basic CRUD operations as well as a few look-up. Ignoring the CRUD operations, they were done in a hierarchy to eliminate a lot of duplicate code, and the inheritance looks like

public abstract class AbstractDAO<T> implements DAO<T> {
    protected EntityManagerFactory entityManagerFactory;
    protected abstract void setEntityManagerFactory(EntityManagerFactory entityManagerFactory);
    public EntityManagerFactory getEntityManagerFactory() {
        return this.entityManagerFactory;
    }
    ...
}
public class FirstAbstractDAO <K> extends AbstractDAO<K> {
    @Override
    @Autowired
    @Qualifier("firstEMF")
    public void setEntityManagerFactory(EntityManagerFactory entityManagerFactory) {
        this.entityManagerFactory = entityManagerFactory;
    }
}
public class SecondAbstractDAO <K> extends AbstractDAO<K> {
    @Override
    @Autowired
    @Qualifier("secondEMF")
    public void setEntityManagerFactory(EntityManagerFactory entityManagerFactory) {
        this.entityManagerFactory = entityManagerFactory;
    }
}

The logs show those getting intialized and, if I set a breakpoint in setEntityManagerFactory, I can clearly see them being initialized.

What I can't figure out, is what is it that Spring Boot 3 is trying to initialize that results in the error in this traceback

java.lang.UnsupportedOperationException: The application must supply JDBC connections
    at org.hibernate.engine.jdbc.connections.internal.UserSuppliedConnectionProviderImpl.getConnection(UserSuppliedConnectionProviderImpl.java:44) ~[hibernate-core-6.2.5.Final.jar:6.2.5.Final]
    at org.hibernate.engine.jdbc.env.internal.JdbcEnvironmentInitiator$ConnectionProviderJdbcConnectionAccess.obtainConnection(JdbcEnvironmentInitiator.java:316) ~[hibernate-core-6.2.5.Final.jar:6.2.5.Final]
    at org.hibernate.engine.jdbc.env.internal.JdbcEnvironmentInitiator.initiateService(JdbcEnvironmentInitiator.java:152) ~[hibernate-core-6.2.5.Final.jar:6.2.5.Final]
    at org.hibernate.engine.jdbc.env.internal.JdbcEnvironmentInitiator.initiateService(JdbcEnvironmentInitiator.java:34) ~[hibernate-core-6.2.5.Final.jar:6.2.5.Final]
    at org.hibernate.boot.registry.internal.StandardServiceRegistryImpl.initiateService(StandardServiceRegistryImpl.java:119) ~[hibernate-core-6.2.5.Final.jar:6.2.5.Final]
    at org.hibernate.service.internal.AbstractServiceRegistryImpl.createService(AbstractServiceRegistryImpl.java:264) ~[hibernate-core-6.2.5.Final.jar:6.2.5.Final]
    at org.hibernate.service.internal.AbstractServiceRegistryImpl.initializeService(AbstractServiceRegistryImpl.java:239) ~[hibernate-core-6.2.5.Final.jar:6.2.5.Final]
    at org.hibernate.service.internal.AbstractServiceRegistryImpl.getService(AbstractServiceRegistryImpl.java:216) ~[hibernate-core-6.2.5.Final.jar:6.2.5.Final]
    at org.hibernate.boot.model.relational.Database.<init>(Database.java:45) ~[hibernate-core-6.2.5.Final.jar:6.2.5.Final]
    at org.hibernate.boot.internal.InFlightMetadataCollectorImpl.getDatabase(InFlightMetadataCollectorImpl.java:230) ~[hibernate-core-6.2.5.Final.jar:6.2.5.Final]
    at org.hibernate.boot.internal.InFlightMetadataCollectorImpl.<init>(InFlightMetadataCollectorImpl.java:198) ~[hibernate-core-6.2.5.Final.jar:6.2.5.Final]
    at org.hibernate.boot.model.process.spi.MetadataBuildingProcess.complete(MetadataBuildingProcess.java:166) ~[hibernate-core-6.2.5.Final.jar:6.2.5.Final]
    at org.hibernate.jpa.boot.internal.EntityManagerFactoryBuilderImpl.metadata(EntityManagerFactoryBuilderImpl.java:1380) ~[hibernate-core-6.2.5.Final.jar:6.2.5.Final]
    at org.hibernate.jpa.boot.internal.EntityManagerFactoryBuilderImpl.build(EntityManagerFactoryBuilderImpl.java:1451) ~[hibernate-core-6.2.5.Final.jar:6.2.5.Final]
    at org.springframework.orm.jpa.vendor.SpringHibernateJpaPersistenceProvider.createContainerEntityManagerFactory(SpringHibernateJpaPersistenceProvider.java:75) ~[spring-orm-6.0.10.jar:6.0.10]
    at org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean.createNativeEntityManagerFactory(LocalContainerEntityManagerFactoryBean.java:376) ~[spring-orm-6.0.10.jar:6.0.10]
...

If I put a breakpoint in createContainerEntityManagerFactory, I see it is trying to intialize firstEMF. I suspect it is doing it "too early" in that when I get down into EntityManager.build, the configuration values for firstEMF are all set, but the dataSource is null.

This seems to me like I'm missing something in my configuration, but I can't figure out what. Noice in the log the "Initialized JPA EntityManagerFactory" after the exception.

-- EDIT/Additional Info --

I've put a breakpoint in one of the getBean methods in AnnotationConfigServletWebServerApplication where it is trying to resolve "firstEMF" as well as in LocalContainerEntityManagerFactoryBean.setDataSource. My XML is being correctly read and it is creating "firstDataSource" and the connection parameters all look to be correctly read. Look at the call chain, this is while initializing firstEMF. So I'm more puzzled now as to why I'm getting an exception later.

Configure and Use Multiple DataSources in Spring Boot mentions using @Primary, but that when doing configuration in Java; I'm not clear on how to configure the same via XML only.

8
  • <property name="dataSource" ref="secondDataSource" /> - the firstEMF bean definition does not contain similar property. Commented Jul 30, 2023 at 9:52
  • Yes, my "sanitizing" deleted that, but it's in the original, I'm editing to correct that which is misleading here. What I discovered is that my two configured EMFs are initialized just fine, but the framework really, really wants a bean named entityManagerFactory. I can't figure out how to turn that off. Commented Aug 2, 2023 at 17:41
  • You can just set primary="true" as an xml atttribute on one of your beans. To get more information on which EMF it is you can enable debug logging to see what is happening when. I also wonder how you are loading this XML file, because in theory Spring Boot should backoff as you have defined a LocalEntityManagerFactoryBean and shouldn't do another one itself. Commented Aug 2, 2023 at 17:57
  • There are parts of your configuration missing. I wonder where this jpaSharedEM_entityManagerFactory bean comes from (as that seems to be the ultimate culprit). Commented Aug 2, 2023 at 18:10
  • I'll have to try the primary="true" as that would be cleaner than my "solution" below. I haven't figured out where that jpaSharedEM_entityManagerFactory comes from either, though I recall one post referencing it in the context of Hikari, which I am not using. Most of the migration to Java 17 and Spring 6 involved mechanically replacing javax with jakarta in the imports Commented Aug 17, 2023 at 15:09

3 Answers 3

0

Here's what I've done. Please tell me why this is wrong. It feels wrong, it smells wrong.

I added this class

@Configuration
public class SpringBootVoodoo {

    @Autowired
    private ApplicationContext context;

    @Bean(name="entityManagerFactory")
    public EntityManagerFactory sessionFactory() {
        return (EntityManagerFactory) context.getBean("firstEMF");
    }
}

It now starts up just fine.

Why did I even try this? Well, the article Configure and Use Multiple DataSources in Spring Boot implies I need to make one of my existing EMF beans be the primary. I can't find how to do that in XML. I don't really want to pull all the XML into code, and adding @Primary to FirstDAO didn't do the trick. This does.

But it smells wrong :-(

Sign up to request clarification or add additional context in comments.

2 Comments

It also breaks the unit tests which now need to have a @MockBean added for entityManagerFactory.
IN XML you just state primary="true" in the bean element.
0

Please check the correct datasource.

import javax.sql.DataSource; // <- old
import jakarta.activation.DataSource; // <- new

If the datasource is not present in the right type, you can not create the EMF.

Comments

-1

In Spring Boot 3 the auto-configuration for JPA has changed and it now expects the DataSource to be provided upfront before creating the EntityManagerFactory.

In your previous Spring Boot 2 setup, the EntityManagerFactory bean was created first, and then it would lookup the DataSource bean. But now this fails because the DataSource is not initialized yet when building the EMF.

The solution is to configure the LocalContainerEntityManagerFactoryBean to depend on the DataSource bean, so that it gets initialized first before building the EMF.

@Bean
public LocalContainerEntityManagerFactoryBean entityManagerFactory() {

  LocalContainerEntityManagerFactoryBean emf = new LocalContainerEntityManagerFactoryBean();
  
  emf.setDataSource(dataSource()); // add this

  // others configs
  
  return emf;
}
@Bean 
public DataSource dataSource() {
  // create and return the data source bean
}

DataSource must be initialized before EntityManagerFactory. Configure EMF bean to depend on DataSource using setDataSource()

The key fix is making sure DataSource is available before building EntityManagerFactory

1 Comment

You cannot create an EntityManagerFactory without a datasource and no it wasn't created beforehand. Nor does he even use the Spring Boot support to create the EMF he used XML.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.