• [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;
        }
    }

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

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

  • 相关阅读:
    PostgreSQL中的partition-wise join
    Partition-wise join
    外观模式 门面模式 Facade 结构型 设计模式(十三)
    桥接模式 桥梁模式 bridge 结构型 设计模式(十二)
    组合模式 合成模式 COMPOSITE 结构型 设计模式(十一)
    创建型设计模式对比总结 设计模式(八)
    原型模式 prototype 创建型 设计模式(七)
    单例模式 创建型 设计模式(六)
    建造者模式 生成器模式 创建型 设计模式(五)
    抽象工厂模式 创建型 设计模式(四)
  • 原文地址:https://www.cnblogs.com/cafebabe-yun/p/10679448.html
Copyright © 2020-2023  润新知