背景
由于系统推广需求,应用统一管理,业务数据按照每个省份存储到不同的数据库中,即对业务系统按照省份进行分库改造。
要求
- 尽可能减少对目前系统代码的侵入式改造
- 根据操作人员的省份机构号进行路由
改造内容
- 根据用户动态路由实现多数据源切换
- 进行多数据源的动态管理
- 定时任务支持多数据源
Dynmiac-Datasource
官方github
官方使用示例
该组件已实现了对了多数据源的管理,因此通过引入该组件,我们可以将重点集中在如何进行数据源的切换。
数据库
- 应用管理形式:应用集中管理,即只部署一套系统,不根据省份进行分别部署。
- 数据库管理形式:主库放置基础信息,每个省份建立数据库,独立维护业务信息。
动态路由
dynamic-datasource提供了两种方式以供切换数据源
@DS注解:支持多种方式
- 静态指定
- 获取session中的属性
- 获取header中的属性
- 基于spel动态解析方法或者属性
@DS("slave") //静态指定
@DS("#session.attribute")//基于session
@DS("#header.attribute")//基于header
@DS()//spel
源码通过使用Processor对@DS内的内容进行解析,当为以#session开头时,会解析session内部特定的attribute。
- 手动切换数据源
DynamicDataSourceContextHolder.push(datasource);
try(){
//处理逻辑
}
finally{
DynamicDataSourceContextHolder.poll();
}
DynamicDataSource是采用栈的形式进行数据源的切换,在使用完后要进行及时进行弹栈操作。
- 最终选择采用@DS注解配合session的方式
session路由实现
@DS("#session:provBranchCode")
通过在登录函数后为session添加该字段即可实现,具体省级机构号需要通过用户信息查询机构号
UserInfo userInfo = UserInfoMapper.selectById(userId);
String provBranchCode = userInfo.getProvBranchCode();
HttpSession session = request.getSession();
session.setAttribute("provBranchCode","000");
其中用户信息表作为通用信息需要存放在主库,并指定@DS("master")进行静态路由。
但是在使用多线程的情况下,新线程有自己独立的栈,这个时候会出现session为空的问题。针对这个问题,通过MockSession来进行解决。
public class MockHttpServletRequest implements HttpServletRequest{}
public class MockHttpSession implements HttpSession{}
通过创建MockHttpServletRequest、MockHttpSession并进行Http请求和session的实现。
MockHttpServletRequest request = new MockHttpServletRequest();
RequestAttributes requestAttributes = new ServletrequestAttributes(request);
RequestContextHolder.setRequestAttributes(requsetAttributes);
@DS注解
目前@DS注解支持添加到DAO层的Mapper、Service层的接口以及实现、Controller层。官方推荐将注解加至Service层的实现上。
系统的数据源切换不是基于业务类别,而是基于机构号,因此选择将@DS注解添加至Mapper层。
定时任务多数据源
系统定时任务基于XXL-job实现。
在定时任务改造时有下列设计方案:
- 为每个省份配置一个定时任务
- 配置一个定时任务,在任务中遍历循环
- 配置一个定时任务,通过为每个省份开启一个线程执行。并通过解析定时任务参数指定执行某个数据源的定时任务
数据源切换失败
关于数据源问题可以看一下下面的连接,较为全面。
数据源切换失败问题汇总
除此之外,在开发过程中遇到了上述没有涉及到的问题。
主要是由于采用了Cursor游标流式读取,配合@DSTranscational时会出现cursor has been closed的问题,经过分析后是由以下原因导致的。
- @Transcational:Cursor通常需要配合@Transcational使用,spring原生的@Transcational会在开启后始终保持与数据库的连接,这样在获取到Cursor后在使用时不会出现cursor has been closed的问题,但这也导致了无法切换数据源的问题。
- @DSTranscational:业务中由于@DS注解加在了Mapper层上,需要从主数据源切换到省份对应的数据源,因此使用了@DSTranscational注解。这就导致了执行完Mapper层的获取Cursor操作后,会进行数据源的弹出,进一步导致了链接和游标的关闭。
解决方案:在使用了cursor的service层实现上使用@Transcational和@DS("#session:provBranchCode")注解,在开启原生事务前提前完成主从数据源的切换。
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。