如何使用 Spring 和 JPA 设置多个数据源
How to setup multiple data sources with Spring and JPA
在我们的应用程序中,我们希望使用 Spring 和 JPA 设置多个数据源。因此我们创建了 2 个 entityManagerFactory、2 个数据源和 2 个事务管理器。
web.xml
<param-value>
/WEB-INF/a_spring.xml
/WEB-INF/b_spring.xml
</param-value>
Persistence.xml
<?xml version="1.0" encoding="UTF-8"?>
<persistence version="1.0" xmlns="http://java.sun.com/xml/ns/persistence" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://java.sun.com/xml/ns/persistence http://java.sun.com/xml/ns/persistence/persistence_1_0.xsd">
<persistence-unit name="db1" transaction-type="RESOURCE_LOCAL">
<class>com.rh.domain.RcA</class>
</persistence-unit>
<persistence-unit name="db2" transaction-type="RESOURCE_LOCAL">
<class>com.rh.domain.Rcb</class>
</persistence-unit>
</persistence>
a_spring.xml
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:tx="http://www.springframework.org/schema/tx"
xmlns:aop="http://www.springframework.org/schema/aop"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/tx
http://www.springframework.org/schema/tx/spring-tx-2.0.xsd
http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop-2.0.xsd">
<bean class="org.springframework.orm.jpa.support.PersistenceAnnotationBeanPostProcessor"/>
<bean id = "RcMaintenanceService" class="com.rh.services.RcAbcMaintenanceServiceImpl" autowire="byName" />
<aop:config>
<aop:pointcut id="rOperation" expression="execution(* com.rh.services.*.*(..))"/>
<aop:advisor advice-ref="txAdvice" pointcut-ref="rOperation"/>
</aop:config>
<tx:advice id="txAdvice" transaction-manager="transactionManager">
<tx:attributes>
<tx:method name="*"/>
</tx:attributes>
</tx:advice>
<bean id="dataSource" class="org.springframework.jndi.JndiObjectFactoryBean">
<property name="jndiName" value="java:comp/env/jdbc/db1" />
</bean>
<bean id="transactionManager" class="org.springframework.orm.jpa.JpaTransactionManager">
<property name="entityManagerFactory" ref="entityManagerFactory"/>
<property name="dataSource" ref="dataSource"/>
</bean>
<bean id="entityManagerFactory" class="org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean">
<property name="persistenceUnitName" value="db1" />
<property name="dataSource" ref="dataSource"/>
<property name="jpaVendorAdapter">
<bean class="org.springframework.orm.jpa.vendor.HibernateJpaVendorAdapter">
<property name="showSql" value="true"/>
<property name="generateDdl" value="false"/>
<property name="database" value="MYSQL" />
<property name="databasePlatform" value="org.hibernate.dialect.MySQL5Dialect"/>
</bean>
</property>
<property name="jpaDialect">
<bean class="org.springframework.orm.jpa.vendor.HibernateJpaDialect">
</bean>
</property>
</bean>
我还向 b_spring.xml.
声明了另一个 entityManagetFactory、Transaction Manager 和 dataSource
错误
Initialization of bean failed; nested exception is
org.springframework.beans.factory.NoSuchBeanDefinitionException: No
unique bean of type [javax.persistence.EntityManagerFactory] is
defined: expected single bean but found 2 Caused by:
org.springframework.beans.factory.NoSuchBeanDefinitionException: No
unique bean of type [javax.persistence.EntityManagerFactory] is
defined: expected single bean but found 2 at
org.springframework.beans.factory.BeanFactoryUtils.beanOfTypeIncludingAncestors(BeanFactoryUtils.java:303)
at
org.springframework.orm.jpa.support.PersistenceAnnotationBeanPostProcessor.findDefaultEntityManagerFactory(PersistenceAnnotationBeanPostProcessor.java:451)
at
org.springframework.orm.jpa.support.PersistenceAnnotationBeanPostProcessor.findEntityManagerFactory(PersistenceAnnotationBeanPostProcessor.java:428)
at
org.springframework.orm.jpa.support.PersistenceAnnotationBeanPostProcessor$AnnotatedMember.resolveEntityManager(PersistenceAnnotationBeanPostProcessor.java:582)
at
org.springframework.orm.jpa.support.PersistenceAnnotationBeanPostProcessor$AnnotatedMember.resolve(PersistenceAnnotationBeanPostProcessor.java:553)
at
org.springframework.orm.jpa.support.PersistenceAnnotationBeanPostProcessor$AnnotatedMember.inject(PersistenceAnnotationBeanPostProcessor.java:489)
有几件事对我来说不太好。 setter 的名称与 属性 名称不匹配,我认为这很重要,第二件事是关于继承,一些注释有时只适用于具体 classes 而不是基础 class是的。我会尝试如下更改基本服务。
public class BaseService {
@PersistenceContext(unitName = "db2")
private EntityManager em;
@PersistenceContext(unitName = "db1")
private EntityManager em1;
public EntityManager getEm() {
return em;
}
protected EntityManager getEm2() {
return em1;
}
public void setEm(EntityManager entityMgr) {
this.em = entityMgr;
}
public void setEm1(EntityManager entityMgr) {
this.em = entityMgr;
}
}
如果这不起作用,我可能会尝试移除基础 class,看看我是否将注释放在混凝土下面 class 我可以让它起作用,我会这样做,只移动RcAbcMaintenanceServiceImpl
class 的注释并删除继承自 BaseService
的扩展语句
此外,我注意到 PersistenceContext
注释有另一个参数 https://docs.oracle.com/javaee/6/api/javax/persistence/PersistenceContext.html,因此您也可以尝试使用名称来匹配 bean 定义中的 id。
在多个数据源配置的情况下,我们需要定义哪个将被视为主要数据源。我们可以指定在 java 配置中使用 @Primary
注释或在 XML bean 配置中使用 primary=true
。
由于在XML中创建了两个实体管理器,我们需要使用@Qualifier
来指定应该在何处注入哪个bean。在你的情况下,是这样的。
@PersistenceContext(unitName = "db1")
public void setEntityManager(@Qualifier("entityManagerFactory") EntityManager entityMgr) {
this.em = entityMgr;
}
对于XML配置,我们可以这样做
<bean id="BaseService" class="x.y.z.BaseService">
<property name="em" ref="entityManagerFactory"/>
<property name="em1" ref="entityManagerFactory1"/>
</bean>
<bean id = "RcMaintenanceService" class="com.rh.services.RcAbcMaintenanceServiceImpl" autowire="byName" parent="BaseService"/>
您是否尝试提供包含您的 EntityManagerFactory bean 的包详细信息?
您可以在 bean 定义中以 属性 形式提供包详细信息 -
<property name="packagesToScan" value="com.XX.XX.XX.XX" />
新属性要添加到这个块-
<bean id="entityManagerFactory1" class="org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean">
<property name="persistenceUnitName" value="db2" />
<property name="dataSource" ref="dataSource1"/>
<-- add here for both beans -->
<property name="jpaVendorAdapter">
<bean class="org.springframework.orm.jpa.vendor.HibernateJpaVendorAdapter">
<property name="showSql" value="true"/>
<property name="generateDdl" value="false"/>
<property name="database" value="MYSQL" />
<property name="databasePlatform" value="org.hibernate.dialect.MySQL5Dialect"/>
</bean>
</property>
<property name="jpaDialect">
<bean class="org.springframework.orm.jpa.vendor.HibernateJpaDialect">
</bean>
</property>
</bean>
此外,您还缺少 persistenceXmlLocation
属性 -
<property name="persistenceXmlLocation" value="***/persistence.xml" />
您 post 编辑的错误消息表明您正在按类型为类型为 EntityManagerFactory 的对象自动装配。 None 您目前展示的代码中包含此类注入,这意味着它可能在您尚未 post 编辑的某些代码中。
如果您要 post 错误的完整堆栈跟踪,您将能够向上遍历堆栈以查看哪个 bean 包含对 EntityManagerFactory 对象的不可满足引用,而该对象又是会让您更改引用它的方式,以允许引用您想要的特定 bean。
更新
根据您提供的更多信息(谢谢)和一些谷歌搜索,似乎其他用户也类似地发现指定 unitName 不足以注入正确的 EntityManager。几个 post 在线备份 @lucid 的建议使用 @Qualifier 注释来获得 Spring 到 select 正确的 bean,但不幸的是 that annotation was introduced in 2.5 所以它只对你可用你升级。 (考虑到您使用的 Spring 框架的年龄,这可能是个好主意,但这是一个单独的话题。)
然而,several users have indicated that an alternate approach is available in 2.0.5, using a single PersistenceUnitManager that references multiple data sources rather than multiple persistence units that each reference a single data source. From the official Spring docs: https://docs.spring.io/spring/docs/2.0.x/reference/orm.html#orm-jpa-multiple-pu.
总的来说,我建议您考虑升级到不超过十年的 Spring 版本,这样您就可以根据 @lucid 的回答指定一个 @Qualifier 注释。但如果由于某种原因这不可能,PersistenceUnitManager 方法应该为您提供一种使其在 Spring 2.0.5.
中工作的方法
在我们的应用程序中,我们希望使用 Spring 和 JPA 设置多个数据源。因此我们创建了 2 个 entityManagerFactory、2 个数据源和 2 个事务管理器。
web.xml
<param-value>
/WEB-INF/a_spring.xml
/WEB-INF/b_spring.xml
</param-value>
Persistence.xml
<?xml version="1.0" encoding="UTF-8"?>
<persistence version="1.0" xmlns="http://java.sun.com/xml/ns/persistence" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://java.sun.com/xml/ns/persistence http://java.sun.com/xml/ns/persistence/persistence_1_0.xsd">
<persistence-unit name="db1" transaction-type="RESOURCE_LOCAL">
<class>com.rh.domain.RcA</class>
</persistence-unit>
<persistence-unit name="db2" transaction-type="RESOURCE_LOCAL">
<class>com.rh.domain.Rcb</class>
</persistence-unit>
</persistence>
a_spring.xml
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:tx="http://www.springframework.org/schema/tx"
xmlns:aop="http://www.springframework.org/schema/aop"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/tx
http://www.springframework.org/schema/tx/spring-tx-2.0.xsd
http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop-2.0.xsd">
<bean class="org.springframework.orm.jpa.support.PersistenceAnnotationBeanPostProcessor"/>
<bean id = "RcMaintenanceService" class="com.rh.services.RcAbcMaintenanceServiceImpl" autowire="byName" />
<aop:config>
<aop:pointcut id="rOperation" expression="execution(* com.rh.services.*.*(..))"/>
<aop:advisor advice-ref="txAdvice" pointcut-ref="rOperation"/>
</aop:config>
<tx:advice id="txAdvice" transaction-manager="transactionManager">
<tx:attributes>
<tx:method name="*"/>
</tx:attributes>
</tx:advice>
<bean id="dataSource" class="org.springframework.jndi.JndiObjectFactoryBean">
<property name="jndiName" value="java:comp/env/jdbc/db1" />
</bean>
<bean id="transactionManager" class="org.springframework.orm.jpa.JpaTransactionManager">
<property name="entityManagerFactory" ref="entityManagerFactory"/>
<property name="dataSource" ref="dataSource"/>
</bean>
<bean id="entityManagerFactory" class="org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean">
<property name="persistenceUnitName" value="db1" />
<property name="dataSource" ref="dataSource"/>
<property name="jpaVendorAdapter">
<bean class="org.springframework.orm.jpa.vendor.HibernateJpaVendorAdapter">
<property name="showSql" value="true"/>
<property name="generateDdl" value="false"/>
<property name="database" value="MYSQL" />
<property name="databasePlatform" value="org.hibernate.dialect.MySQL5Dialect"/>
</bean>
</property>
<property name="jpaDialect">
<bean class="org.springframework.orm.jpa.vendor.HibernateJpaDialect">
</bean>
</property>
</bean>
我还向 b_spring.xml.
声明了另一个 entityManagetFactory、Transaction Manager 和 dataSource错误
Initialization of bean failed; nested exception is org.springframework.beans.factory.NoSuchBeanDefinitionException: No unique bean of type [javax.persistence.EntityManagerFactory] is defined: expected single bean but found 2 Caused by: org.springframework.beans.factory.NoSuchBeanDefinitionException: No unique bean of type [javax.persistence.EntityManagerFactory] is defined: expected single bean but found 2 at org.springframework.beans.factory.BeanFactoryUtils.beanOfTypeIncludingAncestors(BeanFactoryUtils.java:303) at org.springframework.orm.jpa.support.PersistenceAnnotationBeanPostProcessor.findDefaultEntityManagerFactory(PersistenceAnnotationBeanPostProcessor.java:451) at org.springframework.orm.jpa.support.PersistenceAnnotationBeanPostProcessor.findEntityManagerFactory(PersistenceAnnotationBeanPostProcessor.java:428) at org.springframework.orm.jpa.support.PersistenceAnnotationBeanPostProcessor$AnnotatedMember.resolveEntityManager(PersistenceAnnotationBeanPostProcessor.java:582) at org.springframework.orm.jpa.support.PersistenceAnnotationBeanPostProcessor$AnnotatedMember.resolve(PersistenceAnnotationBeanPostProcessor.java:553) at org.springframework.orm.jpa.support.PersistenceAnnotationBeanPostProcessor$AnnotatedMember.inject(PersistenceAnnotationBeanPostProcessor.java:489)
有几件事对我来说不太好。 setter 的名称与 属性 名称不匹配,我认为这很重要,第二件事是关于继承,一些注释有时只适用于具体 classes 而不是基础 class是的。我会尝试如下更改基本服务。
public class BaseService {
@PersistenceContext(unitName = "db2")
private EntityManager em;
@PersistenceContext(unitName = "db1")
private EntityManager em1;
public EntityManager getEm() {
return em;
}
protected EntityManager getEm2() {
return em1;
}
public void setEm(EntityManager entityMgr) {
this.em = entityMgr;
}
public void setEm1(EntityManager entityMgr) {
this.em = entityMgr;
}
}
如果这不起作用,我可能会尝试移除基础 class,看看我是否将注释放在混凝土下面 class 我可以让它起作用,我会这样做,只移动RcAbcMaintenanceServiceImpl
class 的注释并删除继承自 BaseService
此外,我注意到 PersistenceContext
注释有另一个参数 https://docs.oracle.com/javaee/6/api/javax/persistence/PersistenceContext.html,因此您也可以尝试使用名称来匹配 bean 定义中的 id。
在多个数据源配置的情况下,我们需要定义哪个将被视为主要数据源。我们可以指定在 java 配置中使用 @Primary
注释或在 XML bean 配置中使用 primary=true
。
由于在XML中创建了两个实体管理器,我们需要使用@Qualifier
来指定应该在何处注入哪个bean。在你的情况下,是这样的。
@PersistenceContext(unitName = "db1")
public void setEntityManager(@Qualifier("entityManagerFactory") EntityManager entityMgr) {
this.em = entityMgr;
}
对于XML配置,我们可以这样做
<bean id="BaseService" class="x.y.z.BaseService">
<property name="em" ref="entityManagerFactory"/>
<property name="em1" ref="entityManagerFactory1"/>
</bean>
<bean id = "RcMaintenanceService" class="com.rh.services.RcAbcMaintenanceServiceImpl" autowire="byName" parent="BaseService"/>
您是否尝试提供包含您的 EntityManagerFactory bean 的包详细信息?
您可以在 bean 定义中以 属性 形式提供包详细信息 -
<property name="packagesToScan" value="com.XX.XX.XX.XX" />
新属性要添加到这个块-
<bean id="entityManagerFactory1" class="org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean">
<property name="persistenceUnitName" value="db2" />
<property name="dataSource" ref="dataSource1"/>
<-- add here for both beans -->
<property name="jpaVendorAdapter">
<bean class="org.springframework.orm.jpa.vendor.HibernateJpaVendorAdapter">
<property name="showSql" value="true"/>
<property name="generateDdl" value="false"/>
<property name="database" value="MYSQL" />
<property name="databasePlatform" value="org.hibernate.dialect.MySQL5Dialect"/>
</bean>
</property>
<property name="jpaDialect">
<bean class="org.springframework.orm.jpa.vendor.HibernateJpaDialect">
</bean>
</property>
</bean>
此外,您还缺少 persistenceXmlLocation
属性 -
<property name="persistenceXmlLocation" value="***/persistence.xml" />
您 post 编辑的错误消息表明您正在按类型为类型为 EntityManagerFactory 的对象自动装配。 None 您目前展示的代码中包含此类注入,这意味着它可能在您尚未 post 编辑的某些代码中。
如果您要 post 错误的完整堆栈跟踪,您将能够向上遍历堆栈以查看哪个 bean 包含对 EntityManagerFactory 对象的不可满足引用,而该对象又是会让您更改引用它的方式,以允许引用您想要的特定 bean。
更新
根据您提供的更多信息(谢谢)和一些谷歌搜索,似乎其他用户也类似地发现指定 unitName 不足以注入正确的 EntityManager。几个 post 在线备份 @lucid 的建议使用 @Qualifier 注释来获得 Spring 到 select 正确的 bean,但不幸的是 that annotation was introduced in 2.5 所以它只对你可用你升级。 (考虑到您使用的 Spring 框架的年龄,这可能是个好主意,但这是一个单独的话题。)
然而,several users have indicated that an alternate approach is available in 2.0.5, using a single PersistenceUnitManager that references multiple data sources rather than multiple persistence units that each reference a single data source. From the official Spring docs: https://docs.spring.io/spring/docs/2.0.x/reference/orm.html#orm-jpa-multiple-pu.
总的来说,我建议您考虑升级到不超过十年的 Spring 版本,这样您就可以根据 @lucid 的回答指定一个 @Qualifier 注释。但如果由于某种原因这不可能,PersistenceUnitManager 方法应该为您提供一种使其在 Spring 2.0.5.
中工作的方法