多数据源配置, 大多数情况下是用在读写库或者主从库之间的自动切换. 但是在有些情况下, 业务上也需要切换数据源.
而现在主流的多数据源方案, 无论是使用现有的框架还是自定义, 使用的核心原理都是类似的. 这个实例, 就是模拟业务上多数据源的使用场景, 自定义切换规则, 以实现多数据源的切换.
实例的实现逻辑是:
在有注解标注的地方, 按照优先使用注解标注的数据源;
如果注解没有指定数据源, 将解析URL来自动匹配数据源;
如果没有使用注解, 将使用默认数据源
使用的原理是线程内部维护数据源变量, 当请求进来时, 利用切面, 指定数据源, 并在线程内部传递
因为使用的变量都是维护在线程内部, 故而当有线程切换时, 会丢失数据源, 此时需要手动传递数据源, 或者依据对应技术使用可行的技术实现动态的切换, 但是原理是一致的
实例里的多处核心逻辑都是可以扩展或者自定义的, 以满足不同业务的需求.
使用的框架和技术:
springboot
mybatis
mysql
ThreadLocal
AOP
DynamicDataSource
代码展示
先展示一下实例的文件结构:
下面对主要的几个文件进行说明:
application.yml
1 | yml复制代码# 应用名称 |
配置文件, 除了基本的应用名称、端口、路径、日志等配置以外, 主要看数据库驱动的配置, 这里我们不像平时的单数据源的配置, 我们需要配置多个数据源, 上面示例的配置文件里, 由两个注意点:
- db1和db2是我们自定义的数据源名称, 这个我们在后面的MybatisConfig文件里会用到
- 数据源里的配置
jdbc-url
对应单数据源配置里的url
, 这里使用url会注入失败 - ${db1.username}等是导入的
application-jdbc.properties
文件里的配置值
application-jdbc.properties
1 | properties复制代码db1.username = user |
key-value结构, key可以在引入此文件的配置里使用${key}引用
这里我们使用两个数据库, 但是数据库的表结构一致(至少多数据源涉及到的表需要结构一致), 以避免不能共用一套代码
DataSourceType
1 | java复制代码public enum DataSourceType { |
此文件是数据源的枚举, 这里的枚举我们只配置里一个参数, 可以依据业务扩展. 本枚举的值代表数据源的名称, 是和配置文件里的配置对应上的
另外说明一下, NONE是用来代表默认数据源的, 是为了方便编程添加的, 不是必须的
DataSourceUtil
1 | java复制代码public class DataSourceUtil { |
数据源切换工具类, 这里的核心是ThreadLocal<DataSourceType>
变量, ThreadLocal以前有过文档分析, 主要是用来维护线程内部变量的, 其中:
- get()方法用来获取数据源
- set()方法用来设置数据源
- remove()用来清除数据源
MybatisConfig*
1 | Java复制代码@Configuration |
本类主要用来配置和注入mybatis相关配置bean, 主要有下面几步:
- 自动扫描mapper接口配置
@MapperScan(“jin.panpan.database.dao”)
一般常规配置, 如果不配置多数据源, 一般是在入口类上注解
- 数据源注入
@ConfigurationProperties 导入配置文件里的配置
@Bean 指定数据源名称
@Primary 标记默认数据源
- 数据源动态选择Bean注入
加载多个数据源, 如果需要的话, 指定默认数据源
需要注意数据源容器Map的key要和DataSourceType里的字段值一致, 否则无法切换
- 会话工厂Bean配置
单数据源时, 一般无需手动配置; 多数据源时, 需要手动指定会话工厂, 但是配置是模式化的
- 事务管理Bean配置
事务管理的配置也是模式化的, 一般无特殊处理之处
DynamicDataSource*
1 | Java复制代码public class DynamicDataSource extends AbstractRoutingDataSource { |
此类其实是动态路由数据源的核心类, 扩展AbstractRoutingDataSource类, 重写determineCurrentLookupKey方法, determineCurrentLookupKey方法的返回值就是动态数据源, 返回值需要和配置文件及MybatisConfig里的一一对应.
DataSource和DataSourceAspect*
1 | Java复制代码@Retention(RetentionPolicy.RUNTIME) |
DataSource是一个方法注解, 用来作为数据源切换切入点, 此处可以使用类注解或者直接正则织入想监控的点
DataSourceAspect是注解的实现, 是核心实现
1 | Java复制代码@Aspect |
重要的步骤已经加了注解, 完成的工作是:
- 获取数据源 从注解/从路径
- 指定数据源
- 调用结束后清除数据源
核心步骤是通过DataSourceUtil切换数据源
此方法是实现切换逻辑的核心方法, 具体的切换逻辑, 应该由业务逻辑决定, 这里只是示范
逻辑验证
这里说明一下测试数据:
两个数据库: db1 db2
db1中有一条数据 id=1
db2中没有数据
核心代码也有日志, 可以依据日志观察数据源
为了区分路径, 在hosts里加了如下配置, 以区分不同环境路径:
1 | 复制代码127.0.0.1 db1.db.com |
不指定数据源, 也不启用数据源注解
1 | Java复制代码@GetMapping("queryById/{id}") |
按预期使用默认数据源db1,可以查询到一条数据
启用注解, 不指定数据源
1 | Java复制代码@DataSource |
按预期将启用地址匹配数据源
启用注解, 指定数据源
1 | Java复制代码@DataSource(DataSourceType.DB2) |
按预期将使用指定的数据源2
测试在线程间切换时, 导致的数据源丢失
1 | Java复制代码@SneakyThrows |
可以看出, 虽然指定了db2, 但是还是查询出了数据, 说明走了默认数据源, 有日志也可以看出
在线程里(2.basTable=)数据源丢失了, 因为线程已经不是原先的线程了
在线程间切换时, 导致的数据源丢失问题解决
1 | Java复制代码@SneakyThrows |
看日志:
数据源传递成功
在其他的数据源可能丢失的情况, 都可以使用此方法实现
资源清单:
database-dev.zip
本文转载自: 掘金