本文来介绍工作中很常见的一个需求: 动态切换数据源 的理论原理及其实现.
动态数据源切换
实现
思路:
首先 默认读者知道实现动态数据源切换是使用的SpringBoot中 AbstractRoutingDataSource类结合注解和AOP来实现的.
有了这个理论前提, 那么再来构建思路就会很方便.
- 自定义一个注解
@TargetDataSource, 将来可以将该注解加在某个service类或者其中的方法上, 通过value属性来指定被修饰的类或者方法最终应该使用哪一个数据源. - 针对第一步, 对于被修饰的类或者方法, 其要使用的数据源名称需要在使用时方便取出, 所以很自然想到存到
ThreadLocal. - 定义切面, 在其中定义好切入点和环绕通知.(之所以是环绕通知是因为要在业务方法执行前确定使用过的数据源是哪一个并放入
ThreadLocal, 并且在使用后从ThreadLocal中删除, 避免内存泄漏). - 当Mapper执行的时候, 需要DataSource时, 会自动通过AbstractRoutingDataSource中去查找需要的数据源, 只需要返回
ThreadLocal中保存的值即可.
代码实现
定义注解
1 | package com.boy.springbootallroutingdatasource.annotation; |
定义ContextHolder
也就是用来存取数据源名称的ThreadLocal变量 和 存取数据源名称的方法.
1 | package com.boy.springbootallroutingdatasource.context; |
定义切面
定义了切入点表达式和环绕通知, 其中在环绕通知中对逻辑: “在业务方法执行前确定使用过的数据源是哪一个并放入ThreadLocal, 并且在使用后从ThreadLocal中删除, 避免内存泄漏.” 进行了实现.
1 | package com.boy.springbootallroutingdatasource.aspect; |
定义数据源属性类
根据yml文件里设定的数据源信息(或者也是根据DruidDataSource, 因为也没了也是根据其autoconfiguration里的设定来的), 来定义该类的属性.
1 | package com.boy.springbootallroutingdatasource.properties; |
定义数据源加载器类
该类用于读取所有数据源, 这里准备好可以供后面AbstractRoutingDataSource随意使用, 指定使用哪一个.
1 | package com.boy.springbootallroutingdatasource.datasource; |
定义DynamicDataSource
1 | package com.boy.springbootallroutingdatasource.datasource; |
定义默认数据源类型
1 | package com.boy.springbootallroutingdatasource.datasource; |
测试:
DB和层级代码准备:
DB:
1 | # 建库 |
User实体类
1 | package com.boy.springbootallroutingdatasource.service; |
Mapper接口
1 | package com.boy.springbootallroutingdatasource.service; |
service业务代码:
1 | package com.boy.springbootallroutingdatasource.service; |
执行测试
同样的测试代码:
1 | package com.boy.springbootallroutingdatasource; |
场景一. 修饰方法 不加@TargetDataSource注解使用默认数据源 和 加@TargetDataSource并设置属性value 为其指定特定数据源名称.
场景二. 修饰类
库实现 TO-DO
通过后台库里DataSource表的数据实现而非yml里配置的数据源。