Spring + Hibernate + JPA 多租户实现的循环引用问题
Circular reference issue with Spring + Hibernate + JPA multi tenancy implementation
我在使用 Spring + JPA + Hibernate 使用单独的数据库方法为我的 Web 应用程序设置多租户支持时遇到问题。
我已经尝试使用我自己的 CurrentTenantIdentifierResolver
和 AbstractMultiTenantConnectionProvider
实现的 Hibernate 方式,以及使用 AbstractRoutingDataSource
的 Spring 方式。我将使用我命名为 UserRoutingDataSource
的 AbstractRoutingDataSource
解决方案来解释我的问题。
我要实现的目标如下:
- 我有一个“主”数据库,其中包含有关用户及其数据库的所有数据
- 当用户登录时,我将他的数据库 ID 保存在名为
UserSession
的会话范围 bean 中
- 我的
UserRoutingDataSource
的 targetDataSources
需要填充代表我的主数据库 CustomDatabases
table. 的所有数据库的数据源
- 当需要连接时,我的
UserRoutingDataSource
的 determineCurrentLookupKey
需要从用户的 UserSession
实例中检索数据库 ID。
在这两种情况下(Hibernate 方式/Spring 方式)我都以循环引用异常结束。当我尝试在 UserRoutingDataSource
.
中自动装配 CustomDatabasesDAO
和 UserSession
时出现问题
这是我得到的异常:
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.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.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.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
没有对 userEntityManagerFactory
的单个引用时我会得到这个异常。在 CustomDatabasesDAO
中我能想到的唯一相关的事情是我对 master 数据库的 entityManager 的调用:
@PersistenceContext(unitName = "masterEntityManagerFactory")
private EntityManager masterEntityManager;
有关更多上下文,这里是我的 UserRoutingDataSource
文件和我的应用程序上下文文件中的相关部分。
UserRoutingDataSource
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>
我已经尝试了很多东西,我不确定我是否仍然理解依赖注入是如何工作的。而且我不知道我可能会遗漏什么。
谢谢你的帮助。
解决方案:
感谢 Roman 的回答,我解决了我的问题。我也放弃了使用我的会话作用域 bean 来确定我当前查找密钥的想法,而是使用我的 Spring 安全身份验证。当用户登录时,我执行以下操作:
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);
重要的部分是我用户 UsernamePasswordAuthenticationToken
上的 setDetails(details)
。
这是我的(工作)配置的当前状态:
UserRoutingDataSource
@Component
public class UserRoutingDataSource extends AbstractRoutingDataSource {
@Autowired
public UserRoutingDataSource(CustomDatabasesDAO customDatabasesDAO) {
Map<Object, Object> targetDataSources = new HashMap<Object, Object>();
for(CustomDatabases database : customDatabasesDAO.findDatabasesByDeleted(0))
{
DriverManagerDataSource datasource = new DriverManagerDataSource();
datasource.setDriverClassName(database.getCdboDriverName());
datasource.setUrl("jdbc:sqlserver://"+database.getCdboServer()+":"+database.getCdboPort()+";databaseName="+database.getCdboAliasName() + ";");
datasource.setUsername(database.getCdboUserName());
datasource.setPassword(database.getCdboPassword());
targetDataSources.put(database.getCdboDatabaseId(), datasource);
}
/*
* This default datasource is necessary because for some reason (Hibernate, JPA related ?) the routing datasource
* calls the "determineCurrentLookupKey()" on startup which returned null ("default" now) because there is no Authentication at startup yet.
*/
targetDataSources.put("default", new DriverManagerDataSource());
setTargetDataSources(targetDataSources);
afterPropertiesSet();
}
@Override
protected Object determineCurrentLookupKey() {
UsernamePasswordAuthenticationToken auth = (UsernamePasswordAuthenticationToken) SecurityContextHolder.getContext().getAuthentication();
if (auth != null && auth.getDetails() instanceof Map)
{
HashMap<String, Object> details = (HashMap<String, Object>) auth.getDetails();
return details.get("databaseId");
}
return "default";
}
}
应用程序上下文
<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="myUser" />
<property name="password" value="myPassword" />
</bean>
<bean id="masterEntityManagerFactory" class="org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean">
<property name="persistenceUnitName" value="masterEntityManagerFactory" />
<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="userEntityManagerFactory" class="org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean">
<property name="persistenceUnitName" value="userEntityManagerFactory" />
<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" />
</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>
Spring 遍历 EntityManagerFactoryUtils.findEntityManagerFactory()
内的所有 EntityManagerFactory
以找出 属性 persistenceUnitName
值等于 [=13= 的工厂]的属性unitName
值。仅当未找到候选者时,才会使用名称为 unitName
值的 bean。因此 userEntityManagerFactory
在此过程中被实例化。
作为解决方法,您可以尝试在 CustomDatabasesDAO
class.
中使用常规 @Autowired
而不是 @PersistenceUnit
我在使用 Spring + JPA + Hibernate 使用单独的数据库方法为我的 Web 应用程序设置多租户支持时遇到问题。
我已经尝试使用我自己的 CurrentTenantIdentifierResolver
和 AbstractMultiTenantConnectionProvider
实现的 Hibernate 方式,以及使用 AbstractRoutingDataSource
的 Spring 方式。我将使用我命名为 UserRoutingDataSource
的 AbstractRoutingDataSource
解决方案来解释我的问题。
我要实现的目标如下:
- 我有一个“主”数据库,其中包含有关用户及其数据库的所有数据
- 当用户登录时,我将他的数据库 ID 保存在名为
UserSession
的会话范围 bean 中
- 我的
UserRoutingDataSource
的targetDataSources
需要填充代表我的主数据库CustomDatabases
table. 的所有数据库的数据源
- 当需要连接时,我的
UserRoutingDataSource
的determineCurrentLookupKey
需要从用户的UserSession
实例中检索数据库 ID。
在这两种情况下(Hibernate 方式/Spring 方式)我都以循环引用异常结束。当我尝试在 UserRoutingDataSource
.
CustomDatabasesDAO
和 UserSession
时出现问题
这是我得到的异常:
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.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.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.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
没有对 userEntityManagerFactory
的单个引用时我会得到这个异常。在 CustomDatabasesDAO
中我能想到的唯一相关的事情是我对 master 数据库的 entityManager 的调用:
@PersistenceContext(unitName = "masterEntityManagerFactory")
private EntityManager masterEntityManager;
有关更多上下文,这里是我的 UserRoutingDataSource
文件和我的应用程序上下文文件中的相关部分。
UserRoutingDataSource
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>
我已经尝试了很多东西,我不确定我是否仍然理解依赖注入是如何工作的。而且我不知道我可能会遗漏什么。
谢谢你的帮助。
解决方案:
感谢 Roman 的回答,我解决了我的问题。我也放弃了使用我的会话作用域 bean 来确定我当前查找密钥的想法,而是使用我的 Spring 安全身份验证。当用户登录时,我执行以下操作:
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);
重要的部分是我用户 UsernamePasswordAuthenticationToken
上的 setDetails(details)
。
这是我的(工作)配置的当前状态:
UserRoutingDataSource
@Component
public class UserRoutingDataSource extends AbstractRoutingDataSource {
@Autowired
public UserRoutingDataSource(CustomDatabasesDAO customDatabasesDAO) {
Map<Object, Object> targetDataSources = new HashMap<Object, Object>();
for(CustomDatabases database : customDatabasesDAO.findDatabasesByDeleted(0))
{
DriverManagerDataSource datasource = new DriverManagerDataSource();
datasource.setDriverClassName(database.getCdboDriverName());
datasource.setUrl("jdbc:sqlserver://"+database.getCdboServer()+":"+database.getCdboPort()+";databaseName="+database.getCdboAliasName() + ";");
datasource.setUsername(database.getCdboUserName());
datasource.setPassword(database.getCdboPassword());
targetDataSources.put(database.getCdboDatabaseId(), datasource);
}
/*
* This default datasource is necessary because for some reason (Hibernate, JPA related ?) the routing datasource
* calls the "determineCurrentLookupKey()" on startup which returned null ("default" now) because there is no Authentication at startup yet.
*/
targetDataSources.put("default", new DriverManagerDataSource());
setTargetDataSources(targetDataSources);
afterPropertiesSet();
}
@Override
protected Object determineCurrentLookupKey() {
UsernamePasswordAuthenticationToken auth = (UsernamePasswordAuthenticationToken) SecurityContextHolder.getContext().getAuthentication();
if (auth != null && auth.getDetails() instanceof Map)
{
HashMap<String, Object> details = (HashMap<String, Object>) auth.getDetails();
return details.get("databaseId");
}
return "default";
}
}
应用程序上下文
<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="myUser" />
<property name="password" value="myPassword" />
</bean>
<bean id="masterEntityManagerFactory" class="org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean">
<property name="persistenceUnitName" value="masterEntityManagerFactory" />
<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="userEntityManagerFactory" class="org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean">
<property name="persistenceUnitName" value="userEntityManagerFactory" />
<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" />
</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>
Spring 遍历 EntityManagerFactoryUtils.findEntityManagerFactory()
内的所有 EntityManagerFactory
以找出 属性 persistenceUnitName
值等于 [=13= 的工厂]的属性unitName
值。仅当未找到候选者时,才会使用名称为 unitName
值的 bean。因此 userEntityManagerFactory
在此过程中被实例化。
作为解决方法,您可以尝试在 CustomDatabasesDAO
class.
@Autowired
而不是 @PersistenceUnit