上一篇文章《SpringBoot实现多数据源(五)【多数据源事务控制】》
官方文档:https://www.kancloud.cn/tracy5546/dynamic-datasource/2264611
基于 SpringBoot 的多数据源组件,功能强悍,支持 Seata 分布式事务
约定
DynamicDataSource 原理
public Object invoke(MethodInvocation invocation) throws Throwable {
// 获取当前@DS注解的value值
String dsKey = this.determineDatasourceKey(invocation);
// 设置当前数据源的标识ThreadLocal中
DynamicDataSourceContextHolder.push(dsKey);
Object var3;
try {
// 执行目标方法
var3 = invocation.proceed();
} finally {
DynamicDataSourceContextHolder.poll();
}
return var3;
}
// AbstractRoutingDataSource 抽象类方法
protected abstract DataSource determineDataSource();
public Connection getConnection() throws SQLException {
String xid = TransactionContext.getXID();
if (StringUtils.isEmpty(xid)) {
return this.determineDataSource().getConnection();
} else {
String ds = DynamicDataSourceContextHolder.peek();
ds = StringUtils.isEmpty(ds) ? "default" : ds;
ConnectionProxy connection = ConnectionFactory.getConnection(ds);
return (Connection)(connection == null ? this.getConnectionProxy(ds, this.determineDataSource().getConnection()) : connection);
}
}
// DynamicRoutingDataSource 类中的方法
public DataSource determineDataSource() {
// 拿到切换的数据源标识
String dsKey = DynamicDataSourceContextHolder.peek();
// 通过该表示获取对应的数据源
return this.getDataSource(dsKey);
}
用例测试
<dependencies>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-jdbcartifactId>
dependency>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-webartifactId>
dependency>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-aopartifactId>
dependency>
<dependency>
<groupId>org.mybatis.spring.bootgroupId>
<artifactId>mybatis-spring-boot-starterartifactId>
<version>2.1.4version>
dependency>
<dependency>
<groupId>com.alibabagroupId>
<artifactId>druid-spring-boot-starterartifactId>
<version>1.2.8version>
dependency>
<dependency>
<groupId>mysqlgroupId>
<artifactId>mysql-connector-javaartifactId>
<version>8.0.28version>
dependency>
<dependency>
<groupId>org.projectlombokgroupId>
<artifactId>lombokartifactId>
<version>1.18.24version>
dependency>
<dependency>
<groupId>com.baomidougroupId>
<artifactId>dynamic-datasource-spring-boot-starterartifactId>
<version>3.5.0version>
dependency>
dependencies>
spring:
autoconfigure:
# 排除 Druid 自动配置
exclude: com.alibaba.druid.spring.boot.autoconfigure.DruidDataSourceAutoConfigure
datasource:
dynamic:
# 设置默认的数据源或者数据源组,默认值即为master
primary: master
# 严格匹配数据源,默认false. true未匹配到指定数据源时抛异常,false使用默认数据源
strict: false
datasource:
master:
# 3.2.0开始支持SPI可省略此配置
driver-class-name: com.mysql.cj.jdbc.Driver
url: jdbc:mysql://localhost:3306/write?useSSL=true&useUnicode=true&characterEncoding=utf-8&serverTimezone=Asia/Shanghai
username: root
password: 123456
type: com.alibaba.druid.pool.DruidDataSource
slave:
driver-class-name: com.mysql.cj.jdbc.Driver
url: jdbc:mysql://localhost:3306/read?useSSL=true&useUnicode=true&characterEncoding=utf-8&serverTimezone=Asia/Shanghai
username: root
password: 123456
type: com.alibaba.druid.pool.DruidDataSource
# 指定使用 druid 数据源
druid:
# 连接池初始化大小
initial-size: 5
# 最小空闲连接数
min-idle: 10
# 最大连接数
max-active: 20
# 配置获取连接等待超时的时间
maxWait: 60000
# 配置间隔多久才进行一次检测,检测需要关闭的空闲连接,单位是毫秒
timeBetweenEvictionRunsMillis: 60000
# 配置一个连接在池中最小生存的时间,单位是毫秒
minEvictableIdleTimeMillis: 300000
# 配置一个连接在池中最大生存的时间,单位是毫秒
maxEvictableIdleTimeMillis: 900000
# 配置检测连接是否有效
validationQuery: SELECT 1 FROM DUAL
#......省略
#以上会配置一个默认库master,一个组slave下有两个子库slave_1,slave_2
mybatis:
mapper-locations: classpath:com/vinjcent/mapper/**/*.xml
type-aliases-package: com.vinjcent.pojo
package com.vinjcent.pojo;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
@AllArgsConstructor
@NoArgsConstructor
@Data
public class People {
private String name;
}
package com.vinjcent.mapper;
import com.vinjcent.pojo.People;
import org.apache.ibatis.annotations.Mapper;
import java.util.List;
@Mapper
public interface PeopleMapper {
List<People> list();
boolean save(People people);
}
package com.vinjcent.service.impl;
import com.baomidou.dynamic.datasource.annotation.DS;
import com.baomidou.dynamic.datasource.annotation.DSTransactional;
import com.vinjcent.mapper.PeopleMapper;
import com.vinjcent.pojo.People;
import com.vinjcent.service.PeopleService;
import org.springframework.aop.framework.AopContext;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import java.util.List;
@Service
public class PeopleServiceImpl implements PeopleService {
private final PeopleMapper peopleMapper;
@Autowired
public PeopleServiceImpl(PeopleMapper peopleMapper) {
this.peopleMapper = peopleMapper;
}
/**
* 说明: 不能和原生 Spring 事务混合,不使用 @DSTransactional 注解无法开启事务,即事务不会生效
*/
// 从库,如果按照下划线命名方式配置多个,可以指定前缀即可.如slave_1、slave_2、slave3...,只需要设置salve即可,默认使用负载均衡算法
@DS("slave")
@Override
public List<People> list() {
return peopleMapper.list();
}
@DS("master")
@Override
public boolean mSave(People people) {
return peopleMapper.save(people);
}
@DS("slave")
@Override
public boolean sSave(People people) {
boolean save = peopleMapper.save(people);
return save;
}
@DSTransactional
public boolean save (People people) {
PeopleService peopleService = (PeopleService) AopContext.currentProxy();
peopleService.sSave(people);
peopleService.mSave(people);
// 模拟事务回滚
int a = 1 / 0;
return true;
}
}
package com.vinjcent;
import org.mybatis.spring.annotation.MapperScan;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.annotation.EnableAspectJAutoProxy;
@EnableAspectJAutoProxy(exposeProxy = true)
@MapperScan("com.vinjcent.mapper")
@SpringBootApplication
public class DynamicDatasourceFrameworkApplication {
public static void main(String[] args) {
SpringApplication.run(DynamicDatasourceFrameworkApplication.class, args);
}
}
package com.vinjcent.controller;
import com.vinjcent.pojo.People;
import com.vinjcent.service.PeopleService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import java.util.List;
@RestController
@RequestMapping("people")
public class PeopleController {
private final PeopleService peopleService;
@Autowired
public PeopleController(PeopleService peopleService) {
this.peopleService = peopleService;
}
@GetMapping("/list")
public List<People> getAllPeople() {
return peopleService.list();
}
@GetMapping("/insert")
public String addPeople() {
peopleService.save(new People("vinjcent"));
return "添加成功";
}
}