Spring boot 版本:2.4.x 扩展资料:

:::warning 说明:
本页的自定义多数据源的背景是:能明确的是哪些 mapper 使用哪些数据源,在配置数据源的时候直接绑定死的,并不是动态数据源的方案,动态数据源的方案只需要配置一次 SqlSessionFactory,因为数据源是动态切换的。我们这里不是动态数据源方案,是静态配置。
静态和动态举例:

  • 静态:非常明确哪些 Mappser 使用某个数据源,假设业务 A 需要使用数据源 A,业务 B 需要使用数据源 B
  • 动态:同一套 mapper,可以使用多个数据源,换句话说,多个数据源上的表结构是完全一样的;动态的核心是可以切换数据源,所以也可以使用动态的方式来实现静态的功能。

本人的业务需求会包含这 2 种方式。本文实现静态方式,下一篇实现动态方式 :::

MyBatis 基础知识

MyBatis 官方入门文档

编程配置 MyBatis 的核心是构建 SqlSessionFactory,需要提供的有:数据源、Mapper.class 和 Mapper.xml 配置、事物工厂配置

  1. DataSource dataSource = BlogDataSourceFactory.getBlogDataSource();
  2. TransactionFactory transactionFactory = new JdbcTransactionFactory();
  3. Environment environment = new Environment("development", transactionFactory, dataSource);
  4. Configuration configuration = new Configuration(environment);
  5. configuration.addMapper(BlogMapper.class);
  6. SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(configuration);

但是在于 Spring 整合的时候,其中 Mapper.xml 和数据源 是需要通过代码完成配置的,而 mapper.class 则会使用 @MapperScan完成配置

多数据源配置的原理

  1. spring:
  2. datasource:
  3. type: com.alibaba.druid.pool.DruidDataSource
  4. driverClassName: com.mysql.cj.jdbc.Driver
  5. url: jdbc:mysql://127.0.0.1:3307/dpa-be?serverTimezone=Asia/Shanghai&useUnicode=true&characterEncoding=utf-8&useSSL=true
  6. username: root
  7. password: root
  8. druid:
  9. # 数据源名称
  10. name: db1
  11. # 连接池的配置信息
  12. # 初始化大小,最小,最大
  13. initialSize: 5
  14. minIdle: 5
  15. maxActive: 20
  16. # 配置获取连接等待超时的时间
  17. maxWait: 60000
  18. # 配置间隔多久才进行一次检测,检测需要关闭的空闲连接,单位是毫秒
  19. timeBetweenEvictionRunsMillis: 60000
  20. # 配置一个连接在池中最小生存的时间,单位是毫秒
  21. minEvictableIdleTimeMillis: 300000
  22. validationQuery: SELECT 1 FROM DUAL
  23. testWhileIdle: true
  24. testOnBorrow: false
  25. testOnReturn: false
  26. # 打开PSCache,并且指定每个连接上PSCache的大小
  27. poolPreparedStatements: true
  28. maxPoolPreparedStatementPerConnectionSize: 20
  29. # 配置监控统计拦截的filters,去掉后监控界面sql无法统计,'wall'用于防火墙
  30. filters: stat,wall,slf4j
  31. # 通过connectProperties属性来打开mergeSql功能;慢SQL记录
  32. connectionProperties: druid.stat.mergeSql=true;druid.stat.slowSqlMillis=5000
  33. # 配置 DruidStatFilter
  34. web-stat-filter:
  35. enabled: true
  36. url-pattern: "/*"
  37. exclusions: "*.js,*.gif,*.jpg,*.bmp,*.png,*.css,*.ico,/druid/*"
  38. # 配置DruidStatViewServlet
  39. stat-view-servlet:
  40. enabled: true
  41. url-pattern: "/druid/*"
  42. # IP白名单(没有配置或者为空,则允许所有访问)
  43. # allow: 127.0.0.1,192.168.163.1
  44. allow: ""
  45. # IP黑名单 (存在共同时,deny优先于allow)
  46. # deny: 192.168.1.73
  47. # 禁用HTML页面上的“Reset All”功能
  48. reset-enable: false
  49. # 登录名 和 密码
  50. login-username: admin
  51. login-password: 123456

上面的 yml 配置是 org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration中支持的通用方式,但是从 druid 开始,这是每个数据源自动配置实现的,而不是 DataSourceAutoConfiguration 实现的,这一点一定要明白。
我们可以在 Druid 的官方文档找到多数据源的配置,如果你看仔细看看 DruidDataSourceAutoConfigure 的自动配置源码的话,会搞明白一些事情,下面源码中的注释会说明

  1. package com.alibaba.druid.spring.boot.autoconfigure;
  2. import javax.sql.DataSource;
  3. import com.alibaba.druid.pool.DruidDataSource;
  4. import com.alibaba.druid.spring.boot.autoconfigure.properties.DruidStatProperties;
  5. import com.alibaba.druid.spring.boot.autoconfigure.stat.DruidFilterConfiguration;
  6. import com.alibaba.druid.spring.boot.autoconfigure.stat.DruidSpringAopConfiguration;
  7. import com.alibaba.druid.spring.boot.autoconfigure.stat.DruidStatViewServletConfiguration;
  8. import com.alibaba.druid.spring.boot.autoconfigure.stat.DruidWebStatFilterConfiguration;
  9. import org.slf4j.Logger;
  10. import org.slf4j.LoggerFactory;
  11. import org.springframework.boot.autoconfigure.AutoConfigureBefore;
  12. import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
  13. import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
  14. import org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration;
  15. import org.springframework.boot.autoconfigure.jdbc.DataSourceProperties;
  16. import org.springframework.boot.context.properties.EnableConfigurationProperties;
  17. import org.springframework.context.annotation.Bean;
  18. import org.springframework.context.annotation.Configuration;
  19. import org.springframework.context.annotation.Import;
  20. /**
  21. * @author lihengming [89921218@qq.com]
  22. */
  23. @Configuration
  24. // 如果没有提供 DruidDataSource 实现类则该自动配置生效,但是我们一般返回的都是 DataSource 类,而不是 DruidDataSource
  25. // 所以该类的部分配置会被加载
  26. @ConditionalOnClass(DruidDataSource.class)
  27. // 设置在 boot 自动配置前执行该配置类
  28. @AutoConfigureBefore(DataSourceAutoConfiguration.class)
  29. // 开启配置文件
  30. // DruidStatProperties 绑定:spring.datasource.druid,这个是针对单个应用配置的 web 监控页面,统计之类的
  31. // DataSourceProperties 绑定:spring.datasource,这个是通用的基础,就是数据库 url、用户名、密码之类的
  32. @EnableConfigurationProperties({DruidStatProperties.class, DataSourceProperties.class})
  33. // 下面导入了一些配置
  34. @Import({DruidSpringAopConfiguration.class,
  35. // 只要配置了 spring.datasource.druid.stat-view-servlet.enabled 则该配置生效,对应的是页面上的 SQL 监控、SQL 防火墙
  36. DruidStatViewServletConfiguration.class,
  37. // 只要配置了 spring.datasource.druid.web-stat-filter.enabled,则该配置生效,对应的是监控页面上的 Web 应用、URI 监控等功能
  38. DruidWebStatFilterConfiguration.class, //
  39. DruidFilterConfiguration.class})
  40. public class DruidDataSourceAutoConfigure {
  41. private static final Logger LOGGER = LoggerFactory.getLogger(DruidDataSourceAutoConfigure.class);
  42. // 这里是如果不存在 DataSource 则初始化一个 DruidDataSourceWrapper 数据源
  43. @Bean(initMethod = "init")
  44. @ConditionalOnMissingBean
  45. public DataSource dataSource() {
  46. LOGGER.info("Init DruidDataSource");
  47. return new DruidDataSourceWrapper();
  48. }
  49. }

从上面的自动配置来看,我们可以得出几个信息:

  1. 监控页面等配置和数据源配置是分离的,一个应用只能配置一个
  2. 数据源可以自己初始化多个
  3. Druid 的自动配置会在 boot 的自动配置 DataSourceAutoConfiguration 之前运行,所以会产生 DataSource,而 boot 的自动配置就不会走 DataSource 的构建了,但是会有其他依赖 DataSource 的自动配置构建(比如默认的事物管理器)。

那么下面我们就可以开始自定义配置多数据源了。

Spring boot 中配置多数据源

Druid 官方 druid-spring-boot-starter 官方文档

  1. runtimeOnly 'mysql:mysql-connector-java'
  2. implementation 'org.springframework.boot:spring-boot-starter-jdbc'
  3. implementation 'com.alibaba:druid-spring-boot-starter:1.2.4'
  4. implementation "tk.mybatis:mapper-spring-boot-starter:2.1.5"
  5. implementation "tk.mybatis:mapper:4.1.5"

我这里使用了 tk mybatis,在配置上,仅仅是引入的依赖和@MapperScan 注解使用的包路径不一致外,其他的和原生的一致
先搞定 yaml 的配置文件,看看如何写是比较合理的

  1. spring:
  2. datasource:
  3. druid:
  4. # 让 druid 的自动配置生效,配置监控相关功能
  5. # 配置 DruidStatFilter
  6. web-stat-filter:
  7. enabled: true
  8. url-pattern: "/*"
  9. exclusions: "*.js,*.gif,*.jpg,*.bmp,*.png,*.css,*.ico,/druid/*"
  10. # 配置DruidStatViewServlet
  11. stat-view-servlet:
  12. enabled: true
  13. url-pattern: "/druid/*"
  14. # IP白名单(没有配置或者为空,则允许所有访问)
  15. # allow: 127.0.0.1,192.168.163.1
  16. allow: ""
  17. # IP黑名单 (存在共同时,deny优先于allow)
  18. # deny: 192.168.1.73
  19. # 禁用HTML页面上的“Reset All”功能
  20. reset-enable: false
  21. # 登录名 和 密码
  22. login-username: admin
  23. login-password: 123456
  24. # 多数据源配置
  25. db01:
  26. name: DB-01
  27. url: jdbc:mysql://127.0.0.1:3307/test1?serverTimezone=Asia/Shanghai&useUnicode=true&characterEncoding=utf-8&useSSL=true
  28. username: root
  29. password: root
  30. # 连接池的配置信息
  31. # 初始化大小,最小,最大
  32. initialSize: 5
  33. minIdle: 5
  34. maxActive: 20
  35. # 配置获取连接等待超时的时间
  36. maxWait: 60000
  37. # 配置间隔多久才进行一次检测,检测需要关闭的空闲连接,单位是毫秒
  38. timeBetweenEvictionRunsMillis: 60000
  39. # 配置一个连接在池中最小生存的时间,单位是毫秒
  40. minEvictableIdleTimeMillis: 300000
  41. validationQuery: SELECT 1 FROM DUAL
  42. testWhileIdle: true
  43. testOnBorrow: false
  44. testOnReturn: false
  45. # 打开PSCache,并且指定每个连接上PSCache的大小
  46. poolPreparedStatements: true
  47. maxPoolPreparedStatementPerConnectionSize: 20
  48. # 配置监控统计拦截的filters,去掉后监控界面sql无法统计,'wall'用于防火墙
  49. filters: stat,wall,slf4j
  50. # 通过connectProperties属性来打开mergeSql功能;慢SQL记录
  51. connectionProperties: druid.stat.mergeSql=true;druid.stat.slowSqlMillis=5000
  52. # 多数据源配置
  53. db02:
  54. name: DB-02
  55. url: jdbc:mysql://127.0.0.1:3307/test2?serverTimezone=Asia/Shanghai&useUnicode=true&characterEncoding=utf-8&useSSL=true
  56. username: root
  57. password: root
  58. # 连接池的配置信息
  59. # 初始化大小,最小,最大
  60. initialSize: 5
  61. minIdle: 5
  62. maxActive: 20
  63. # 配置获取连接等待超时的时间
  64. maxWait: 60000
  65. # 配置间隔多久才进行一次检测,检测需要关闭的空闲连接,单位是毫秒
  66. timeBetweenEvictionRunsMillis: 60000
  67. # 配置一个连接在池中最小生存的时间,单位是毫秒
  68. minEvictableIdleTimeMillis: 300000
  69. validationQuery: SELECT 1 FROM DUAL
  70. testWhileIdle: true
  71. testOnBorrow: false
  72. testOnReturn: false
  73. # 打开PSCache,并且指定每个连接上PSCache的大小
  74. poolPreparedStatements: true
  75. maxPoolPreparedStatementPerConnectionSize: 20
  76. # 配置监控统计拦截的filters,去掉后监控界面sql无法统计,'wall'用于防火墙
  77. filters: stat,wall,slf4j
  78. # 通过connectProperties属性来打开mergeSql功能;慢SQL记录
  79. connectionProperties: druid.stat.mergeSql=true;druid.stat.slowSqlMillis=5000

:::warning 这里需要明白的是:

  1. spring.datasource.druid的配置会触发 DruidDataSourceAutoConfigure 的自动配置生效,让监控等配置进行自动配置
  2. 至于 spring.datasource 下的其他配置项,如 db01、db02 这个配置是需要自己处理的配置项 :::

配置 数据源和 SqlSessionFactory

  1. package cn.mrcode.autoconfig.mybatis.primary;
  2. import com.alibaba.druid.spring.boot.autoconfigure.DruidDataSourceBuilder;
  3. import org.mybatis.spring.SqlSessionFactoryBean;
  4. import org.mybatis.spring.transaction.SpringManagedTransactionFactory;
  5. import org.springframework.beans.factory.annotation.Qualifier;
  6. import org.springframework.boot.context.properties.ConfigurationProperties;
  7. import org.springframework.context.annotation.Bean;
  8. import org.springframework.context.annotation.Configuration;
  9. import org.springframework.context.annotation.Primary;
  10. import org.springframework.core.io.Resource;
  11. import org.springframework.core.io.support.PathMatchingResourcePatternResolver;
  12. import org.springframework.core.io.support.ResourcePatternResolver;
  13. import org.springframework.jdbc.datasource.DataSourceTransactionManager;
  14. import org.springframework.transaction.jta.TransactionFactory;
  15. import tk.mybatis.spring.annotation.MapperScan;
  16. import javax.sql.DataSource;
  17. import java.io.IOException;
  18. import java.util.ArrayList;
  19. import java.util.Arrays;
  20. import java.util.List;
  21. @Configuration
  22. @MapperScan(
  23. // 自动生成 mapper 放到了 original 下
  24. // 自定义的 mapper 放到了 ext 下
  25. value = {
  26. "cn.mrcode.repo.mapper.ext",
  27. "cn.mrcode.repo.mapper.original"
  28. },
  29. // 让 mapper 与 sqlSession 绑定
  30. sqlSessionFactoryRef = "primarySqlSessionFactoryBean")
  31. public class PrimaryMyBatisConfigurer {
  32. @Bean("primaryDataSource")
  33. @ConfigurationProperties(prefix = "spring.datasource.db01")
  34. @Primary
  35. public DataSource dataSource() {
  36. return DruidDataSourceBuilder.create().build();
  37. }
  38. /**
  39. * 配置 mybatis
  40. */
  41. @Bean("primarySqlSessionFactoryBean")
  42. @Primary
  43. public SqlSessionFactoryBean sqlSessionFactoryBean(@Qualifier("primaryDataSource") DataSource dataSource) throws IOException {
  44. SqlSessionFactoryBean sqlSessionFactoryBean = new SqlSessionFactoryBean();
  45. sqlSessionFactoryBean.setDataSource(dataSource);
  46. // 设置事物工厂,这里默认是 SpringManagedTransactionFactory,所以可以不用设置
  47. // SpringManagedTransactionFactory 是一个工具类,在事务开启关闭的时候会将 dataSource 传递进去,
  48. // 所以这里可以直接 new
  49. sqlSessionFactoryBean.setTransactionFactory(new SpringManagedTransactionFactory());
  50. // 这里需要一个 Resource 可变数组,如何写?
  51. /* 其实这个可以通过查看他的自动配置源码是如何写的
  52. mybatis:
  53. mapper-locations: /mapper/*.xml
  54. */
  55. ResourcePatternResolver resourceResolver = new PathMatchingResourcePatternResolver();
  56. List<Resource> resources = new ArrayList<>();
  57. resources.addAll(Arrays.asList(resourceResolver.getResources("/mapper/original/*.xml")));
  58. resources.addAll(Arrays.asList(resourceResolver.getResources("/mapper/ext/*.xml")));
  59. sqlSessionFactoryBean.setMapperLocations(resources.toArray(new Resource[resources.size()]));
  60. return sqlSessionFactoryBean;
  61. }
  62. }

至于另一个配置也和上面的类似,不过要改变的是 bean 的名称、mapper 相关配置

  1. package cn.mrcode.autoconfig.mybatis.mls;
  2. import com.alibaba.druid.spring.boot.autoconfigure.DruidDataSourceBuilder;
  3. import org.mybatis.spring.SqlSessionFactoryBean;
  4. import org.springframework.beans.factory.annotation.Qualifier;
  5. import org.springframework.boot.context.properties.ConfigurationProperties;
  6. import org.springframework.context.annotation.Bean;
  7. import org.springframework.context.annotation.Configuration;
  8. import org.springframework.core.io.support.PathMatchingResourcePatternResolver;
  9. import org.springframework.core.io.support.ResourcePatternResolver;
  10. import tk.mybatis.spring.annotation.MapperScan;
  11. import javax.sql.DataSource;
  12. import java.io.IOException;
  13. @Configuration
  14. @MapperScan(
  15. value = {
  16. "cn.mrcode.repo.mapper.db02"
  17. },
  18. sqlSessionFactoryRef = "db02SqlSessionFactoryBean")
  19. public class Db02MyBatisConfigurer {
  20. @Bean("db02DataSource")
  21. @ConfigurationProperties(prefix = "spring.datasource.db02")
  22. public DataSource dataSource() {
  23. return DruidDataSourceBuilder.create().build();
  24. }
  25. /**
  26. * 配置 mybatis
  27. */
  28. @Bean("db02SqlSessionFactoryBean")
  29. public SqlSessionFactoryBean sqlSessionFactoryBean(@Qualifier("db02DataSource") DataSource dataSource) throws IOException {
  30. SqlSessionFactoryBean sqlSessionFactoryBean = new SqlSessionFactoryBean();
  31. sqlSessionFactoryBean.setDataSource(dataSource);
  32. ResourcePatternResolver resourceResolver = new PathMatchingResourcePatternResolver();
  33. sqlSessionFactoryBean.setMapperLocations(resourceResolver.getResources("/mapper/db02/**/*.xml"));
  34. return sqlSessionFactoryBean;
  35. }
  36. }

:::danger 因为这里的 mapper 和 dataSource 绑定了,所以这里扫描的 mapper 的时候有一个前提:需要将不同数据源的 mapper 分离开。 ::: :::warning 在运行之前,还需要注意一个地方:在单数据源的时候,我们一般会在启动类里面写
@MapperScan(“xxx.mapper”)
改成上面的方案之后,需要将启动类上的取消掉,否则会导致多一次的初始化 :::

启动测试

启动成功后可以访问 druid 的 web 页面:http://localhost:8080/druid/
image.png
然后可以在数据源里面看到 2 个数据源出现(数据源是延迟初始化,首次访问数据库的时候才会真正初始化,所以这里如果没有显示其他的数据源,需要排除下是否访问了该数据源)

事务测试

简单的方案,分别在两个数据源的的其中某张表中插入数据,然后抛出异常,比如下面这样

  1. package cn.mrcodeservice;
  2. import cn.mrcoderepo.entity.mls.MlsSm;
  3. import cn.mrcoderepo.entity.original.Account;
  4. import cn.mrcoderepo.mapper.mls.original.MlsSmMapper;
  5. import cn.mrcoderepo.mapper.original.AccountMapper;
  6. import org.springframework.beans.factory.annotation.Autowired;
  7. import org.springframework.stereotype.Service;
  8. import org.springframework.transaction.annotation.Transactional;
  9. import javax.annotation.Resource;
  10. /**
  11. * 事物测试
  12. */
  13. @Service
  14. public class TransactionServiceTest {
  15. @Resource
  16. private AccountMapper accountMapper;
  17. @Resource
  18. private MlsSmMapper mlsSmMapper;
  19. @Transactional()
  20. public void db1(boolean error) {
  21. Account record = new Account();
  22. record.setName("test-123456");
  23. record.setPhone("test-123456");
  24. record.setPassword("123456");
  25. record.setIsEnable(false);
  26. record.setRoleType((byte) 1);
  27. record.setFirmType((byte) 1);
  28. record.setEmail("test-123456@qq.com");
  29. accountMapper.insertSelective(record);
  30. if (error) {
  31. throw new RuntimeException("测试事务是否回滚");
  32. }
  33. }
  34. @Transactional("mlsDataSourceTransactionManager")
  35. public void db2(boolean error) {
  36. MlsSm record = new MlsSm();
  37. record.setTitle("test-123456");
  38. mlsSmMapper.insertSelective(record);
  39. if (error) {
  40. throw new RuntimeException("测试事务是否回滚");
  41. }
  42. }
  43. }
  1. package cn.mrcodeservice;
  2. import cn.mrcoderepo.entity.mls.MlsSm;
  3. import cn.mrcoderepo.entity.original.Account;
  4. import cn.mrcoderepo.mapper.mls.original.MlsSmMapper;
  5. import cn.mrcoderepo.mapper.original.AccountMapper;
  6. import org.springframework.beans.factory.annotation.Autowired;
  7. import org.springframework.stereotype.Service;
  8. import org.springframework.transaction.annotation.Transactional;
  9. import javax.annotation.Resource;
  10. /**
  11. * 事物测试
  12. */
  13. @Service
  14. public class TransactionServiceTest {
  15. @Resource
  16. private AccountMapper accountMapper;
  17. @Resource
  18. private MlsSmMapper mlsSmMapper;
  19. @Transactional()
  20. public void db1(boolean error) {
  21. Account record = new Account();
  22. record.setName("test-123456");
  23. record.setPhone("test-123456");
  24. record.setPassword("123456");
  25. record.setIsEnable(false);
  26. record.setRoleType((byte) 1);
  27. record.setFirmType((byte) 1);
  28. record.setEmail("test-123456@qq.com");
  29. accountMapper.insertSelective(record);
  30. if (error) {
  31. throw new RuntimeException("测试事务是否回滚");
  32. }
  33. }
  34. @Transactional()
  35. public void db2(boolean error) {
  36. MlsSm record = new MlsSm();
  37. record.setTitle("test-123456");
  38. mlsSmMapper.insertSelective(record);
  39. if (error) {
  40. throw new RuntimeException("测试事务是否回滚");
  41. }
  42. }
  43. }

这里测试下来,db1 的是否是可以回滚的,但是 db2 的事务是无法回滚的,原因是:

  1. 我们在设置 db1 的时候使用了, @Primary注解
  2. spring 默认生成了一个事务管理器,在这里会注入一个数据源 ```java package org.springframework.boot.autoconfigure.jdbc;

import javax.sql.DataSource;

import org.springframework.beans.factory.ObjectProvider; import org.springframework.boot.autoconfigure.AutoConfigureOrder; import org.springframework.boot.autoconfigure.EnableAutoConfiguration; import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; import org.springframework.boot.autoconfigure.condition.ConditionalOnSingleCandidate; import org.springframework.boot.autoconfigure.transaction.TransactionManagerCustomizers; import org.springframework.boot.context.properties.EnableConfigurationProperties; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.core.Ordered; import org.springframework.core.env.Environment; import org.springframework.jdbc.core.JdbcTemplate; import org.springframework.jdbc.datasource.DataSourceTransactionManager; import org.springframework.jdbc.support.JdbcTransactionManager; import org.springframework.transaction.TransactionManager;

/**

  • {@link EnableAutoConfiguration Auto-configuration} for {@link JdbcTransactionManager}. *
  • @author Dave Syer
  • @author Stephane Nicoll
  • @author Andy Wilkinson
  • @author Kazuki Shimizu
  • @since 1.0.0 */ @Configuration(proxyBeanMethods = false) @ConditionalOnClass({ JdbcTemplate.class, TransactionManager.class }) @AutoConfigureOrder(Ordered.LOWEST_PRECEDENCE) @EnableConfigurationProperties(DataSourceProperties.class) public class DataSourceTransactionManagerAutoConfiguration {

    @Configuration(proxyBeanMethods = false) @ConditionalOnSingleCandidate(DataSource.class) static class JdbcTransactionManagerConfiguration {

    1. @Bean
    2. @ConditionalOnMissingBean(TransactionManager.class)
    3. DataSourceTransactionManager transactionManager(Environment environment, DataSource dataSource,
    4. ObjectProvider<TransactionManagerCustomizers> transactionManagerCustomizers) {
    5. DataSourceTransactionManager transactionManager = createTransactionManager(environment, dataSource);
    6. transactionManagerCustomizers.ifAvailable((customizers) -> customizers.customize(transactionManager));
    7. return transactionManager;
    8. }
    9. private DataSourceTransactionManager createTransactionManager(Environment environment, DataSource dataSource) {
    10. return environment.getProperty("spring.dao.exceptiontranslation.enabled", Boolean.class, Boolean.TRUE)
    11. ? new JdbcTransactionManager(dataSource) : new DataSourceTransactionManager(dataSource);
    12. }

    }

}

  1. ` @Transactional`注解是需要指定一个事务管理器的,如果没有指定就使用默认的,所以 db1 生效了,db2 没有生效。<br />那么我们需要为每个 db 配置事物管理器,如下
  2. <a name="lAvKt"></a>
  3. ## 事物管理器配置
  4. ```java
  5. public class PrimaryMyBatisConfigurer {
  6. ...
  7. @Bean("primaryDataSourceTransactionManager")
  8. @Primary // 设置默认的,在使用 @Transactional 注解时,不指定就会使用这里的
  9. public DataSourceTransactionManager transactionManager(@Qualifier("primaryDataSource") DataSource dataSource) {
  10. return new DataSourceTransactionManager(dataSource);
  11. }
  12. }
  13. public class Db02MyBatisConfigurer {
  14. ...
  15. @Bean("db02DataSourceTransactionManager")
  16. public DataSourceTransactionManager transactionManager(@Qualifier("db02DataSource") DataSource dataSource) {
  17. return new DataSourceTransactionManager(dataSource);
  18. }
  19. }

但是在插入的时候使用 @Transactional 就需要指定事务管理器了

  1. package cn.mrcodeservice;
  2. import cn.mrcoderepo.entity.mls.MlsSm;
  3. import cn.mrcoderepo.entity.original.Account;
  4. import cn.mrcoderepo.mapper.mls.original.MlsSmMapper;
  5. import cn.mrcoderepo.mapper.original.AccountMapper;
  6. import org.springframework.beans.factory.annotation.Autowired;
  7. import org.springframework.stereotype.Service;
  8. import org.springframework.transaction.annotation.Transactional;
  9. import javax.annotation.Resource;
  10. /**
  11. * 事物测试
  12. */
  13. @Service
  14. public class TransactionServiceTest {
  15. @Resource
  16. private AccountMapper accountMapper;
  17. @Resource
  18. private MlsSmMapper mlsSmMapper;
  19. @Transactional() // 该方法会使用默认的 db01 的事务管理器
  20. public void db1(boolean error) {
  21. Account record = new Account();
  22. record.setName("test-123456");
  23. record.setPhone("test-123456");
  24. record.setPassword("123456");
  25. record.setIsEnable(false);
  26. record.setRoleType((byte) 1);
  27. record.setFirmType((byte) 1);
  28. record.setEmail("test-123456@qq.com");
  29. accountMapper.insertSelective(record);
  30. if (error) {
  31. throw new RuntimeException("测试事务是否回滚");
  32. }
  33. }
  34. @Transactional("db02DataSourceTransactionManager") // 该方法会使用 db02 的事务管理器
  35. public void db2(boolean error) {
  36. MlsSm record = new MlsSm();
  37. record.setTitle("test-123456");
  38. mlsSmMapper.insertSelective(record);
  39. if (error) {
  40. throw new RuntimeException("测试事务是否回滚");
  41. }
  42. }
  43. }

这样设置之后 db2 的测试方法报错后,事务就能回滚了