GitChat学术分享 网址:https://gitbook.cn/books/5c6ce6d0565cd4570835e62c/index.html
随着业务的发展,数据库的压力肯定会越来越大,如何分割数据库的读写压力是我们需要考虑的问题,而能够动态的切换数据源就是我们的首要目标。
在网上搜索 MyBatis 多数据源大部分文章都是配置到配置文件里。这样每次新建数据源都需要由 Java 开发者自己修改配置文件,修改相关的配置类,工具类,然后再重启后台服务才能生效,一旦后续增加的数据源较多,新增数据源将会是个繁琐的重复性工作。
经过一段时间总结和借鉴多数据源技术文章,终于构建了一套符合需求的项目,相对之前的配置数据源配置文件大大减少了 Java 开发者的工作量,也降低了由于修改代码产生的出错率。因此借此机会在 GitChat 平台和大家分享。
多数据源 支持主流数据源 MySQL、Sqlserver、Oracle 等数据源
动态切换 新建数据源,只需在默认数据库增加一行数据源配置信息,不修改配置文件。 新数据源参数传递到后台服务后,判断是否存在该数据源,若不存在自动建立新的连接池。
不重启服务 由于不需要修改配置文件,后台服务就不需要重启,提高了后台服务的稳定性和可靠性。
持久化数据源 超过 20 分钟无服务请求,再次请求不会出现超时或数据库重新连接错误提示。 服务所在服务器断网一段时间后,又再次联网,再次请求服务不会提示数据库连接断开等错误提示。
可扩展性强 支持单数据源和多数据源,对于未来项目扩展其他数据源对接提供了基础配置。
线程独立 每个请求独立占用一个单独线程,不会出现数据混乱。同时可以在方法中自由的切换数据源,实现多库数据汇总功能。
单数据源项目、多数据源项目。
系统新旧框架切换时数据源切换。
项目周期数据源配置 :开发库、测试库、正式库配置。
Filter 拦截器 和 AbstractRoutingDataSource 抽象类
Filter 拦截器
直接实现 Filter 接口,重写拦截方法,再到 @WebFilter 注解上配置拦截规则即可实现。
下面是拦截到请求中的 dsn 参数,重写拦截方法,判断是否存在数据源,若不存在数据源则新建数据源并切换到该数据源,再继续下一个链。
@WebFilter(urlPatterns = "/*")
public class DataSourceFilter implements Filter {
private static final Logger log = LoggerFactory.getLogger(DataSourceFilter.class);
@Resource
private ProjectListMapper projectListMapper;
@Override
public void destroy() {
}
@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain arg2) throws IOException, ServletException {
String dsn=request.getParameter("dsn");//SqlServer_ip_数据库
try {
if (dsn != null && dsn.trim().length() 0) {
//根据dsn获取连接信息
SysDBInfo sysDBInfo = new SysDBInfo();
ProjectList projectList=new ProjectList();
projectList.setDsn(dsn);
//切换到默认数据库
DataSourceContextHolder.setDataSourceType(DEFAULT_DATASOURCE_TYPE);
//根据dsn获取数据库配置
List0){
sysDBInfo.setDbType(dataMapList.get(0).getDataType());
sysDBInfo.setIpAddress(dataMapList.get(0).getAddress());
sysDBInfo.setServiceName(dataMapList.get(0).getData());
sysDBInfo.setUserName(dataMapList.get(0).getUserName());
sysDBInfo.setPwd(dataMapList.get(0).getPwd());
sysDBInfo.setDsn(dataMapList.get(0).getDsn());
}
//如果不存在当前数据源,创建数据源并添加
if (!DataSourceConfig.hasCurrentDataSource(sysDBInfo)) {
log.info("不存在数据源:{}",sysDBInfo);
DataSource ds = DataSourceConfig.buildDataSource(sysDBInfo);
DataSourceConfig.addDataSource();
}else {
log.info("存在数据源:{}",sysDBInfo);
}
//使用当前数据源
DataSourceContextHolder.setDataSourceType(sysDBInfo.getDsn());
}
} catch (Exception e) {
e.printStackTrace();
}
arg2.doFilter(request, response); //到下一个链
//清除数据源
DataSourceContextHolder.clearDataSourceType();
}
@Override
public void init(FilterConfig arg0) throws ServletException {
}
}
AbstractRoutingDataSource
这个类是实现多数据源的关键,他的作用就是动态切换数据源,实质:有多少个数据源就存多少个数据源在 targetDataSources( 是 AbstractRoutingDataSource 的一个 map 类型的属性,其中 value 为每个数据源,key 表示每个数据源的名字)这个属性中,然后根据 determineCurrentLookupKey()这个方法获取当前数据源在 map 中的key值,然后 determineTargetDataSource()方法中动态获取当前数据源,如果当前数据源不存并且默认数据源也不存在就抛出异常。
定义一个动态数据源
继承 AbstractRoutingDataSource 抽象类,并重写determineCurrentLookupKey()方法
public class DynamicDataSource extends AbstractRoutingDataSource{
@Override protected Object determineCurrentLookupKey() { return
DataSourceContextHolder.getDataSourceType();
}
}
创建一个切换数据源类型的类
ThreadLocal 就是为了线程的安全性,每个线程之间不会相互影响。
public class DataSourceContextHolder { public static final Logger
log = LoggerFactory.getLogger(DataSourceContextHolder.class);
/**
* 默认数据源
*/
public static final String DEFAULT_DATASOURCE_TYPE = DataSourceConfig.DEFAULT_DATASOURCE_TYPE;
private static final ThreadLocal();
public static void setDataSourceType(String dataSourceType) {
log.info("切换到{}数据源",dataSourceType);
contextHolder.set(dataSourceType); }
//获取数据源
public static String getDataSourceType() {
if(contextHolder.get() == null) {
return DEFAULT_DATASOURCE_TYPE;
}
return contextHolder.get(); }
//清除数据源
public static void clearDataSourceType() {
contextHolder.remove(); } }
定义默认数据源
通过引入配置文件,调用 DataSourceBuilder.create().build() 创建数据源;然后将默认数据源信息设置到 DataSource 里。
创建动态数据源
创建方法 buildDataSource()将数据源信息动态添加 map 里,再将 map 导入到 DataSource 里。
将方法 DataSource dataSource = factory.build(); 替换为 BasicDataSource dataSource1= (BasicDataSource) factory.build(); 这样可以给数据源对象增加持久化参数等。
启动类修改
将 @SpringBootApplication 改为 @SpringBootApplication(exclude= {DataSourceAutoConfiguration.class}) //不自动配置 DataSource 。 项目启动后,调用查询默认数据源的数据源列表,将数据源列表插入到 DataSoure 中。
项目启动数据源截图
项目启动日志
2019/02/25-23:46:37 [main] INFO com.d_cloud.Application-
<<============= Spring Boot Start OK =============>
2019/02/25-23:46:39 [main] DEBUG
com.d_cloud.dao.sqlserver.ProjectListMapper.selectAllBeans- ==
Preparing: select
pj_seq,pj_name,dsn,data,url,dataType,address,userName,pwd from
PROJECTLIST where userName is not null order by dsn,pj_name
2019/02/25-23:46:39 [main] DEBUG
com.d_cloud.dao.sqlserver.ProjectListMapper.selectAllBeans- ==>
Parameters: 2019/02/25-23:46:39 [main] DEBUG
com.d_cloud.dao.sqlserver.ProjectListMapper.selectAllBeans- <==
Total: 3 2019/02/25-23:46:43 [main] INFO
com.d_cloud.common.datasource.DataSourceConfig- 添加H191_dev数据源到Map
2019/02/25-23:46:44 [main] INFO
com.d_cloud.common.datasource.DataSourceConfig- 添加H191_test数据源到Map
2019/02/25-23:46:46 [main] INFO
com.d_cloud.common.datasource.DataSourceConfig- 添加H79_dev数据源到Map
2019/02/25-23:48:20 [main] INFO
com.d_cloud.common.datasource.DataSourceConfig-
添加Map到数据源dynamicDataSource 2019/02/25-23:48:20 [main] INFO
com.d_cloud.Application- Started Application in 109.951 seconds (JVM
running for 111.267)
多个数据源汇总查询
/** *项目列表——all */
@RequestMapping(value="ProjectListSum",method=RequestMethod.GET)
@ResponseBody public List
getProjectListSum(HttpServletRequest request, HttpServletResponse
response, ProjectList info ) throws Exception {
DataSourceContextHolder.setDataSourceType("H191_dev");
List
SpringCloud 微服务动态切换数据源
SpringCloud 的各个提供服务者目前是从远程 Git-Config 仓库获取各个服务的配置文件来配置数据库。修改某个服务的数据源需修改远程 Git-Config 仓库;若用此方法可以实现服务的动态切换,数据源加密到加密 token 解析再连接数据源;从而实现单个服务多数据源接口提供。
PaaS 平台应用
PaaS 平台可以针对每个开发者提供数据源配置表。开发者只需将项目周期的各个阶段数据源配置到表里,比如配置(开发,测试,正式数据源)配置到表里,无需开发连接数据相关的工具类。从而减少开发时间,提高开发效率,提高项目复用性。