[spring jpa] 解决SimpleJpaRepository的多数据源配置问题

前言

  前段时间使用spring jpa做了一个项目,由于涉及到了多个数据库,因此需要进行多数据源的配置。网上找了很多的资料,尝试着配置,都以失败告终。之后通过断点最终完成了多数据源的配置。这篇博客主要为了记录下,使用SimpleJpaRepository如何配置多数据源。也希望可以帮助到更多的人。

 

环境

java版本:8

框架: spring boot(2.0.4.RELEASE)、jpa

数据库:mysql

 

配置步骤

  • 目录结果

 

  • pom文件
<parent>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-parent</artifactId>
    <version>2.0.4.RELEASE</version>
    <relativePath/>
  </parent>
  <dependencies>
    <dependency>
      <groupId>junit</groupId>
      <artifactId>junit</artifactId>
      <version>4.12</version>
      <scope>test</scope>
    </dependency>
    <dependency>
      <groupId>org.springframework.boot</groupId>
      <artifactId>spring-boot-starter</artifactId>
    </dependency>
    <dependency>
      <groupId>org.springframework.boot</groupId>
      <artifactId>spring-boot-starter-data-jpa</artifactId>
    </dependency>
      <dependency>
          <groupId>org.springframework.boot</groupId>
          <artifactId>spring-boot-starter-test</artifactId>
          <scope>test</scope>
      </dependency>
    <dependency>
      <groupId>mysql</groupId>
      <artifactId>mysql-connector-java</artifactId>
      <version>${mysql.driver.version}</version>
      <scope>runtime</scope>
    </dependency>
    <dependency>
      <groupId>com.alibaba</groupId>
      <artifactId>druid</artifactId>
      <version>1.0.19</version>
    </dependency>
  </dependencies>

 

  • application.yml
spring:
  application:
    name: multi-database
  datasource:
    main:
      url: jdbc:mysql://localhost:3306/test_main?useUnicode=true&characterEncoding=utf-8&useSSL=false
      username: root
      password: ***
    slave:
      url: jdbc:mysql://localhost:3306/test_slave?useUnicode=true&characterEncoding=utf-8&useSSL=false
      username: root
      password: ***
  jpa:
    properties:
      hibernate:
        ddl-auto: update
        naming:
          physical-strategy: org.springframework.boot.orm.jpa.hibernate.SpringPhysicalNamingStrategy
    generate-ddl: true
    show-sql: true
    database: mysql
    database-platform: org.hibernate.dialect.MySQL5InnoDBDialect

配置文件中我配置了两个数据源,一个是 main ,一个是 slave 。

 

  • 数据连接池配置

DruidDBConfig.java:

@Configuration
public class DruidDBConfig {

    @Value("${spring.datasource.main.url}")
    private String mainDBUrl;

    @Value("${spring.datasource.main.username}")
    private String mainUsername;

    @Value("${spring.datasource.main.password}")
    private String mainPassword;

    @Value("${spring.datasource.slave.url}")
    private String slaveDBUrl;

    @Value("${spring.datasource.slave.username}")
    private String slaveUsername;

    @Value("${spring.datasource.slave.password}")
    private String slavePassword;

    @Value("com.mysql.jdbc.Driver")
    private String driverClassName;

    @Value("5")
    private int initialSize;

    @Value("5")
    private int minIdle;

    @Value("20")
    private int maxActive;

    @Value("60000")
    private int maxWait;

    /**
     * 配置间隔多久才进行一次检测,检测需要关闭的空闲连接,单位是毫秒
     */
    @Value("60000")
    private int timeBetweenEvictionRunsMillis;
    /**
     * 配置一个连接在池中最小生存的时间,单位是毫秒
     */
    @Value("300000")
    private int minEvictableIdleTimeMillis;

    @Value("SELECT 1 FROM DUAL")
    private String validationQuery;

    @Value("true")
    private boolean testWhileIdle;

    @Value("false")
    private boolean testOnBorrow;

    @Value("false")
    private boolean testOnReturn;

    /**
     * 打开PSCache,并且指定每个连接上PSCache的大小
     */
    @Value("true")
    private boolean poolPreparedStatements;

    @Value("20")
    private int maxPoolPreparedStatementPerConnectionSize;

    @Value("druid.stat.mergeSql=true;druid.stat.slowSqlMillis=500")
    private String connectionProperties;

    @Bean(name = "mainDataSource")
    @Qualifier("mainDataSource")
    @Primary // 主数据源,如果有两个数据源,没有指定数据源则默认为该数据源
    public DataSource mainDataSource() {
        return getDruidDataSource(mainUsername, mainPassword, mainDBUrl);
    }

    @Bean(name = "slaveDataSource")
    @Qualifier("slaveDataSource")
    public DataSource slaveDataSource() {
        return getDruidDataSource(slaveUsername, slavePassword, slaveDBUrl);
    }

    private DruidDataSource getDruidDataSource(String username, String password, String url) {
        DruidDataSource datasource = new DruidDataSource();

        datasource.setUrl(url);
        datasource.setUsername(username);
        datasource.setPassword(password);
        datasource.setDriverClassName(driverClassName);

        //configuration
        datasource.setInitialSize(initialSize);
        datasource.setMinIdle(minIdle);
        datasource.setMaxActive(maxActive);
        datasource.setMaxWait(maxWait);
        datasource.setTimeBetweenEvictionRunsMillis(timeBetweenEvictionRunsMillis);
        datasource.setMinEvictableIdleTimeMillis(minEvictableIdleTimeMillis);
        datasource.setValidationQuery(validationQuery);
        datasource.setTestWhileIdle(testWhileIdle);
        datasource.setTestOnBorrow(testOnBorrow);
        datasource.setTestOnReturn(testOnReturn);
        datasource.setPoolPreparedStatements(poolPreparedStatements);
        datasource.setMaxPoolPreparedStatementPerConnectionSize(maxPoolPreparedStatementPerConnectionSize);
        datasource.setConnectionProperties(connectionProperties);

        return datasource;
    }
}

在这里统一管理两个数据源。

 

  • 主数据源的配置

MainDataSourceConfig.java:

@Configuration
@EnableTransactionManagement
@EnableJpaRepositories(entityManagerFactoryRef = "entityManagerFactoryMain"
        , transactionManagerRef = "transactionManagerMain"
        , basePackages = {"com.yun.demo.dao.main"})// dao所在的位置
public class MainDataSourceConfig {
    @Autowired
    @Qualifier("mainDataSource")
    private DataSource mainDataSource;

    @Autowired
    private JpaProperties jpaProperties;


    @Primary
    @Bean(name = "entityManagerMain")
    public EntityManager entityManager(EntityManagerFactoryBuilder builder) {
        return SharedEntityManagerCreator.createSharedEntityManager(entityManagerFactoryMain(builder).getObject()); // 这里比较关键,这里我们要创建一个SharedEntityManager,不然无法在SimpleJpa上使用多个数据源
    }
    @Primary
    @Bean(name = "entityManagerFactoryMain")
    public LocalContainerEntityManagerFactoryBean entityManagerFactoryMain (EntityManagerFactoryBuilder builder) {
        return builder
                .dataSource(mainDataSource)
                .properties(getVendorProperties())
                .packages("com.yun.demo.entity.main") //设置实体类所在位置
                .persistenceUnit("mainPersistenceUnit")
                .build();
    }

    @Primary
    @Bean(name = "transactionManagerMain")
    public PlatformTransactionManager transactionManagerMain(EntityManagerFactoryBuilder builder) {
        return new JpaTransactionManager(entityManagerFactoryMain(builder).getObject());
    }


    private Map<String, Object> getVendorProperties() {
        HibernateSettings hibernateSettings = new HibernateSettings();
        return jpaProperties.getHibernateProperties(hibernateSettings);
    }
}

由于 SimpleJpaRepository 使用的是 SharedEntityManager 去管理的,而网上大部分帖子都不是使用它,因此如果你的类继承了 SimpleJpaRepository ,而没有有配置 SimpleJpaRepository ,就会报错 no transaction is in progress 。下面就是报错信息:

Caused by: javax.persistence.TransactionRequiredException: no transaction is in progress
    at org.hibernate.internal.SessionImpl.checkTransactionNeeded(SessionImpl.java:3505)
    at org.hibernate.internal.SessionImpl.doFlush(SessionImpl.java:1427)
    at org.hibernate.internal.SessionImpl.flush(SessionImpl.java:1423)
    at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
    at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
    at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
    at java.lang.reflect.Method.invoke(Method.java:498)
    at org.springframework.orm.jpa.ExtendedEntityManagerCreator$ExtendedEntityManagerInvocationHandler.invoke(ExtendedEntityManagerCreator.java:350)
    at com.sun.proxy.$Proxy71.flush(Unknown Source)
    at org.springframework.data.jpa.repository.support.SimpleJpaRepository.flush(SimpleJpaRepository.java:534)
    at org.springframework.data.jpa.repository.support.SimpleJpaRepository.saveAndFlush(SimpleJpaRepository.java:505)
    at org.springframework.data.jpa.repository.support.SimpleJpaRepository$$FastClassBySpringCGLIB$$31f56960.invoke(<generated>)
    at org.springframework.cglib.proxy.MethodProxy.invoke(MethodProxy.java:204)
    at org.springframework.aop.framework.CglibAopProxy$CglibMethodInvocation.invokeJoinpoint(CglibAopProxy.java:746)
    at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:163)
    at org.springframework.dao.support.PersistenceExceptionTranslationInterceptor.invoke(PersistenceExceptionTranslationInterceptor.java:139)
    ... 37 more

 

  • 从数据源的配置

SlaveDataSourceConfig.java:

@Configuration
@EnableTransactionManagement
@EnableJpaRepositories(entityManagerFactoryRef = "entityManagerFactorySlave"
        , transactionManagerRef = "transactionManagerSlave"
        , basePackages = {"com.yun.demo.dao.slave"})
public class SlaveDataSourceConfig {
    @Autowired
    @Qualifier("slaveDataSource")
    private DataSource slaveDataSource;

    @Autowired
    private JpaProperties jpaProperties;


    @Bean(name = "entityManagerSlave")
    public EntityManager entityManager(EntityManagerFactoryBuilder builder) {
        return SharedEntityManagerCreator.createSharedEntityManager(entityManagerFactorySlave(builder).getObject());
    }
    @Bean(name = "entityManagerFactorySlave")
    public LocalContainerEntityManagerFactoryBean entityManagerFactorySlave (EntityManagerFactoryBuilder builder) {
        return builder
                .dataSource(slaveDataSource)
                .properties(getVendorProperties())
                .packages("com.yun.demo.entity.slave") //设置实体类所在位置
                .persistenceUnit("slavePersistenceUnit")
                .build();
    }

    @Bean(name = "transactionManagerSlave")
    public PlatformTransactionManager transactionManagerSlave(EntityManagerFactoryBuilder builder) {
        return new JpaTransactionManager(entityManagerFactorySlave(builder).getObject());
    }


    private Map<String, Object> getVendorProperties() {
        HibernateSettings hibernateSettings = new HibernateSettings();
        return jpaProperties.getHibernateProperties(hibernateSettings);
    }
}

从库的配置跟主库的配置差不多。

 

  • 告诉dao这里我们需要使用从库的数据源

SlaveDao.java:

package com.yun.demo.dao.slave;  //这里是配置从库的数据源的basePackages地址

import com.yun.demo.entity.slave.Address;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.data.jpa.repository.support.JpaEntityInformationSupport;
import org.springframework.stereotype.Repository;

import javax.persistence.EntityManager;

@Repository
public class AddressDao extends MySimpleJpaRepository<Address, Long> {

    @Autowired //这里需要使用@Qualifier("entityManagerSlave")将数据源配置到从库
    public AddressDao(@Qualifier("entityManagerSlave") EntityManager entityManager) {
        super(JpaEntityInformationSupport.getEntityInformation(Address.class, entityManager), entityManager);
    }

}

 

  • 修改从库 SimpleJpaRepository 的事务管理器

由于 SimpleJpaRepository  的事务管理器是默认没有使用从库的事务,因此从库如果使用 SimpleJpaRepository ,就会报以下错误:

Caused by: javax.persistence.TransactionRequiredException: no transaction is in progress
    at org.hibernate.internal.SessionImpl.checkTransactionNeeded(SessionImpl.java:3505)
    at org.hibernate.internal.SessionImpl.doFlush(SessionImpl.java:1427)
    at org.hibernate.internal.SessionImpl.flush(SessionImpl.java:1423)
    at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
    at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
    at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
    at java.lang.reflect.Method.invoke(Method.java:498)
    at org.springframework.orm.jpa.ExtendedEntityManagerCreator$ExtendedEntityManagerInvocationHandler.invoke(ExtendedEntityManagerCreator.java:350)
    at com.sun.proxy.$Proxy71.flush(Unknown Source)
    at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
    at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
    at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
    at java.lang.reflect.Method.invoke(Method.java:498)
    at org.springframework.orm.jpa.SharedEntityManagerCreator$SharedEntityManagerInvocationHandler.invoke(SharedEntityManagerCreator.java:305)
    at com.sun.proxy.$Proxy71.flush(Unknown Source)
    at org.springframework.data.jpa.repository.support.SimpleJpaRepository.flush(SimpleJpaRepository.java:534)
    at org.springframework.data.jpa.repository.support.SimpleJpaRepository.saveAndFlush(SimpleJpaRepository.java:505)
    at org.springframework.data.jpa.repository.support.SimpleJpaRepository$$FastClassBySpringCGLIB$$31f56960.invoke(<generated>)
    at org.springframework.cglib.proxy.MethodProxy.invoke(MethodProxy.java:204)
    at org.springframework.aop.framework.CglibAopProxy$CglibMethodInvocation.invokeJoinpoint(CglibAopProxy.java:746)
    at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:163)
    at org.springframework.dao.support.PersistenceExceptionTranslationInterceptor.invoke(PersistenceExceptionTranslationInterceptor.java:139)
    ... 37 more

解决上面的问题,需要重写 SimpleJpaRepository 类。不多说,上代码:

package com.yun.demo.dao.slave;

import org.springframework.data.jpa.repository.support.JpaEntityInformation;
import org.springframework.data.jpa.repository.support.SimpleJpaRepository;
import org.springframework.transaction.annotation.Transactional;

import javax.persistence.EntityManager;

public class MySimpleJpaRepository<T, ID> extends SimpleJpaRepository<T, ID> {
    public MySimpleJpaRepository(JpaEntityInformation<T, ?> entityInformation, EntityManager entityManager) {
        super(entityInformation, entityManager);
    }

    @Override
    @Transactional(transactionManager = "transactionManagerSlave") // 在这里我们告诉这个方法,我们需要使用从库的事务管理器
    public <S extends T> S saveAndFlush(S entity) {
        S result = this.save(entity);
        this.flush();
        return result;
    }
}

上面的类中我只举了一个方法的例子,如果你有更多的增、删、改方法需要用,那么都需要重写。

 

至此我们应该就可以解决上面的问题了。

转载于:https://www.cnblogs.com/cafebabe-yun/p/10679448.html

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
Spring Boot是一个用于构建独立的、可执行的Spring应用程序的框架,简化了Spring应用程序的配置和部署。JPA(Java Persistence API)是一种用于管理Java对象和关系数据库之间映射的规范。Druid是阿里巴巴开源的关系型数据库连接池。 在Spring Boot中配置多数据源需要以下几步: 1. 引入相关依赖:需要引入Spring Boot、Spring Data JPA和Druid的相关依赖。 2. 配置数据源:在application.properties或application.yml文件中配置多个数据源的连接信息,并指定每个数据源的名称和相关属性。 3. 配置数据源连接池:使用@ConfigurationProperties注解创建多个数据源的连接池对象,并指定数据源的名称以及相关属性。 4. 配置实体管理器工厂:为每个数据源配置对应的实体管理器工厂,用于处理JPA实体与数据库之间的映射关系。 5. 配置事务管理器:为每个数据源配置对应的事务管理器,用于处理事务操作。 6. 配置数据源路由:创建动态数据源,根据传入的数据源名称选择对应的数据源进行操作。 7. 配置JPARepository:创建接口继承JpaRepository,用于定义数据访问方法。 通过以上步骤配置多数据源后,就可以在Spring Boot应用程序中使用多个数据源进行数据库的操作。可以根据需要在Service或Controller中使用@PersistenceContext注解指定具体的数据源,或者使用@Primary注解指定默认的数据源。 总结:通过Spring Boot的自动配置和Druid的连接池,可以很方便地实现多数据源配置。使用JPA进行数据操作,能够有效地减少开发人员编写SQL语句的工作量,提高开发效率。通过合理的配置,可以根据需要选择不同的数据源进行操作,实现灵活的数据访问。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值