Search code examples
springhibernatetransactionsannotations

Multiple SessionFactory configuration fails with org.springframework.beans.factory.UnsatisfiedDependencyException Exception


Our application need to deal with multiple databases. We tried to configure multiple data sources through Hibernate configuration and added two configurations one for database 1 and second for database 2. This configuration fails with following Exception

org.springframework.beans.factory.UnsatisfiedDependencyException: Error creating bean with name 'DB1TransactionManager' defined in class path resource [org/npcc/ccms/config/db/HibernateConfig4DB1.class]: Unsatisfied dependency expressed through constructor argument with index 0 of type [org.hibernate.SessionFactory]: : No qualifying bean of type [org.hibernate.SessionFactory] is defined: expected single matching bean but found 2: AgrgtrSessionFactory,HRSessionFactory,StageSessionFactory; nested exception is org.springframework.beans.factory.NoUniqueBeanDefinitionException: No qualifying bean of type [org.hibernate.SessionFactory] is defined: expected single matching bean but found 3: DB1SessionFactory,DB2SessionFactory
org.springframework.beans.factory.support.ConstructorResolver.createArgumentArray(ConstructorResolver.java:747)
    org.springframework.beans.factory.support.ConstructorResolver.instantiateUsingFactoryMethod(ConstructorResolver.java:462)
    org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.instantiateUsingFactoryMethod(AbstractAutowireCapableBeanFactory.java:1094)
    org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBeanInstance(AbstractAutowireCapableBeanFactory.java:989)
    org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.doCreateBean(AbstractAutowireCapableBeanFactory.java:504)
    org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBean(AbstractAutowireCapableBeanFactory.java:475)
    org.springframework.beans.factory.support.AbstractBeanFactory$1.getObject(AbstractBeanFactory.java:302)
    org.springframework.beans.factory.support.DefaultSingletonBeanRegistry.getSingleton(DefaultSingletonBeanRegistry.java:228)
    org.springframework.beans.factory.support.AbstractBeanFactory.doGetBean(AbstractBeanFactory.java:298)
    org.springframework.beans.factory.support.AbstractBeanFactory.getBean(AbstractBeanFactory.java:193)
    org.springframework.beans.factory.support.DefaultListableBeanFactory.preInstantiateSingletons(DefaultListableBeanFactory.java:703)
    org.springframework.context.support.AbstractApplicationContext.finishBeanFactoryInitialization(AbstractApplicationContext.java:760)
    org.springframework.context.support.AbstractApplicationContext.refresh(AbstractApplicationContext.java:482)
    org.springframework.web.servlet.FrameworkServlet.configureAndRefreshWebApplicationContext(FrameworkServlet.java:658)
    org.springframework.web.servlet.FrameworkServlet.initWebApplicationContext(FrameworkServlet.java:530)
    org.springframework.web.servlet.FrameworkServlet.initServletBean(FrameworkServlet.java:484)
    org.springframework.web.servlet.HttpServletBean.init(HttpServletBean.java:136)
    javax.servlet.GenericServlet.init(GenericServlet.java:160)
    org.apache.catalina.authenticator.AuthenticatorBase.invoke(AuthenticatorBase.java:472)
    org.apache.catalina.valves.ErrorReportValve.invoke(ErrorReportValve.java:99)
    org.apache.catalina.valves.AccessLogValve.invoke(AccessLogValve.java:947)
    org.apache.catalina.connector.CoyoteAdapter.service(CoyoteAdapter.java:408)
    org.apache.coyote.http11.AbstractHttp11Processor.process(AbstractHttp11Processor.java:1009)
    org.apache.coyote.AbstractProtocol$AbstractConnectionHandler.process(AbstractProtocol.java:589)
    org.apache.tomcat.util.net.JIoEndpoint$SocketProcessor.run(JIoEndpoint.java:310)
    java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1142)
    java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:617)
    java.lang.Thread.run(Thread.java:745)

root cause

org.springframework.beans.factory.NoUniqueBeanDefinitionException: No qualifying bean of type [org.hibernate.SessionFactory] is defined: expected single matching bean but found 2: DB1SessionFactory,DB2SessionFactory
    org.springframework.beans.factory.support.DefaultListableBeanFactory.doResolveDependency(DefaultListableBeanFactory.java:970)
    org.springframework.beans.factory.support.DefaultListableBeanFactory.resolveDependency(DefaultListableBeanFactory.java:858)
    org.springframework.beans.factory.support.ConstructorResolver.resolveAutowiredArgument(ConstructorResolver.java:811)
    org.springframework.beans.factory.support.ConstructorResolver.createArgumentArray(ConstructorResolver.java:739)
    org.springframework.beans.factory.support.ConstructorResolver.instantiateUsingFactoryMethod(ConstructorResolver.java:462)
    org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.instantiateUsingFactoryMethod(AbstractAutowireCapableBeanFactory.java:1094)
    org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBeanInstance(AbstractAutowireCapableBeanFactory.java:989)
    org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.doCreateBean(AbstractAutowireCapableBeanFactory.java:504)
    org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBean(AbstractAutowireCapableBeanFactory.java:475)
    org.springframework.beans.factory.support.AbstractBeanFactory$1.getObject(AbstractBeanFactory.java:302)
    org.springframework.beans.factory.support.DefaultSingletonBeanRegistry.getSingleton(DefaultSingletonBeanRegistry.java:228)
    org.springframework.beans.factory.support.AbstractBeanFactory.doGetBean(AbstractBeanFactory.java:298)
    org.springframework.beans.factory.support.AbstractBeanFactory.getBean(AbstractBeanFactory.java:193)
    org.springframework.beans.factory.support.DefaultListableBeanFactory.preInstantiateSingletons(DefaultListableBeanFactory.java:703)
    org.springframework.context.support.AbstractApplicationContext.finishBeanFactoryInitialization(AbstractApplicationContext.java:760)
    org.springframework.context.support.AbstractApplicationContext.refresh(AbstractApplicationContext.java:482)
    org.springframework.web.servlet.FrameworkServlet.configureAndRefreshWebApplicationContext(FrameworkServlet.java:658)
    org.springframework.web.servlet.FrameworkServlet.initWebApplicationContext(FrameworkServlet.java:530)
    org.springframework.web.servlet.FrameworkServlet.initServletBean(FrameworkServlet.java:484)
    org.springframework.web.servlet.HttpServletBean.init(HttpServletBean.java:136)
    javax.servlet.GenericServlet.init(GenericServlet.java:160)
    org.apache.catalina.authenticator.AuthenticatorBase.invoke(AuthenticatorBase.java:472)
    org.apache.catalina.valves.ErrorReportValve.invoke(ErrorReportValve.java:99)
    org.apache.catalina.valves.AccessLogValve.invoke(AccessLogValve.java:947)
    org.apache.catalina.connector.CoyoteAdapter.service(CoyoteAdapter.java:408)
    org.apache.coyote.http11.AbstractHttp11Processor.process(AbstractHttp11Processor.java:1009)
    org.apache.coyote.AbstractProtocol$AbstractConnectionHandler.process(AbstractProtocol.java:589)
    org.apache.tomcat.util.net.JIoEndpoint$SocketProcessor.run(JIoEndpoint.java:310)
    java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1142)
    java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:617)
    java.lang.Thread.run(Thread.java:745)

First Database Configuration:

@Configuration
@EnableTransactionManagement
@ComponentScan({ "org.npcc.ccms.config" })
@PropertySource(value = { "classpath:application.properties" })
public class HibernateConfig4DB1 {
    final static Logger logger = LogManager.getLogger(HibernateConfig4DB1.class);

    @Autowired
    private Environment environment;

     @Bean(name="DB1SessionFactory")
    public LocalSessionFactoryBean db1SessionFactory() {
        LocalSessionFactoryBean sessionFactory = new LocalSessionFactoryBean();
        sessionFactory.setDataSource(dataSource());
        sessionFactory.setPackagesToScan(new String[] { "org.npcc.ccms.model.db1" });
        sessionFactory.setHibernateProperties(hibernateProperties());
        return sessionFactory;
     }

    @Bean(destroyMethod="")
    public DataSource dataSource() {
        JndiTemplate jndi = new JndiTemplate();
        DataSource dataSource = null;
        try {
            dataSource = (DataSource) jndi.lookup(environment.getRequiredProperty("datasource"));
        } catch (NamingException e) {
            logger.error("NamingException for java:comp/env/jdbc/ccms_cp1_orcl", e);
        }
        return dataSource;
    }

    private Properties hibernateProperties() {
        Properties properties = new Properties();
        properties.put("hibernate.dialect", environment.getRequiredProperty("hibernate.dialect"));
        properties.put("hibernate.show_sql", environment.getRequiredProperty("hibernate.show_sql"));
        properties.put("hibernate.format_sql", environment.getRequiredProperty("hibernate.format_sql"));
        return properties;        
    }

    @Bean(name="DB1TransactionManager")
    @Autowired
    @Qualifier("DB1SessionFactory")
    public HibernateTransactionManager db1TransactionManager(SessionFactory s) {
       HibernateTransactionManager txManager = new HibernateTransactionManager();
       txManager.setSessionFactory(s);
       return txManager;
    }
}

Second database configuration:

@Configuration
@EnableTransactionManagement
@ComponentScan({ "org.npcc.ccms.config" })
@PropertySource(value = { "classpath:application.properties" })
public class HibernateConfig4DB2 {
    final static Logger logger = LogManager.getLogger(HibernateConfig4DB2.class);

    @Autowired
    private Environment environment;

     @Bean(name="DB2SessionFactory")
    public LocalSessionFactoryBean db2SessionFactory() {
        LocalSessionFactoryBean sessionFactory = new LocalSessionFactoryBean();
        sessionFactory.setDataSource(dataSource());
        sessionFactory.setPackagesToScan(new String[] { "org.npcc.ccms.model.db2" });
        sessionFactory.setHibernateProperties(hibernateProperties());
        return sessionFactory;
     }

    @Bean(destroyMethod="")
    public DataSource dataSource() {
        JndiTemplate jndi = new JndiTemplate();
        DataSource dataSource = null;
        try {
            dataSource = (DataSource) jndi.lookup(environment.getRequiredProperty("datasource"));
        } catch (NamingException e) {
            logger.error("NamingException for java:comp/env/jdbc/ccms_cp1_orcl", e);
        }
        return dataSource;
    }

    private Properties hibernateProperties() {
        Properties properties = new Properties();
        properties.put("hibernate.dialect", environment.getRequiredProperty("hibernate.dialect"));
        properties.put("hibernate.show_sql", environment.getRequiredProperty("hibernate.show_sql"));
        properties.put("hibernate.format_sql", environment.getRequiredProperty("hibernate.format_sql"));
        return properties;        
    }

    @Bean(name="DB2TransactionManager")
    @Autowired
    @Qualifier("DB2SessionFactory")
    public HibernateTransactionManager db2TransactionManager(SessionFactory s) {
       HibernateTransactionManager txManager = new HibernateTransactionManager();
       txManager.setSessionFactory(s);
       return txManager;
    }
}

Solution

  • That's because you defined 2 SessionFactory in your configuration and Spring can't guess which one to pick.

    You could autowire your sessionFactory and use it in your @Bean :

    @Autowired
    @Qualifier("DB1SessionFactory")
    private SessionFactory sessionFactory;
    
    @Bean(name="DB1TransactionManager")
    public HibernateTransactionManager db2TransactionManager() {
       HibernateTransactionManager txManager = new HibernateTransactionManager();
       txManager.setSessionFactory(this.sessionFactory);
       return txManager;
    }
    

    To explicitly choose the bean to use.

    Moreover you should note that only one configuration need to hold @EnableTransactionManagement, plus you are component scanning twice the same package which is unnecessary. Again, you have 2 @PropertySource with the same properties file, only one will be needed.

    In your case I would create a RootHibernateConfig holding @Configuration, @EnableTransactionManagement, @ComponentScan({ "org.npcc.ccms.config" }) and @PropertySource(value = { "classpath:application.properties" }) annotations + @Import with your HibernateConfig4DB1 and HibernateConfig4DB2.

    You can remove @Autowired and @Qualifier annotations from your transactionManager beans methods. Don't forget to explicitly use the needed transactionManager in your code.

    See : http://docs.spring.io/spring/docs/current/javadoc-api/org/springframework/transaction/annotation/Transactional.html

    Spring - Is it possible to use multiple transaction managers in the same application?