Spring + Hibernate + JPA多租户实现的循环引用问题

时间:2015-11-13 10:00:13

标签: java spring hibernate jpa multi-tenant

我在使用Spring + JPA + Hibernate使用单独的数据库方法为我的Web应用程序设置多租户支持时遇到了问题。

我尝试使用自己的CurrentTenantIdentifierResolverAbstractMultiTenantConnectionProvider实现Hibernate方式,而Spring使用AbstractRoutingDataSource。我将使用我命名为AbstractRoutingDataSource的{​​{1}}解决方案来解释我的问题。

我想要实现的目标如下:

  1. 我有一个“主”数据库,其中包含有关用户及其数据库的所有数据
  2. 当用户登录时,我将他的数据库ID保存在名为UserRoutingDataSource
  3. 的会话范围bean中
  4. 我的UserSession UserRoutingDataSource需要填充代表我主数据库targetDataSources表的所有数据库的数据源。
  5. 当需要连接时,CustomDatabases的{​​{1}}需要从用户的UserRoutingDataSource实例中检索数据库ID。
  6. 在这两种情况下(Hibernate方式/ Spring方式)我最终得到循环引用异常。当我尝试在determineCurrentLookupKey中自动加载UserSessionCustomDatabasesDAO时,会出现此问题。

    这是我得到的例外:

    UserSession

    我不理解的是,当UserRoutingDataSource没有对org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'userEntityManagerFactory' defined in class path resource [MyApp/webapp/WEB-INF/config/applicationContext.xml]: Cannot resolve reference to bean 'userRoutingDataSource' while setting bean property 'dataSource'; nested exception is org.springframework.beans.factory.UnsatisfiedDependencyException: Error creating bean with name 'userRoutingDataSource' defined in class path resource [MyApp/webapp/WEB-INF/config/applicationContext.xml]: Unsatisfied dependency expressed through constructor argument with index 0 of type [myApp.java.data.dao.Global.CustomDatabasesDAO]: : Error creating bean with name 'customDatabasesDAO': Injection of persistence dependencies failed; nested exception is org.springframework.beans.factory.BeanCurrentlyInCreationException: Error creating bean with name 'userEntityManagerFactory': FactoryBean which is currently in creation returned null from getObject; nested exception is org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'customDatabasesDAO': Injection of persistence dependencies failed; nested exception is org.springframework.beans.factory.BeanCurrentlyInCreationException: Error creating bean with name 'userEntityManagerFactory': FactoryBean which is currently in creation returned null from getObject at org.springframework.beans.factory.support.BeanDefinitionValueResolver.resolveReference(BeanDefinitionValueResolver.java:359) at org.springframework.beans.factory.support.BeanDefinitionValueResolver.resolveValueIfNecessary(BeanDefinitionValueResolver.java:108) at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.applyPropertyValues(AbstractAutowireCapableBeanFactory.java:1481) at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.populateBean(AbstractAutowireCapableBeanFactory.java:1226) at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.doCreateBean(AbstractAutowireCapableBeanFactory.java:543) at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBean(AbstractAutowireCapableBeanFactory.java:482) at org.springframework.beans.factory.support.AbstractBeanFactory$1.getObject(AbstractBeanFactory.java:305) at org.springframework.beans.factory.support.DefaultSingletonBeanRegistry.getSingleton(DefaultSingletonBeanRegistry.java:230) at org.springframework.beans.factory.support.AbstractBeanFactory.doGetBean(AbstractBeanFactory.java:301) at org.springframework.beans.factory.support.AbstractBeanFactory.getBean(AbstractBeanFactory.java:196) at org.springframework.context.support.AbstractApplicationContext.getBean(AbstractApplicationContext.java:1051) at org.springframework.context.support.AbstractApplicationContext.finishBeanFactoryInitialization(AbstractApplicationContext.java:828) at org.springframework.context.support.AbstractApplicationContext.refresh(AbstractApplicationContext.java:537) at org.springframework.web.context.ContextLoader.configureAndRefreshWebApplicationContext(ContextLoader.java:446) at org.springframework.web.context.ContextLoader.initWebApplicationContext(ContextLoader.java:328) at org.springframework.web.context.ContextLoaderListener.contextInitialized(ContextLoaderListener.java:107) at org.apache.catalina.core.StandardContext.listenerStart(StandardContext.java:4728) at org.apache.catalina.core.StandardContext.startInternal(StandardContext.java:5162) at org.apache.catalina.util.LifecycleBase.start(LifecycleBase.java:150) at org.apache.catalina.core.ContainerBase$StartChild.call(ContainerBase.java:1409) at org.apache.catalina.core.ContainerBase$StartChild.call(ContainerBase.java:1399) at java.util.concurrent.FutureTask.run(Unknown Source) at java.util.concurrent.ThreadPoolExecutor.runWorker(Unknown Source) at java.util.concurrent.ThreadPoolExecutor$Worker.run(Unknown Source) at java.lang.Thread.run(Unknown Source) Caused by: org.springframework.beans.factory.UnsatisfiedDependencyException: Error creating bean with name 'userRoutingDataSource' defined in class path resource [MyApp/webapp/WEB-INF/config/applicationContext.xml]: Unsatisfied dependency expressed through constructor argument with index 0 of type [myApp.java.data.dao.Global.CustomDatabasesDAO]: : Error creating bean with name 'customDatabasesDAO': Injection of persistence dependencies failed; nested exception is org.springframework.beans.factory.BeanCurrentlyInCreationException: Error creating bean with name 'userEntityManagerFactory': FactoryBean which is currently in creation returned null from getObject; nested exception is org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'customDatabasesDAO': Injection of persistence dependencies failed; nested exception is org.springframework.beans.factory.BeanCurrentlyInCreationException: Error creating bean with name 'userEntityManagerFactory': FactoryBean which is currently in creation returned null from getObject at org.springframework.beans.factory.support.ConstructorResolver.createArgumentArray(ConstructorResolver.java:749) at org.springframework.beans.factory.support.ConstructorResolver.autowireConstructor(ConstructorResolver.java:185) at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.autowireConstructor(AbstractAutowireCapableBeanFactory.java:1143) at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBeanInstance(AbstractAutowireCapableBeanFactory.java:1046) at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.doCreateBean(AbstractAutowireCapableBeanFactory.java:510) at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBean(AbstractAutowireCapableBeanFactory.java:482) at org.springframework.beans.factory.support.AbstractBeanFactory$1.getObject(AbstractBeanFactory.java:305) at org.springframework.beans.factory.support.DefaultSingletonBeanRegistry.getSingleton(DefaultSingletonBeanRegistry.java:230) at org.springframework.beans.factory.support.AbstractBeanFactory.doGetBean(AbstractBeanFactory.java:301) at org.springframework.beans.factory.support.AbstractBeanFactory.getBean(AbstractBeanFactory.java:196) at org.springframework.beans.factory.support.BeanDefinitionValueResolver.resolveReference(BeanDefinitionValueResolver.java:351) ... 24 more Caused by: org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'customDatabasesDAO': Injection of persistence dependencies failed; nested exception is org.springframework.beans.factory.BeanCurrentlyInCreationException: Error creating bean with name 'userEntityManagerFactory': FactoryBean which is currently in creation returned null from getObject at org.springframework.orm.jpa.support.PersistenceAnnotationBeanPostProcessor.postProcessPropertyValues(PersistenceAnnotationBeanPostProcessor.java:357) at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.populateBean(AbstractAutowireCapableBeanFactory.java:1214) at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.doCreateBean(AbstractAutowireCapableBeanFactory.java:543) at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBean(AbstractAutowireCapableBeanFactory.java:482) at org.springframework.beans.factory.support.AbstractBeanFactory$1.getObject(AbstractBeanFactory.java:305) at org.springframework.beans.factory.support.DefaultSingletonBeanRegistry.getSingleton(DefaultSingletonBeanRegistry.java:230) at org.springframework.beans.factory.support.AbstractBeanFactory.doGetBean(AbstractBeanFactory.java:301) at org.springframework.beans.factory.support.AbstractBeanFactory.getBean(AbstractBeanFactory.java:196) at org.springframework.beans.factory.support.DefaultListableBeanFactory.findAutowireCandidates(DefaultListableBeanFactory.java:1192) at org.springframework.beans.factory.support.DefaultListableBeanFactory.doResolveDependency(DefaultListableBeanFactory.java:1116) at org.springframework.beans.factory.support.DefaultListableBeanFactory.resolveDependency(DefaultListableBeanFactory.java:1014) at org.springframework.beans.factory.support.ConstructorResolver.resolveAutowiredArgument(ConstructorResolver.java:813) at org.springframework.beans.factory.support.ConstructorResolver.createArgumentArray(ConstructorResolver.java:741) ... 34 more Caused by: org.springframework.beans.factory.BeanCurrentlyInCreationException: Error creating bean with name 'userEntityManagerFactory': FactoryBean which is currently in creation returned null from getObject at org.springframework.beans.factory.support.FactoryBeanRegistrySupport.doGetObjectFromFactoryBean(FactoryBeanRegistrySupport.java:181) at org.springframework.beans.factory.support.FactoryBeanRegistrySupport.getObjectFromFactoryBean(FactoryBeanRegistrySupport.java:127) at org.springframework.beans.factory.support.AbstractBeanFactory.getObjectForBeanInstance(AbstractBeanFactory.java:1584) at org.springframework.beans.factory.support.AbstractBeanFactory.doGetBean(AbstractBeanFactory.java:253) at org.springframework.beans.factory.support.AbstractBeanFactory.getBean(AbstractBeanFactory.java:196) at org.springframework.orm.jpa.EntityManagerFactoryUtils.findEntityManagerFactory(EntityManagerFactoryUtils.java:130) at org.springframework.orm.jpa.support.PersistenceAnnotationBeanPostProcessor.findNamedEntityManagerFactory(PersistenceAnnotationBeanPostProcessor.java:556) at org.springframework.orm.jpa.support.PersistenceAnnotationBeanPostProcessor.findEntityManagerFactory(PersistenceAnnotationBeanPostProcessor.java:538) at org.springframework.orm.jpa.support.PersistenceAnnotationBeanPostProcessor$PersistenceElement.resolveEntityManager(PersistenceAnnotationBeanPostProcessor.java:707) at org.springframework.orm.jpa.support.PersistenceAnnotationBeanPostProcessor$PersistenceElement.getResourceToInject(PersistenceAnnotationBeanPostProcessor.java:680) at org.springframework.beans.factory.annotation.InjectionMetadata$InjectedElement.inject(InjectionMetadata.java:169) at org.springframework.beans.factory.annotation.InjectionMetadata.inject(InjectionMetadata.java:88) at org.springframework.orm.jpa.support.PersistenceAnnotationBeanPostProcessor.postProcessPropertyValues(PersistenceAnnotationBeanPostProcessor.java:354) ... 46 more 的单一引用时,我会收到此异常。我在CustomDatabasesDAO中可以想到的唯一相关的事情是我对master数据库的entityManager的调用:

    userEntityManagerFactory

    有关更多上下文,请参阅我的CustomDatabasesDAO文件以及应用程序上下文文件中的相关部分。

    UserRoutingDataSource

    @PersistenceContext(unitName = "masterEntityManagerFactory")
    private EntityManager masterEntityManager;
    

    应用程序上下文

    UserRoutingDataSource

    我尝试了很多东西,我不确定我是否仍然理解依赖注入的工作原理。我不知道自己可能缺少什么。

    谢谢你的帮助。

    解决方案:

    感谢Roman的回答,我解决了我的问题。我也放弃了使用我的会话范围bean来确定我当前的查找键的想法,而是使用我的Spring Security身份验证。当用户登录时,我执行以下操作:

    public class UserRoutingDataSource extends AbstractRoutingDataSource {
    
        /*private CustomDatabasesDAO customDatabasesDAO;
    
        @Autowired
        public void setCustomDatabasesDAO(final CustomDatabasesDAO customDatabasesDAO)
        {
            this.customDatabasesDAO = customDatabasesDAO;
        }*/
    
        @Autowired
        private UserSession session;
    
        /*@Autowired
        public void setUserSession(final UserSession session)
        {
            this.session = session;
        }*/
    
        @Autowired
        public UserRoutingDataSource(CustomDatabasesDAO customDatabasesDAO) {
            Map<Object, Object> targetDataSources = new HashMap<Object, Object>();
    
            for(CustomDatabases database : customDatabasesDAO.findDatabasesByDeleted(0))
            {
                // All the information necessary for the datasource will eventually be retrieved from the database variable
                DriverManagerDataSource datasource = new DriverManagerDataSource();
                datasource.setDriverClassName("com.microsoft.sqlserver.jdbc.SQLServerDriver");
                datasource.setUrl("jdbc:sqlserver://localhost:1433;databaseName=" + database.getCdboAliasName() + ";");
                datasource.setUsername("username");
                datasource.setPassword("password");
    
                targetDataSources.put(String.valueOf(database.getCdboDatabaseId()), datasource);
            }
    
            setTargetDataSources(targetDataSources);
        }
    
    
        @Override
        protected Object determineCurrentLookupKey() {
            return session.getCdboDatabaseId();
        }
    }
    

    重要的部分是我的用户/*<!--<bean id="currentTenantIdentifierResolverImpl" class="myApp.java.config.CurrentTenantIdentifierResolverImpl" /> <bean id="multiTenantConnectionProvider" class="myApp.java.config.MultiTenantConnectionProvider" />-->*/ <bean id="masterDataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource"> <property name="driverClassName" value="com.microsoft.sqlserver.jdbc.SQLServerDriver" /> <property name="url" value="jdbc:sqlserver://localhost:1433;databaseName=myDatabase;" /> <property name="username" value="sa" /> <property name="password" value="password" /> </bean> <bean id="masterEntityManagerFactory" class="org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean"> <property name="dataSource" ref="masterDataSource" /> <property name="packagesToScan" value="myApp.java.data.model" /> <property name="jpaVendorAdapter"> <bean class="org.springframework.orm.jpa.vendor.HibernateJpaVendorAdapter" /> </property> <property name="jpaProperties"> <props> <prop key="hibernate.dialect">org.hibernate.dialect.SQLServerDialect</prop> <prop key="hibernate.show_sql">false</prop> <prop key="hibernate.use_outer_join">true</prop> </props> </property> </bean> <bean id="userRoutingDataSource" class="myApp.java.config.UserRoutingDataSource"> <property name="targetDataSources"> <map /> </property> </bean> <bean id="userEntityManagerFactory" class="org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean"> <property name="dataSource" ref="userRoutingDataSource" /> <property name="packagesToScan" value="myApp.java.data.model" /> <property name="jpaVendorAdapter"> <bean class="org.springframework.orm.jpa.vendor.HibernateJpaVendorAdapter" /> </property> <property name="jpaProperties"> <map> <entry key="hibernate.dialect" value="org.hibernate.dialect.SQLServerDialect" /> <entry key="hibernate.show_sql" value="false" /> <entry key="hibernate.use_outer_join" value="true" /> /*<!--<entry key="hibernate.tenant_identifier_resolver" value-ref="currentTenantIdentifierResolverImpl" /> <entry key="hibernate.multi_tenant_connection_provider" value-ref="multiTenantConnectionProvider" /> <entry key="hibernate.multiTenancy" value="DATABASE" />-->*/ </map> </property> </bean> <!-- Transaction managers --> <tx:annotation-driven /> <bean id="masterTransactionManager" class="org.springframework.orm.jpa.JpaTransactionManager"> <property name="entityManagerFactory" ref="masterEntityManagerFactory" /> <qualifier value="master" /> </bean> <bean id="userTransactionManager" class="org.springframework.orm.jpa.JpaTransactionManager"> <property name="entityManagerFactory" ref="userEntityManagerFactory" /> <qualifier value="user" /> </bean> 上的Authentication auth = SecurityContextHolder.getContext().getAuthentication(); List<GrantedAuthority> authorities = new ArrayList<GrantedAuthority>(); authorities.add(new SimpleGrantedAuthority("ROLE_USER")); UsernamePasswordAuthenticationToken newAuth = new UsernamePasswordAuthenticationToken(auth.getPrincipal(), auth.getCredentials(), authorities); HashMap<String, Object> details = new HashMap<String, Object>(); details.put("databaseId", session.getCdboDatabaseId()); newAuth.setDetails(details); SecurityContextHolder.getContext().setAuthentication(newAuth);

    以下是我(工作)配置的当前状态:

    UserRoutingDataSource

    setDetails(details)

    应用程序上下文

    UsernamePasswordAuthenticationToken

1 个答案:

答案 0 :(得分:0)

Spring迭代EntityManagerFactory内的所有EntityManagerFactoryUtils.findEntityManagerFactory(),找出属性persistenceUnitName的值等于@PersistenceUnit的属性unitName值的工厂。仅当未找到候选者时,才会使用名称为unitName的bean。因此userEntityManagerFactory在此过程中被实例化。

作为一种解决方法,您可以尝试在@Autowired课程中使用常规@PersistenceUnit代替CustomDatabasesDAO