readme
2024/6/13...大约 5 分钟
SpringBoot + Mybatis 实现多数据源
AbstractRoutingDataSource方式切换
简易版本,晓得原理后再使用框架更的心应手。
切换原理
Spring 提供了一个 AbstractRoutingDataSource
抽象类,应用直接操作 AbstractRoutingDataSource
的实现类,告诉 AbstractRoutingDataSource
访问哪个数据库,然后由 AbstractRoutingDataSource
从事先配置好的数据源选择后,访问对应的数据库。
使用方法:
- 继承
AbstractRoutingDataSource
- 初始化所有数据源
- 通过模版方法返回当前数据源标识
多数据源切换方式
多数据源切换方式有多种,常见有 AOP 、 Mybatis 两种方式。使用场景不一样, AOP 一般用于不同业务的数据源切换; Mybatis 一般结合插件实现读写分离动态切换数据源。
AOP 自定义注解方式
创建一个带值的注解类
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
@Target({ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
public @interface RW {
String value() default "W";
}
创建一个切换数据源的切面类
引入切面依赖
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-aop</artifactId>
</dependency>
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.springframework.stereotype.Component;
@Component
@Aspect
public class DynamicDataSourceAspect {
/**
* 切面表达式:
* execution 代表所要执行的表达式主体
* 第一处 * 代表方法返回类型 *代表所有类型
* 第二处 包名代表aop监控的类所在的包
* 第三处 .. 代表该包以及其子包下的所有类方法
* 第四处 * 代表类名,*代表所有类
* 第五处 *(..) *代表类中的方法名,(..)表示方法中的任何参数
*
* @param point 切点
* @param rw 注解
*/
@Before("execution(* learn.note.changesource..*.*(..)) && @annotation(rw)")
public void before(JoinPoint point, RW rw) {
String name = rw.value();
}
}
Mybatis 插件方式
import org.apache.ibatis.executor.Executor;
import org.apache.ibatis.mapping.MappedStatement;
import org.apache.ibatis.mapping.SqlCommandType;
import org.apache.ibatis.plugin.*;
import org.apache.ibatis.session.ResultHandler;
import org.apache.ibatis.session.RowBounds;
@Intercepts({
// 为 Exception 对象的 update 方法进行插件代理
@Signature(type = Executor.class, method = "update", args = {MappedStatement.class, Object.class}),
@Signature(type = Executor.class, method = "query", args = {MappedStatement.class, Object.class, RowBounds.class, ResultHandler.class})
})
public class DynamicDataSourcePlugin implements Interceptor {
@Override
public Object intercept(Invocation invocation) throws Throwable {
// 拿到执行方法的所有参数
Object[] objects = invocation.getArgs();
// MappedStatement 封装了属性和 SQL ,
MappedStatement ms = (MappedStatement)objects[0];
// 拿到当前 SQL 的执行类型
if (ms.getSqlCommandType().equals(SqlCommandType.SELECT)) {
// 设置读
} else {
// 设置写
}
return invocation.proceed();
}
@Override
public Object plugin(Object target) {
if (target instanceof Executor) {
return Plugin.wrap(target, this);
} else {
return target;
}
}
}
编写完成需要将本类作为一个 Bean 提供出去,Mybatis 加载时会自动将插件引入。
import org.apache.ibatis.plugin.Interceptor;
import org.springframework.beans.factory.annotation.Configurable;
import org.springframework.context.annotation.Bean;
@Configurable
public class MybatisPluginConfig {
@Bean
public Interceptor dynamicDataSourcePlugin() {
return new DynamicDataSourcePlugin();
}
}
集成多个 Mybatis 框架
使用 @MapperScan
中的 sqlSessionFactoryRef
属性使用不同的 sqlSession
启动占用资源多
开源框架 dynamic-datasource 多数据源组件
基于 Springboot 的多数据源组件,功能强悍,支持 Seata 分布式事务。
- 支持数据源分组,适用于多种场景:纯粹多库、读写分离、一主多从、混合模式
- 支持数据库敏感配置信息加密ENC
- 支持每个数据库独立初始化表结构 schema 和数据库 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 事务混用
约定
- 本框架只做切换数据源 这个核心的事情,并不限制你的具体操作,切换了数据源可以做任何 CRUD
- 配置文件所有的以下划线分割的数据源首部即为组的名称,相同组名称的数据源会放在一个组下
- 切换数据源可以是组名,也可以是具体数据源名称。组名切换时采用负载均衡算法切换,默认是轮询的
- 默认的数据源名称为 master ,可以通过
spring.datasource.dynamci.primary
修改 - 方法上的注解优先于类上注解
- DS 支持继承抽象类上的 DS,暂不支持继承接口上的DS
使用方法
引入依赖
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>dynamic-datasource-spring-boot-starter</artifactId>
<version>4.3.0</version>
</dependency>
配置数据源
spring:
datasource:
dynamic:
enabled: true #启用动态数据源,默认true
primary: master #设置默认的数据源或者数据源组,默认值即为master
strict: false #严格匹配数据源,默认false. true未匹配到指定数据源时抛异常,false使用默认数据源
grace-destroy: false #是否优雅关闭数据源,默认为false,设置为true时,关闭数据源时如果数据源中还存在活跃连接,至多等待10s后强制关闭
datasource:
master:
url: jdbc:mysql://xx.xx.xx.xx:3306/dynamic
username: root
password: 123456
driver-class-name: com.mysql.jdbc.Driver # 3.2.0开始支持SPI可省略此配置
slave_1:
url: jdbc:mysql://xx.xx.xx.xx:3307/dynamic
username: root
password: 123456
driver-class-name: com.mysql.jdbc.Driver
slave_2:
url: ENC(xxxxx) # 内置加密,使用请查看详细文档
username: ENC(xxxxx)
password: ENC(xxxxx)
driver-class-name: com.mysql.jdbc.Driver
#......省略
#以上会配置一个默认库master,一个组slave下有两个子库slave_1,slave_2
多主多从:
spring:
datasource:
dynamic:
datasource:
master_1:
master_2:
slave_1:
slave_2:
slave_3:
纯粹多库:
spring:
datasource:
dynamic:
datasource:
mysql:
oracle:
sqlserver:
postgresql:
h2:
混合配置:
spring:
datasource:
dynamic:
datasource:
master:
slave_1:
slave_2:
oracle_1:
oracle_2:
使用 @DS
切换数据源。
@DS 可以注解在方法上或类上,同时存在就近原则 方法上注解 优先于 类上注解。
注解 | 结果 |
---|---|
没有@DS | 默认数据源 |
@DS("dsName") | dsName可以为组名也可以为具体某个库的名称 |
@Service
@DS("slave")
public class UserServiceImpl implements UserService {
@Autowired
private JdbcTemplate jdbcTemplate;
public List selectAll() {
return jdbcTemplate.queryForList("select * from user");
}
@Override
@DS("slave_1")
public List selectByCondition() {
return jdbcTemplate.queryForList("select * from user where age >10");
}
}