如何使用 Spring 和 JPA 设置多个数据源

Posted

技术标签:

【中文标题】如何使用 Spring 和 JPA 设置多个数据源【英文标题】:How to setup multiple data sources with Spring and JPA 【发布时间】:2018-09-30 16:16:27 【问题描述】:

在我们的应用程序中,我们希望使用 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。

错误

bean 初始化失败;嵌套异常是 org.springframework.beans.factory.NoSuchBeanDefinitionException: 否 [javax.persistence.EntityManagerFactory] ​​类型的唯一 bean 是 定义:预期的单个 bean,但发现 2 原因: org.springframework.beans.factory.NoSuchBeanDefinitionException: 否 [javax.persistence.EntityManagerFactory] ​​类型的唯一 bean 是 已定义:预期的单个 bean,但在 org.springframework.beans.factory.BeanFactoryUtils.beanOfTypeIncludingAncestors(BeanFactoryUtils.java:303) 在 org.springframework.orm.jpa.support.PersistenceAnnotationBeanPostProcessor.findDefaultEntityManagerFactory(PersistenceAnnotationBeanPostProcessor.java:451) 在 org.springframework.orm.jpa.support.PersistenceAnnotationBeanPostProcessor.findEntityManagerFactory(PersistenceAnnotationBeanPostProcessor.java:428) 在 org.springframework.orm.jpa.support.PersistenceAnnotationBeanPostProcessor$AnnotatedMember.resolveEntityManager(PersistenceAnnotationBeanPostProcessor.java:582) 在 org.springframework.orm.jpa.support.PersistenceAnnotationBeanPostProcessor$AnnotatedMember.resolve(PersistenceAnnotationBeanPostProcessor.java:553) 在 org.springframework.orm.jpa.support.PersistenceAnnotationBeanPostProcessor$AnnotatedMember.inject(PersistenceAnnotationBeanPostProcessor.java:489)

【问题讨论】:

你能发布哪个类触发了异常吗,更多的堆栈跟踪可以帮助解决这个问题 @Koitoer 我认为是因为 Spring Appplication Context 中的配置问题 这是您完整的应用程序上下文文件还是只是其中的一部分?您的异常意味着您正在按类型注入 EntityManagerFactory bean,而您必须按名称进行。 @ali4j 怎么按名字来做 你能发布完整的堆栈跟踪,而不是其中最深的元素吗? 【参考方案1】:

在多个数据源配置的情况下,我们需要定义哪个将被视为主要数据源。我们可以指定在 java config 中使用 @Primary 注解或在 XML bean config 中使用 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"/>

【讨论】:

找不到符号限定符 你用的是哪个版本的spring? 版本:2.0.5.【参考方案2】:

您是否尝试提供包含您的 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" />

【讨论】:

我的意思是为 entityManagerFactory bean 添加这两个缺失的属性 packagesToScanpersistenceXmlLocation 就我而言,packagesToScan = "com.rh.services.RcAbcMaintenanceServiceImpl"? 无效packageToScan【参考方案3】:

您发布的错误消息表明您正在按类型为 EntityManagerFactory 类型的对象自动装配。到目前为止,您显示的代码中没有一个包含这种注入,这意味着它可能存在于您尚未发布的某些代码中。

如果您要发布错误的完整堆栈跟踪,您将能够向上查看堆栈以查看哪个 bean 包含对 EntityManagerFactory 对象的不可满足的引用,这反过来又可以让您改变您的方式'正在引用它以允许引用您想要的特定 bean。

更新

根据您提供的更多信息(谢谢)和一些谷歌搜索,似乎其他用户也同样发现指定 unitName 不足以注入正确的 EntityManager。网上有几篇文章支持@lucid 的建议,即使用@Qualifier 注解让Spring 选择正确的bean,但不幸的是that annotation was introduced in 2.5 所以只有升级后才能使用它。 (考虑到您使用的 Spring 框架的年代,这可能是一个好主意,但这是一个单独的话题。)

但是,several users have indicated 在 2.0.5 中提供了另一种方法,使用引用多个数据源的单个 PersistenceUnitManager 而不是每个引用单个数据源的多个持久性单元。来自 Spring 官方文档:https://docs.spring.io/spring/docs/2.0.x/reference/orm.html#orm-jpa-multiple-pu。

总的来说,我建议您考虑升级到不超过十年的 Spring 版本,这样您就可以根据 @lucid 的答案指定一个 @Qualifier 注释。但如果由于某种原因无法做到这一点,PersistenceUnitManager 方法应该为您提供一种使其在 Spring 2.0.5 中工作的方法。

【讨论】:

我正在使用org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean【参考方案4】:

有几件事对我来说看起来不太好。设置器的名称与属性名称不匹配,我认为这很重要,第二件事是关于继承,某些注释有时仅适用于具体类而不适用于基类。我会尝试如下更改基础服务。

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;


如果这不起作用,我可能会尝试删除基类并查看是否将注释放在具体类下我可以使它起作用,我会这样做,只将注释移动到 RcAbcMaintenanceServiceImpl 类并删除继承自BaseService的extend语句

另外,我注意到PersistenceContext 注释还有另一个参数https://docs.oracle.com/javaee/6/api/javax/persistence/PersistenceContext.html,因此您也可以尝试使用名称来匹配 bean 定义上的 id。

【讨论】:

试过你的答案,但没有运气。会不会是因为&lt;aop:config&gt; 任何 github 链接,我可以在其中下载项目以查看我能做什么 对不起,有点难以给出,代码和结构太多了。在我声明两个 entityManagerFactory 后,错误仍然存​​在。不知道为什么 不要使用Spring默认使用的任何bean名称(如dataSource,transactionManager,entityManager,...),而是在它们后面加上“1”和“2”,那里您在其他地方遇到异常的可能性很小,该异常将指向您必须修复的正确位置。

以上是关于如何使用 Spring 和 JPA 设置多个数据源的主要内容,如果未能解决你的问题,请参考以下文章

如何通过使用 JPA + Hibernate 和 Spring-boot 在一个数据库中使用多个模式?

spring-data-jpa 如何使用多个数据源? [复制]

多个数据库的 Spring Data JPA 配置

使用Spring时如何注入多个JPA EntityManager(持久化单元)

如何在JAVA JPA Spring Boot中的一个SQL查询中选择多个数据

如何在 Spring Boot 中使用 Hibernate/JPA 返回多级 json