-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.

rbrbrts
  • 19
  • 4
  • `` - the `firstEMF` bean definition does not contain similar property. – Andrey B. Panfilov Jul 30 '23 at 09: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. – rbrbrts Aug 02 '23 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. – M. Deinum Aug 02 '23 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). – M. Deinum Aug 02 '23 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 – rbrbrts Aug 17 '23 at 15:09
  • I don't know who downvoted the question, but I would appreciate a comment on why you think my question is "does not show any research effort, is unclear, or not useful." – rbrbrts Aug 29 '23 at 15:30

2 Answers2

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 :-(

rbrbrts
  • 19
  • 4
  • It also breaks the unit tests which now need to have a @MockBean added for entityManagerFactory. – rbrbrts Aug 02 '23 at 13:07
-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

acakojic
  • 306
  • 2
  • 11
  • 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. – M. Deinum Jul 31 '23 at 06:29