Spring boot 版本:2.4.x 扩展资料:
:::warning
说明:
本页的自定义多数据源的背景是:能明确的是哪些 mapper 使用哪些数据源,在配置数据源的时候直接绑定死的,并不是动态数据源的方案,动态数据源的方案只需要配置一次 SqlSessionFactory,因为数据源是动态切换的。我们这里不是动态数据源方案,是静态配置。
静态和动态举例:
- 静态:非常明确哪些 Mappser 使用某个数据源,假设业务 A 需要使用数据源 A,业务 B 需要使用数据源 B
- 动态:同一套 mapper,可以使用多个数据源,换句话说,多个数据源上的表结构是完全一样的;动态的核心是可以切换数据源,所以也可以使用动态的方式来实现静态的功能。
本人的业务需求会包含这 2 种方式。本文实现静态方式,下一篇实现动态方式 :::
MyBatis 基础知识
编程配置 MyBatis 的核心是构建 SqlSessionFactory,需要提供的有:数据源、Mapper.class 和 Mapper.xml 配置、事物工厂配置
DataSource dataSource = BlogDataSourceFactory.getBlogDataSource();
TransactionFactory transactionFactory = new JdbcTransactionFactory();
Environment environment = new Environment("development", transactionFactory, dataSource);
Configuration configuration = new Configuration(environment);
configuration.addMapper(BlogMapper.class);
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(configuration);
但是在于 Spring 整合的时候,其中 Mapper.xml 和数据源 是需要通过代码完成配置的,而 mapper.class 则会使用 @MapperScan
完成配置
多数据源配置的原理
spring:
datasource:
type: com.alibaba.druid.pool.DruidDataSource
driverClassName: com.mysql.cj.jdbc.Driver
url: jdbc:mysql://127.0.0.1:3307/dpa-be?serverTimezone=Asia/Shanghai&useUnicode=true&characterEncoding=utf-8&useSSL=true
username: root
password: root
druid:
# 数据源名称
name: db1
# 连接池的配置信息
# 初始化大小,最小,最大
initialSize: 5
minIdle: 5
maxActive: 20
# 配置获取连接等待超时的时间
maxWait: 60000
# 配置间隔多久才进行一次检测,检测需要关闭的空闲连接,单位是毫秒
timeBetweenEvictionRunsMillis: 60000
# 配置一个连接在池中最小生存的时间,单位是毫秒
minEvictableIdleTimeMillis: 300000
validationQuery: SELECT 1 FROM DUAL
testWhileIdle: true
testOnBorrow: false
testOnReturn: false
# 打开PSCache,并且指定每个连接上PSCache的大小
poolPreparedStatements: true
maxPoolPreparedStatementPerConnectionSize: 20
# 配置监控统计拦截的filters,去掉后监控界面sql无法统计,'wall'用于防火墙
filters: stat,wall,slf4j
# 通过connectProperties属性来打开mergeSql功能;慢SQL记录
connectionProperties: druid.stat.mergeSql=true;druid.stat.slowSqlMillis=5000
# 配置 DruidStatFilter
web-stat-filter:
enabled: true
url-pattern: "/*"
exclusions: "*.js,*.gif,*.jpg,*.bmp,*.png,*.css,*.ico,/druid/*"
# 配置DruidStatViewServlet
stat-view-servlet:
enabled: true
url-pattern: "/druid/*"
# IP白名单(没有配置或者为空,则允许所有访问)
# allow: 127.0.0.1,192.168.163.1
allow: ""
# IP黑名单 (存在共同时,deny优先于allow)
# deny: 192.168.1.73
# 禁用HTML页面上的“Reset All”功能
reset-enable: false
# 登录名 和 密码
login-username: admin
login-password: 123456
上面的 yml 配置是 org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration
中支持的通用方式,但是从 druid 开始,这是每个数据源自动配置实现的,而不是 DataSourceAutoConfiguration
实现的,这一点一定要明白。
我们可以在 Druid 的官方文档找到多数据源的配置,如果你看仔细看看 DruidDataSourceAutoConfigure 的自动配置源码的话,会搞明白一些事情,下面源码中的注释会说明
package com.alibaba.druid.spring.boot.autoconfigure;
import javax.sql.DataSource;
import com.alibaba.druid.pool.DruidDataSource;
import com.alibaba.druid.spring.boot.autoconfigure.properties.DruidStatProperties;
import com.alibaba.druid.spring.boot.autoconfigure.stat.DruidFilterConfiguration;
import com.alibaba.druid.spring.boot.autoconfigure.stat.DruidSpringAopConfiguration;
import com.alibaba.druid.spring.boot.autoconfigure.stat.DruidStatViewServletConfiguration;
import com.alibaba.druid.spring.boot.autoconfigure.stat.DruidWebStatFilterConfiguration;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.boot.autoconfigure.AutoConfigureBefore;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration;
import org.springframework.boot.autoconfigure.jdbc.DataSourceProperties;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Import;
/**
* @author lihengming [89921218@qq.com]
*/
@Configuration
// 如果没有提供 DruidDataSource 实现类则该自动配置生效,但是我们一般返回的都是 DataSource 类,而不是 DruidDataSource
// 所以该类的部分配置会被加载
@ConditionalOnClass(DruidDataSource.class)
// 设置在 boot 自动配置前执行该配置类
@AutoConfigureBefore(DataSourceAutoConfiguration.class)
// 开启配置文件
// DruidStatProperties 绑定:spring.datasource.druid,这个是针对单个应用配置的 web 监控页面,统计之类的
// DataSourceProperties 绑定:spring.datasource,这个是通用的基础,就是数据库 url、用户名、密码之类的
@EnableConfigurationProperties({DruidStatProperties.class, DataSourceProperties.class})
// 下面导入了一些配置
@Import({DruidSpringAopConfiguration.class,
// 只要配置了 spring.datasource.druid.stat-view-servlet.enabled 则该配置生效,对应的是页面上的 SQL 监控、SQL 防火墙
DruidStatViewServletConfiguration.class,
// 只要配置了 spring.datasource.druid.web-stat-filter.enabled,则该配置生效,对应的是监控页面上的 Web 应用、URI 监控等功能
DruidWebStatFilterConfiguration.class, //
DruidFilterConfiguration.class})
public class DruidDataSourceAutoConfigure {
private static final Logger LOGGER = LoggerFactory.getLogger(DruidDataSourceAutoConfigure.class);
// 这里是如果不存在 DataSource 则初始化一个 DruidDataSourceWrapper 数据源
@Bean(initMethod = "init")
@ConditionalOnMissingBean
public DataSource dataSource() {
LOGGER.info("Init DruidDataSource");
return new DruidDataSourceWrapper();
}
}
从上面的自动配置来看,我们可以得出几个信息:
- 监控页面等配置和数据源配置是分离的,一个应用只能配置一个
- 数据源可以自己初始化多个
- Druid 的自动配置会在 boot 的自动配置 DataSourceAutoConfiguration 之前运行,所以会产生 DataSource,而 boot 的自动配置就不会走 DataSource 的构建了,但是会有其他依赖 DataSource 的自动配置构建(比如默认的事物管理器)。
Spring boot 中配置多数据源
runtimeOnly 'mysql:mysql-connector-java'
implementation 'org.springframework.boot:spring-boot-starter-jdbc'
implementation 'com.alibaba:druid-spring-boot-starter:1.2.4'
implementation "tk.mybatis:mapper-spring-boot-starter:2.1.5"
implementation "tk.mybatis:mapper:4.1.5"
我这里使用了 tk mybatis,在配置上,仅仅是引入的依赖和@MapperScan
注解使用的包路径不一致外,其他的和原生的一致
先搞定 yaml 的配置文件,看看如何写是比较合理的
spring:
datasource:
druid:
# 让 druid 的自动配置生效,配置监控相关功能
# 配置 DruidStatFilter
web-stat-filter:
enabled: true
url-pattern: "/*"
exclusions: "*.js,*.gif,*.jpg,*.bmp,*.png,*.css,*.ico,/druid/*"
# 配置DruidStatViewServlet
stat-view-servlet:
enabled: true
url-pattern: "/druid/*"
# IP白名单(没有配置或者为空,则允许所有访问)
# allow: 127.0.0.1,192.168.163.1
allow: ""
# IP黑名单 (存在共同时,deny优先于allow)
# deny: 192.168.1.73
# 禁用HTML页面上的“Reset All”功能
reset-enable: false
# 登录名 和 密码
login-username: admin
login-password: 123456
# 多数据源配置
db01:
name: DB-01
url: jdbc:mysql://127.0.0.1:3307/test1?serverTimezone=Asia/Shanghai&useUnicode=true&characterEncoding=utf-8&useSSL=true
username: root
password: root
# 连接池的配置信息
# 初始化大小,最小,最大
initialSize: 5
minIdle: 5
maxActive: 20
# 配置获取连接等待超时的时间
maxWait: 60000
# 配置间隔多久才进行一次检测,检测需要关闭的空闲连接,单位是毫秒
timeBetweenEvictionRunsMillis: 60000
# 配置一个连接在池中最小生存的时间,单位是毫秒
minEvictableIdleTimeMillis: 300000
validationQuery: SELECT 1 FROM DUAL
testWhileIdle: true
testOnBorrow: false
testOnReturn: false
# 打开PSCache,并且指定每个连接上PSCache的大小
poolPreparedStatements: true
maxPoolPreparedStatementPerConnectionSize: 20
# 配置监控统计拦截的filters,去掉后监控界面sql无法统计,'wall'用于防火墙
filters: stat,wall,slf4j
# 通过connectProperties属性来打开mergeSql功能;慢SQL记录
connectionProperties: druid.stat.mergeSql=true;druid.stat.slowSqlMillis=5000
# 多数据源配置
db02:
name: DB-02
url: jdbc:mysql://127.0.0.1:3307/test2?serverTimezone=Asia/Shanghai&useUnicode=true&characterEncoding=utf-8&useSSL=true
username: root
password: root
# 连接池的配置信息
# 初始化大小,最小,最大
initialSize: 5
minIdle: 5
maxActive: 20
# 配置获取连接等待超时的时间
maxWait: 60000
# 配置间隔多久才进行一次检测,检测需要关闭的空闲连接,单位是毫秒
timeBetweenEvictionRunsMillis: 60000
# 配置一个连接在池中最小生存的时间,单位是毫秒
minEvictableIdleTimeMillis: 300000
validationQuery: SELECT 1 FROM DUAL
testWhileIdle: true
testOnBorrow: false
testOnReturn: false
# 打开PSCache,并且指定每个连接上PSCache的大小
poolPreparedStatements: true
maxPoolPreparedStatementPerConnectionSize: 20
# 配置监控统计拦截的filters,去掉后监控界面sql无法统计,'wall'用于防火墙
filters: stat,wall,slf4j
# 通过connectProperties属性来打开mergeSql功能;慢SQL记录
connectionProperties: druid.stat.mergeSql=true;druid.stat.slowSqlMillis=5000
:::warning 这里需要明白的是:
spring.datasource.druid
的配置会触发 DruidDataSourceAutoConfigure 的自动配置生效,让监控等配置进行自动配置- 至于
spring.datasource
下的其他配置项,如 db01、db02 这个配置是需要自己处理的配置项 :::
配置 数据源和 SqlSessionFactory
package cn.mrcode.autoconfig.mybatis.primary;
import com.alibaba.druid.spring.boot.autoconfigure.DruidDataSourceBuilder;
import org.mybatis.spring.SqlSessionFactoryBean;
import org.mybatis.spring.transaction.SpringManagedTransactionFactory;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Primary;
import org.springframework.core.io.Resource;
import org.springframework.core.io.support.PathMatchingResourcePatternResolver;
import org.springframework.core.io.support.ResourcePatternResolver;
import org.springframework.jdbc.datasource.DataSourceTransactionManager;
import org.springframework.transaction.jta.TransactionFactory;
import tk.mybatis.spring.annotation.MapperScan;
import javax.sql.DataSource;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
@Configuration
@MapperScan(
// 自动生成 mapper 放到了 original 下
// 自定义的 mapper 放到了 ext 下
value = {
"cn.mrcode.repo.mapper.ext",
"cn.mrcode.repo.mapper.original"
},
// 让 mapper 与 sqlSession 绑定
sqlSessionFactoryRef = "primarySqlSessionFactoryBean")
public class PrimaryMyBatisConfigurer {
@Bean("primaryDataSource")
@ConfigurationProperties(prefix = "spring.datasource.db01")
@Primary
public DataSource dataSource() {
return DruidDataSourceBuilder.create().build();
}
/**
* 配置 mybatis
*/
@Bean("primarySqlSessionFactoryBean")
@Primary
public SqlSessionFactoryBean sqlSessionFactoryBean(@Qualifier("primaryDataSource") DataSource dataSource) throws IOException {
SqlSessionFactoryBean sqlSessionFactoryBean = new SqlSessionFactoryBean();
sqlSessionFactoryBean.setDataSource(dataSource);
// 设置事物工厂,这里默认是 SpringManagedTransactionFactory,所以可以不用设置
// SpringManagedTransactionFactory 是一个工具类,在事务开启关闭的时候会将 dataSource 传递进去,
// 所以这里可以直接 new
sqlSessionFactoryBean.setTransactionFactory(new SpringManagedTransactionFactory());
// 这里需要一个 Resource 可变数组,如何写?
/* 其实这个可以通过查看他的自动配置源码是如何写的
mybatis:
mapper-locations: /mapper/*.xml
*/
ResourcePatternResolver resourceResolver = new PathMatchingResourcePatternResolver();
List<Resource> resources = new ArrayList<>();
resources.addAll(Arrays.asList(resourceResolver.getResources("/mapper/original/*.xml")));
resources.addAll(Arrays.asList(resourceResolver.getResources("/mapper/ext/*.xml")));
sqlSessionFactoryBean.setMapperLocations(resources.toArray(new Resource[resources.size()]));
return sqlSessionFactoryBean;
}
}
至于另一个配置也和上面的类似,不过要改变的是 bean 的名称、mapper 相关配置
package cn.mrcode.autoconfig.mybatis.mls;
import com.alibaba.druid.spring.boot.autoconfigure.DruidDataSourceBuilder;
import org.mybatis.spring.SqlSessionFactoryBean;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.io.support.PathMatchingResourcePatternResolver;
import org.springframework.core.io.support.ResourcePatternResolver;
import tk.mybatis.spring.annotation.MapperScan;
import javax.sql.DataSource;
import java.io.IOException;
@Configuration
@MapperScan(
value = {
"cn.mrcode.repo.mapper.db02"
},
sqlSessionFactoryRef = "db02SqlSessionFactoryBean")
public class Db02MyBatisConfigurer {
@Bean("db02DataSource")
@ConfigurationProperties(prefix = "spring.datasource.db02")
public DataSource dataSource() {
return DruidDataSourceBuilder.create().build();
}
/**
* 配置 mybatis
*/
@Bean("db02SqlSessionFactoryBean")
public SqlSessionFactoryBean sqlSessionFactoryBean(@Qualifier("db02DataSource") DataSource dataSource) throws IOException {
SqlSessionFactoryBean sqlSessionFactoryBean = new SqlSessionFactoryBean();
sqlSessionFactoryBean.setDataSource(dataSource);
ResourcePatternResolver resourceResolver = new PathMatchingResourcePatternResolver();
sqlSessionFactoryBean.setMapperLocations(resourceResolver.getResources("/mapper/db02/**/*.xml"));
return sqlSessionFactoryBean;
}
}
:::danger
因为这里的 mapper 和 dataSource 绑定了,所以这里扫描的 mapper 的时候有一个前提:需要将不同数据源的 mapper 分离开。
:::
:::warning
在运行之前,还需要注意一个地方:在单数据源的时候,我们一般会在启动类里面写
@MapperScan(“xxx.mapper”)
改成上面的方案之后,需要将启动类上的取消掉,否则会导致多一次的初始化
:::
启动测试
启动成功后可以访问 druid 的 web 页面:http://localhost:8080/druid/
然后可以在数据源里面看到 2 个数据源出现(数据源是延迟初始化,首次访问数据库的时候才会真正初始化,所以这里如果没有显示其他的数据源,需要排除下是否访问了该数据源)
事务测试
简单的方案,分别在两个数据源的的其中某张表中插入数据,然后抛出异常,比如下面这样
package cn.mrcodeservice;
import cn.mrcoderepo.entity.mls.MlsSm;
import cn.mrcoderepo.entity.original.Account;
import cn.mrcoderepo.mapper.mls.original.MlsSmMapper;
import cn.mrcoderepo.mapper.original.AccountMapper;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import javax.annotation.Resource;
/**
* 事物测试
*/
@Service
public class TransactionServiceTest {
@Resource
private AccountMapper accountMapper;
@Resource
private MlsSmMapper mlsSmMapper;
@Transactional()
public void db1(boolean error) {
Account record = new Account();
record.setName("test-123456");
record.setPhone("test-123456");
record.setPassword("123456");
record.setIsEnable(false);
record.setRoleType((byte) 1);
record.setFirmType((byte) 1);
record.setEmail("test-123456@qq.com");
accountMapper.insertSelective(record);
if (error) {
throw new RuntimeException("测试事务是否回滚");
}
}
@Transactional("mlsDataSourceTransactionManager")
public void db2(boolean error) {
MlsSm record = new MlsSm();
record.setTitle("test-123456");
mlsSmMapper.insertSelective(record);
if (error) {
throw new RuntimeException("测试事务是否回滚");
}
}
}
package cn.mrcodeservice;
import cn.mrcoderepo.entity.mls.MlsSm;
import cn.mrcoderepo.entity.original.Account;
import cn.mrcoderepo.mapper.mls.original.MlsSmMapper;
import cn.mrcoderepo.mapper.original.AccountMapper;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import javax.annotation.Resource;
/**
* 事物测试
*/
@Service
public class TransactionServiceTest {
@Resource
private AccountMapper accountMapper;
@Resource
private MlsSmMapper mlsSmMapper;
@Transactional()
public void db1(boolean error) {
Account record = new Account();
record.setName("test-123456");
record.setPhone("test-123456");
record.setPassword("123456");
record.setIsEnable(false);
record.setRoleType((byte) 1);
record.setFirmType((byte) 1);
record.setEmail("test-123456@qq.com");
accountMapper.insertSelective(record);
if (error) {
throw new RuntimeException("测试事务是否回滚");
}
}
@Transactional()
public void db2(boolean error) {
MlsSm record = new MlsSm();
record.setTitle("test-123456");
mlsSmMapper.insertSelective(record);
if (error) {
throw new RuntimeException("测试事务是否回滚");
}
}
}
这里测试下来,db1 的是否是可以回滚的,但是 db2 的事务是无法回滚的,原因是:
- 我们在设置 db1 的时候使用了,
@Primary
注解 - 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 {
@Bean
@ConditionalOnMissingBean(TransactionManager.class)
DataSourceTransactionManager transactionManager(Environment environment, DataSource dataSource,
ObjectProvider<TransactionManagerCustomizers> transactionManagerCustomizers) {
DataSourceTransactionManager transactionManager = createTransactionManager(environment, dataSource);
transactionManagerCustomizers.ifAvailable((customizers) -> customizers.customize(transactionManager));
return transactionManager;
}
private DataSourceTransactionManager createTransactionManager(Environment environment, DataSource dataSource) {
return environment.getProperty("spring.dao.exceptiontranslation.enabled", Boolean.class, Boolean.TRUE)
? new JdbcTransactionManager(dataSource) : new DataSourceTransactionManager(dataSource);
}
}
}
而 ` @Transactional`注解是需要指定一个事务管理器的,如果没有指定就使用默认的,所以 db1 生效了,db2 没有生效。<br />那么我们需要为每个 db 配置事物管理器,如下
<a name="lAvKt"></a>
## 事物管理器配置
```java
public class PrimaryMyBatisConfigurer {
...
@Bean("primaryDataSourceTransactionManager")
@Primary // 设置默认的,在使用 @Transactional 注解时,不指定就会使用这里的
public DataSourceTransactionManager transactionManager(@Qualifier("primaryDataSource") DataSource dataSource) {
return new DataSourceTransactionManager(dataSource);
}
}
public class Db02MyBatisConfigurer {
...
@Bean("db02DataSourceTransactionManager")
public DataSourceTransactionManager transactionManager(@Qualifier("db02DataSource") DataSource dataSource) {
return new DataSourceTransactionManager(dataSource);
}
}
但是在插入的时候使用 @Transactional
就需要指定事务管理器了
package cn.mrcodeservice;
import cn.mrcoderepo.entity.mls.MlsSm;
import cn.mrcoderepo.entity.original.Account;
import cn.mrcoderepo.mapper.mls.original.MlsSmMapper;
import cn.mrcoderepo.mapper.original.AccountMapper;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import javax.annotation.Resource;
/**
* 事物测试
*/
@Service
public class TransactionServiceTest {
@Resource
private AccountMapper accountMapper;
@Resource
private MlsSmMapper mlsSmMapper;
@Transactional() // 该方法会使用默认的 db01 的事务管理器
public void db1(boolean error) {
Account record = new Account();
record.setName("test-123456");
record.setPhone("test-123456");
record.setPassword("123456");
record.setIsEnable(false);
record.setRoleType((byte) 1);
record.setFirmType((byte) 1);
record.setEmail("test-123456@qq.com");
accountMapper.insertSelective(record);
if (error) {
throw new RuntimeException("测试事务是否回滚");
}
}
@Transactional("db02DataSourceTransactionManager") // 该方法会使用 db02 的事务管理器
public void db2(boolean error) {
MlsSm record = new MlsSm();
record.setTitle("test-123456");
mlsSmMapper.insertSelective(record);
if (error) {
throw new RuntimeException("测试事务是否回滚");
}
}
}
这样设置之后 db2 的测试方法报错后,事务就能回滚了