Spring-Data-JPA多数据源配置

文章目录

  • 1 简介
  • 2 环境
  • 3 配置
    • 3.1 application.yml配置
    • 3.2 配置主数据源
    • 3.3 配置次数据源
    • 3.4 使用验证
  • 4 结语
  • 5 附件

1 简介

近期给团队小伙伴做技术支持时,其从网上copy的jpa多数据源配置项目遇到了各种数据库连接异常抑或事务失效的情况,故专门新建了一个纯净对jpa多数据源配置进行了整理。本文主要展示Jpa多数据源不使用jta和使用jta分布式事务的项目配置,并编写了一些示例验证事务特性。本文演示不详尽之处,文末会附上整个项目源码下载链接,并欢迎批评指正。

2 环境

  • spring-boot 2.3.2.RELEASE
  • spring-boot-starter-data-jpa
  • spring-boot-starter-jta-atomikos
  • jdk 1.8
  • mysql5.8

springboot选择了2.3.2.RELEASE版本,在较低版本(测试了2.1.4)下会出现jta配置异常找不到jtaPlatform的错误。

3 配置

3.1 application.yml配置

spring:
  main:
    allow-bean-definition-overriding: true

  # 使用springboot默认的hikari数据库连接池,配置比较简单明了
  datasource:
    common: &ds_common
      minimun-idle: 5
      maximum-pool-size: 15
      max-life-time: 600000
      idle-timeout: 300000
      validation-timeout: 5000
      connection-timeout: 5000
    #门架原始数据查询数据库
    main:
      <<: *ds_common
      pool-name: mainDataSource
      jdbc-url: jdbc:mysql:///main?useUnicode=true&characterEncoding=UTF8&autoReconnect=true&useSSL=false&serverTimezone=Asia/Shanghai
      username: root
      password: root
      connection-test-query: select 1
    slave:
      <<: *ds_common
      pool-name: slaveDataSource
      jdbc-url: jdbc:mysql:///slave?useUnicode=true&characterEncoding=UTF8&autoReconnect=true&useSSL=false&serverTimezone=Asia/Shanghai
      username: root
      password: root
      connection-test-query: select 1


  jpa:
    common: &jpa_common
      show-sql: true
      generate-ddl: true
      properties:
        hibernate:
          hbm2ddl:
            auto: update
    mysql:
      <<: *jpa_common
      database: mysql
      database-platform: org.hibernate.dialect.MySQL57Dialect
#      非jta事务
  jta:
    enabled: false

logging:
  file:
    path: D:\demo\logs\multi-datasource
#    调试所需
#  level:
#    org:
#      hibernate: trace

server:
  port: 8888

此处有些配置是默认也列出来重复配置了一遍,方便后期项目调优调试。

3.2 配置主数据源

@Configuration
@EnableTransactionManagement
@EnableJpaRepositories(entityManagerFactoryRef = "mainEntityManagerFactory",
        transactionManagerRef = "mainTransactionManager",
//        此处的packages用于repository扫描管理
        basePackages = {"com.kyq.multids.modules.main"})
public class MainDataSourceConfig {
    @Value("${spring.jta.enabled: false}")
    private boolean jta;

    @Primary
    @Bean("mysqlJpaProperties")
    @ConfigurationProperties(prefix = "spring.jpa.mysql")
    public JpaProperties mysqlJpaProperties() {
        return new JpaProperties();
    }


    @Primary
    @Bean("mainDataSource")
    @ConfigurationProperties(prefix = "spring.datasource.main")
    public DataSource mainDataSource(){
        return DataSourceBuilder.create().build();
    }

    @Primary
    @Bean("mainEntityManagerFactory")
    public LocalContainerEntityManagerFactoryBean mainFactoryBean(@Qualifier("mainDataSource") DataSource dataSource,
                                                                  @Qualifier("mysqlJpaProperties") JpaProperties jpaProperties){
        HibernateJpaVendorAdapter jpaVendorAdapter = new HibernateJpaVendorAdapter();
        jpaVendorAdapter.setGenerateDdl(jpaProperties.isGenerateDdl());
        jpaVendorAdapter.setDatabasePlatform(jpaProperties.getDatabasePlatform());
        jpaVendorAdapter.setShowSql(jpaProperties.isShowSql());
        jpaVendorAdapter.setDatabase(jpaProperties.getDatabase());

        return new EntityManagerFactoryBuilder(jpaVendorAdapter, jpaProperties.getProperties(), null)
                .dataSource(dataSource)
                .jta(jta)
                .persistenceUnit("mainEntityManagerFactory")
//                此处的packages用于domain扫描管理
                .packages("com.kyq.multids.modules.main")
                .build();
    }

    @Primary
    @Bean
    public JdbcTemplate mainJdbcTemplate(@Qualifier("mainDataSource") DataSource mainDataSource){
        JdbcTemplate jdbcTemplate = new JdbcTemplate(mainDataSource);
        return jdbcTemplate;
    }

    @Primary
    @Bean("mainTransactionManager")
    public PlatformTransactionManager mainTransactionManager(@Qualifier("mainEntityManagerFactory") EntityManagerFactory entityManagerFactory){

        return new JpaTransactionManager(entityManagerFactory);
    }

//    @Primary
//    @Bean
//    public EntityManager mainEntityManager(@Qualifier("mainEntityManagerFactory") LocalContainerEntityManagerFactoryBean entityManagerFactoryBean){
//        return entityManagerFactoryBean.getObject().createEntityManager();
//    }
  • @EnableJpaRepositories注解配置了jpa的repository接口扫描信息,使用的事务管理器,entityManagerFactoryBean,扫描包等信息,而LocalContainerEntityManagerFactoryBean的packages则设置了jpa的domain的扫描和实体生成规则,sql是否打印等,需要留意同步更新。
  • 千万不能配置entityManager,使用下述方式注入的entityManager是org.hibernate.internal.SessionImpl的代理实例,是没办法重复使用的,会产生各种难以找到原因的BUG,具体可以参照我另一篇文章的介绍。

3.3 配置次数据源

@Configuration
@EnableTransactionManagement
@EnableJpaRepositories(entityManagerFactoryRef = "slaveEntityManagerFactory",
        transactionManagerRef = "slaveTransactionManager",
        basePackages = {"com.kyq.multids.modules.slave"})
public class SlaveDataSourceConfig {

    @Value("${spring.jta.enabled: false}")
    private boolean jta;

    @Bean("slaveDataSource")
    @ConfigurationProperties(prefix = "spring.datasource.slave")
    public DataSource slaveDataSource(){
        return DataSourceBuilder.create().build();
    }

    @Bean("slaveEntityManagerFactory")
    public LocalContainerEntityManagerFactoryBean slaveFactoryBean(@Qualifier("slaveDataSource") DataSource dataSource,
                                                                  @Qualifier("mysqlJpaProperties") JpaProperties jpaProperties){
        HibernateJpaVendorAdapter jpaVendorAdapter = new HibernateJpaVendorAdapter();
        jpaVendorAdapter.setGenerateDdl(jpaProperties.isGenerateDdl());
        jpaVendorAdapter.setDatabasePlatform(jpaProperties.getDatabasePlatform());
        jpaVendorAdapter.setShowSql(jpaProperties.isShowSql());
        jpaVendorAdapter.setDatabase(jpaProperties.getDatabase());

        return new EntityManagerFactoryBuilder(jpaVendorAdapter, jpaProperties.getProperties(), null)
                .dataSource(dataSource)
                .jta(jta)
                .persistenceUnit("slaveEntityManagerFactory")
                .packages("com.kyq.multids.modules.slave")
                .build();
    }

    @Bean("slaveTransactionManager")
    public PlatformTransactionManager slaveTransactionManager(@Qualifier("slaveEntityManagerFactory")EntityManagerFactory entityManagerFactory){
        return new JpaTransactionManager(entityManagerFactory);
    }

    @Bean
    public JdbcTemplate slaveJdbcTemplate(@Qualifier("slaveDataSource")DataSource slaveDataSource){
        return new JdbcTemplate(slaveDataSource);
    }
}

3.4 使用验证

//MultiDataSourceTestServiceImpl.java
/**
 * Description: com.kyq.multids.modules.index.service.impl
 * 注意,因为本项目有着多个transactionManager各自管理各自的数据库,
 *      nonjta项目的MultiDataSourceTestServiceImpl是没有在class上添加@Transactional的。
 *
 * CopyRight:  © 2015 CSTC. All rights reserved.
 * Company: cstc
 *
 * @author kyq1024
 * @version 1.0
 * @timestamp 2021-08-18 14:27
 */
@Service
public class MultiDataSourceTestServiceImpl implements MultiDataSourceTestService {
	......
    /**
     * 此处Qualifier注解指向的是一个beanFactory对象而不是一个entityManger对象,对此有兴趣可参见我的另一篇博客对此有详细说明。
     * */
    @Autowired
    @Qualifier("mainEntityManagerFactory")
    EntityManager mainEntityManager;

    @PersistenceContext(unitName = "slaveEntityManagerFactory")
    EntityManager slaveEntityManager;
    /**
     * 测试使用原生SQL语句跨数据库查询数据
     *
     * @return*/
    public Map<String, Object> findDataAcrossDs(){
        Map<String, Object> ret = new HashMap<String, Object>(4);

        Query userQuery = mainEntityManager.createNativeQuery("select * from base_user")
                .unwrap(NativeQuery.class)
                .setResultTransformer(AliasToEntityMapResultTransformer.INSTANCE);
        List userList = userQuery.getResultList();


        Query dictQuery = slaveEntityManager.createNativeQuery("select * from base_dict")
                .unwrap(NativeQuery.class)
                .setResultTransformer(AliasToEntityMapResultTransformer.INSTANCE);
        List dictList = dictQuery.getResultList();

        ret.put("user", userList);
        ret.put("dict", dictList);
        return ret;
    }
        /**
     * 测试跨数据库保存数据
     * case1: 使用jdbcTemplate进行数据库修改操作,指定txManager为transactionManager时,user和dict都不会保存,回滚正常;
     * case2: 指定txManager为slaveTransactionManager时,user会保存成功;
     *
     * 触发了两次txInfo,分别是addDictByJdbcOnError和addByTemplate
     * */
//    @Transactional(rollbackFor = RuntimeException.class)
    @Transactional(transactionManager = "slaveTransactionManager", rollbackFor = RuntimeException.class)
    public void addByTemplate(){
        userService.addUserByJdbc();
        dictService.addDictByJdbcOnError();
    }
    ......
}

4 结语

经过上述简单配置,已经可以实现jpa的多数据源操作,且在有需要也可使用jdbcTemplate进行原生sql操作并保证单数据源事务回滚。如果需要实现多数据源的分布式事务,则需要引入jta进行事务管理即可实现,本文就不再详述,有兴趣可以下载附件Demo查看完整的配置源码(含本文演示的源码和事务测试示例)。

5 附件

  • 本Demo源码下载地址
  • entityManager注入分析

你可能感兴趣的:(Spring,Data,JPA,spring,boot,java,jpa多数据源,jta配置)