在 SpringBoot 中实现多数据源访问的最佳实践

1 概述在实际业务开发中通常会在单个应用中通过 分库分表 或者 读写分离的方式来提供应用的读写性能 。
在具体的开发中有很多方式:

  1. 通过不同的 mApper,映射到不同的 mybatis 源的方式
  2. 通过继承 Spring 的 AbstractRoutingDataSource 抽象类并重写 determineCurrentLookupKey 方法来管理多个数据源的方式
本文将详细介绍在 SpringBoot 应用中如何通过 自定义注解 和 aop 的方式实现多数据源的访问,采用了第二种的方式 。
2 关键点
  • 通过 @Aspect 注解来解析自定义注解
  • 通过继承 Spring 的 AbstractRoutingDataSource 抽象类,重写 determineCurrentLookupKey 方法来管理多个数据源
  • 通过 自定义注解 中的参数用来访问不同的数据源
  • 由于 mybatis 的事务 和 sqlSession 的打开和关闭 也是通过 aop 来实现的,因此这里必须通过 @Order 注解来提高自定义注解的优先级
3 使用场景
  1. 分库分表,根据业务来划分不同的库,比如与用户相关的表在 db_user 库,与订单相关的表在 db_order 库 。
  2. 读写分离,master 和 slave 模式,master 库只用来写入数据,slave 库只用来读取数据 。
这里根据场景 1 来实现具体的例子 。
4 具体的例子4.1 开发环境
  • SpringBoot: 2.2.2.RELEASE
  • mybatis-spring-boot-starter: 2.1.1
  • HikariCP: 3.4.1
4.2 数据库和表
  • db_user 库 的 t_user 表如下
CREATE TABLE `t_user` (`id` int(18) NOT NULL AUTO_INCREMENT COMMENT '流水号',`name` varchar(25) COLLATE utf8_bin DEFAULT NULL COMMENT '名称',`age` int(10) DEFAULT NULL COMMENT '年龄',`sex` char(1) COLLATE utf8_bin DEFAULT NULL COMMENT '性别',`remarks` varchar(255) COLLATE utf8_bin DEFAULT NULL COMMENT '备注',`create_date` datetime DEFAULT NULL COMMENT '创建时间',`create_user` varchar(64) COLLATE utf8_bin DEFAULT NULL COMMENT '创建人',`update_date` datetime DEFAULT NULL COMMENT '更新时间',`update_user` varchar(64) COLLATE utf8_bin DEFAULT NULL COMMENT '更新人',`del_flag` char(1) COLLATE utf8_bin DEFAULT NULL COMMENT '删除标记(0:正常;1:删除)',PRIMARY KEY (`id`)) ENGINE=InnoDB AUTO_INCREMENT=2 DEFAULT CHARSET=utf8 COLLATE=utf8_bin COMMENT='用户信息表'
  • db_order 库 的 t_order 表如下
CREATE TABLE `t_order` (`id` int(18) NOT NULL AUTO_INCREMENT COMMENT '流水号',`user_id` int(18) DEFAULT NULL COMMENT '用户id',`order_date` datetime DEFAULT NULL COMMENT '订单时间',`order_amount` decimal(10,0) DEFAULT NULL COMMENT '订单金额',`remarks` varchar(255) COLLATE utf8mb4_bin DEFAULT NULL COMMENT '备注',`create_date` datetime DEFAULT NULL COMMENT '创建时间',`create_user` varchar(64) COLLATE utf8mb4_bin DEFAULT NULL COMMENT '创建人',`update_date` datetime DEFAULT NULL COMMENT '更新时间',`update_user` varchar(64) COLLATE utf8mb4_bin DEFAULT NULL COMMENT '更新人',`del_flag` char(1) COLLATE utf8mb4_bin DEFAULT NULL COMMENT '删除标记(0:正常;1:删除)',PRIMARY KEY (`id`)) ENGINE=InnoDB AUTO_INCREMENT=2 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_bin COMMENT='订单表'4.3 代码结构如下
在 SpringBoot 中实现多数据源访问的最佳实践

文章插图
 
4.4 自定义注解和 AOP 实现
  1. MultiDataSource
import com.ckJAVA.entity.DbEnum;import java.lang.annotation.*;/** * 数据库切换的注解,只作用在方法上 */@Target(ElementType.METHOD)@Retention(RetentionPolicy.RUNTIME)@Documentedpublic @interface MultiDataSource {// 用于指定数据库名称的DbEnum value() default DbEnum.user;}
  1. 通过 aop 来读取注解的配置,并在方法前后进行数据库的切换
import com.ckjava.aop.annotation.MultiDataSource;import com.ckjava.config.MultiDataSourceHolder;import com.ckjava.entity.DbEnum;import org.aspectj.lang.JoinPoint;import org.aspectj.lang.annotation.After;import org.aspectj.lang.annotation.Aspect;import org.aspectj.lang.annotation.Before;import org.aspectj.lang.annotation.Pointcut;import org.aspectj.lang.reflect.MethodSignature;import org.springframework.core.annotation.Order;import org.springframework.stereotype.Component;import java.lang.reflect.Method;/** * 通过 aop 来读取注解的配置,并在方法前后进行数据库的切换 */@Aspect@Component@Order(1)public class MultiDataSourceAspect {@Pointcut("@annotation(com.ckjava.aop.annotation.MultiDataSource)")public void dataSourcePointCut() {}/*** 在方法执行前设置数据库 key** @param point*/@Before("dataSourcePointCut()")public void before(JoinPoint point) {MethodSignature signature = (MethodSignature) point.getSignature();Method method = signature.getMethod();// 在含有 MultiDataSource 注解的方法执行前,设置线程的数据库源变量MultiDataSource dataSource = method.getAnnotation(MultiDataSource.class);if (dataSource == null) {MultiDataSourceHolder.setDataSource(DbEnum.user);} else {MultiDataSourceHolder.setDataSource(dataSource.value());}}/*** 在方法执行后移除 数据库 key*/@After("dataSourcePointCut()")public void after() {// 移除线程本地数据库源变量MultiDataSourceHolder.clearDataSource();}}


推荐阅读