上一篇文章《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);
}
用例测试
org.springframework.boot spring-boot-starter-jdbc org.springframework.boot spring-boot-starter-web org.springframework.boot spring-boot-starter-aop org.mybatis.spring.boot mybatis-spring-boot-starter 2.1.4 com.alibaba druid-spring-boot-starter 1.2.8 mysql mysql-connector-java 8.0.28 org.projectlombok lombok 1.18.24 com.baomidou dynamic-datasource-spring-boot-starter 3.5.0
spring:autoconfigure:# 排除 Druid 自动配置exclude: com.alibaba.druid.spring.boot.autoconfigure.DruidDataSourceAutoConfiguredatasource:dynamic:# 设置默认的数据源或者数据源组,默认值即为masterprimary: master# 严格匹配数据源,默认false. true未匹配到指定数据源时抛异常,false使用默认数据源strict: falsedatasource:master:# 3.2.0开始支持SPI可省略此配置driver-class-name: com.mysql.cj.jdbc.Driverurl: jdbc:mysql://localhost:3306/write?useSSL=true&useUnicode=true&characterEncoding=utf-8&serverTimezone=Asia/Shanghaiusername: rootpassword: 123456type: com.alibaba.druid.pool.DruidDataSourceslave:driver-class-name: com.mysql.cj.jdbc.Driverurl: jdbc:mysql://localhost:3306/read?useSSL=true&useUnicode=true&characterEncoding=utf-8&serverTimezone=Asia/Shanghaiusername: rootpassword: 123456type: 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_2mybatis:mapper-locations: classpath:com/vinjcent/mapper/**/*.xmltype-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 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;@Autowiredpublic PeopleServiceImpl(PeopleMapper peopleMapper) {this.peopleMapper = peopleMapper;}/*** 说明: 不能和原生 Spring 事务混合,不使用 @DSTransactional 注解无法开启事务,即事务不会生效*/// 从库,如果按照下划线命名方式配置多个,可以指定前缀即可.如slave_1、slave_2、slave3...,只需要设置salve即可,默认使用负载均衡算法@DS("slave")@Overridepublic List list() {return peopleMapper.list();}@DS("master")@Overridepublic boolean mSave(People people) {return peopleMapper.save(people);}@DS("slave")@Overridepublic boolean sSave(People people) {boolean save = peopleMapper.save(people);return save;}@DSTransactionalpublic 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;@Autowiredpublic PeopleController(PeopleService peopleService) {this.peopleService = peopleService;}@GetMapping("/list")public List getAllPeople() {return peopleService.list();}@GetMapping("/insert")public String addPeople() {peopleService.save(new People("vinjcent"));return "添加成功";}}
下一篇:查询:按A分组,满足B时对应的C