SpringBoot实现多数据源(六)【dynamic-datasource 多数据源组件】
创始人
2024-02-20 18:39:41
0

上一篇文章《SpringBoot实现多数据源(五)【多数据源事务控制】》

六、dynamic-datasource 多数据源组件


官方文档:https://www.kancloud.cn/tracy5546/dynamic-datasource/2264611

基于 SpringBoot 的多数据源组件,功能强悍,支持 Seata 分布式事务

  • 支持 数据源分组,适用于多种场景,纯粹多库、读写分离、一主多从混合模式
  • 支持数据源敏感配置信息加密 ENC()
  • 支持每个数据库独立初始化表结构 scheme 和数据库 database
  • 支持无数据源启动,支持懒加载数据源(需要的时候再创建连接)
  • 支持 自定义注解,需继承 DS(3.2.0+)
  • 提供并简化 Druid,HikariCp、BeeCp、Dbcp2的快速集成
  • 提供对 Mybatis-Plus、Quartz、ShardingJdbc、P6sy、Jndi等组件的集成方案
  • 提供 自定义数据源来源 方案(如全从数据库加载)
  • 提供项目启动后 动态增加移除数据源 方案
  • 提供 Mybatis 环境下的 纯读写分离 方案
  • 提供使用 spel 动态参数解析数据源方案,内置 spel,session、header,支持自定义
  • 支持 多层数据源嵌套切换(ServiceA >>> ServiceB >>> ServiceC)
  • 提供基于 Seata 的分布式事务方案
  • 提供本地数据源事务方案(附:不能和原生 Spring 事务混合)

约定

  1. 本框架只做 切换数据源 这件核心的事情,并不限制你的具体操作,切换了数据源可以做任何CRUD
  2. 配置文件所有以"_"下划线分割的数据源 首部 即为组的名称,相同组名称的数据源会放在一个组下
  3. 切换数据源可以是组名,也可以是具体数据源名称。组名则切换时采用负载均衡算法切换,默认使用轮询
  4. 默认的数据源名称为 master,你可以通过 spring.datasource.dynamic.primary 修改
  5. 方法上的注解优先于类上的注解
  6. DS 支持继承抽象类上的 DS,暂不支持继承接口上的 DS

DynamicDataSource 原理

  1. 通过 DynamicDataSourceAutoConfiguration 自动配置类
  2. 配置了 DynamicRoutingDataSource,相当于前面定义的 DynamicDataSource,用来动态提供数据源
  3. 配置了 DynamicDataSourceAnnotationAdvisor 就相当于前面定义的切面类
  4. 设置了 DynamicDataSourceAnnotationInterceptor,当前 advisor 的拦截器,可以理解为前面定义的环绕通知
  5. 当执行方法,会执行 DynamicDataSourceAnnotationInterceptor#invoke 来进行增强
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;
}
  1. 在执行数据库操作的时候,就会调用DataSource.getConnection(),此时DataSource指的就是 DynamicRoutingDataSource
  2. 然后执行模板抽象方法 AbstractRoutingDataSource#determineDataSource,被 DynamicRoutingDataSource 重写后调用
// 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);
}

用例测试

  1. 创建一个 dynamic_datasource_framework 的 SpringBoot 模块,并导入依赖
    • pom.xml
org.springframework.bootspring-boot-starter-jdbcorg.springframework.bootspring-boot-starter-weborg.springframework.bootspring-boot-starter-aoporg.mybatis.spring.bootmybatis-spring-boot-starter2.1.4com.alibabadruid-spring-boot-starter1.2.8mysqlmysql-connector-java8.0.28org.projectlomboklombok1.18.24com.baomidoudynamic-datasource-spring-boot-starter3.5.0

  1. 应用配置文件
    • application.yml
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
  1. 实体类、Mapper层(Dao层)、Service层
  • People
package com.vinjcent.pojo;import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;@AllArgsConstructor
@NoArgsConstructor
@Data
public class People {private String name;}
  • PeopleMapper
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);}
  • PeopleServiceImpl(看方法写Service接口)
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;}}
  1. 主启动类,扫描mapper接口、暴露代理对象
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);}}
  1. 运行并测试接口
    • PeopleController
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 "添加成功";}}

相关内容

热门资讯

监控摄像头接入GB28181平... 流程简介将监控摄像头的视频在网站和APP中直播,要解决的几个问题是:1&...
Windows10添加群晖磁盘... 在使用群晖NAS时,我们需要通过本地映射的方式把NAS映射成本地的一块磁盘使用。 通过...
protocol buffer... 目录 目录 什么是protocol buffer 1.protobuf 1.1安装  1.2使用...
Fluent中创建监测点 1 概述某些仿真问题,需要创建监测点,用于获取空间定点的数据࿰...
educoder数据结构与算法...                                                   ...
MySQL下载和安装(Wind... 前言:刚换了一台电脑,里面所有东西都需要重新配置,习惯了所...
MFC文件操作  MFC提供了一个文件操作的基类CFile,这个类提供了一个没有缓存的二进制格式的磁盘...
在Word、WPS中插入AxM... 引言 我最近需要写一些文章,在排版时发现AxMath插入的公式竟然会导致行间距异常&#...
有效的括号 一、题目 给定一个只包括 '(',')','{','}'...
【Ctfer训练计划】——(三... 作者名:Demo不是emo  主页面链接:主页传送门 创作初心ÿ...