A01-多数据源库的简单分析-dynamic-datasource-spring-boot-starter库的研究
最近在查看动态多数据源,看到了dynamic-datasource-spring-boot-starter库,地址在:https://github.com/baomidou/dynamic-datasource-spring-boot-starter
这里进行简单分析,学习其基本原理。
一、注解的引入
这个库,要求通过DS注解,在自己的业务代码的方法上,声明使用哪种数据源。
//如果引入了jar包,通过 spring的META-INF/spring.factories自动引入配置 DynamicDataSourceAutoConfiguration //DynamicDataSourceAutoConfiguration 类,注册了Bean DynamicDataSourceAnnotationAdvisor,这里实现了对DS注解的拦截器 DynamicDataSourceAnnotationInterceptor
二、注解引起的拦截器
/**
* 这里通过一个线程上下文变量实现了 数据源坐标的传递。
* 并且带有推断的功能,如果是#开头的数据源坐标,可以在运行时,通过DsProcessor 进行推断,推断结果,放入dsKey中。
* 如果是普通数据源,则直接放在线程上下文里。 这里使用了Deque 来存储,跟随着调用线程栈的变化而变化(如果遇到了DS注解,也会入栈出栈)。
*
*/
public class DynamicDataSourceAnnotationInterceptor implements MethodInterceptor { @Override public Object invoke(MethodInvocation invocation) throws Throwable { try { String dsKey = determineDatasourceKey(invocation); DynamicDataSourceContextHolder.push(dsKey); return invocation.proceed(); } finally { DynamicDataSourceContextHolder.poll(); } } }
/**
* 这里为止,只是在线程里,放了数据源的坐标
*/
三、mybatis插件的配合
/**
* 这里使用了mybatis的插件规范,增加@Intercepts注解,声明拦截mybatis的什么类、什么方法、什么入参。
* 因为 增、删、改 都是走的update,这里拦截了mybatis的Executor的query和update方法
*
* 这里主要处理读写分离之类的需求,如果当前没有指定数据源(线程上下文没有设置),就尝试主备配置。
* 对于多数据源切换之类的需求,这里可以跳过,没有实质逻辑
*/
@Intercepts({ @Signature(type = Executor.class, method = "query", args = {MappedStatement.class, Object.class, RowBounds.class, ResultHandler.class}), @Signature(type = Executor.class, method = "update", args = {MappedStatement.class, Object.class})}) @Slf4j public class MasterSlaveAutoRoutingPlugin implements Interceptor { @Autowired private DynamicDataSourceProperties properties; @Override public Object intercept(Invocation invocation) throws Throwable { Object[] args = invocation.getArgs(); MappedStatement ms = (MappedStatement) args[0]; boolean empty = true; try { empty = StringUtils.isEmpty(DynamicDataSourceContextHolder.peek()); if (empty) { DynamicDataSourceContextHolder.push(getDataSource(ms)); } return invocation.proceed(); } finally { if (empty) { DynamicDataSourceContextHolder.clear(); } } } }
四、动态数据源
/** * 这里声明了一个动态数据源,实现了DataSource,注册给外部使用。如果使用了mybatis的话,mybatis在获取connection的时候,会在这里获取connection * * */ @Slf4j public class DynamicRoutingDataSource extends AbstractRoutingDataSource implements InitializingBean, DisposableBean { //通过线程变量,配合出来,获取最新的一次设置的数据源坐标(函数调用存在嵌套,此时数据源坐标的Deque可能入栈了好几个坐标了) @Override public DataSource determineDataSource() { return getDataSource(DynamicDataSourceContextHolder.peek()); } //通过DataSource获取connection @Override public Connection getConnection() throws SQLException { return determineDataSource().getConnection(); } //做选择策略 public DataSource getDataSource(String ds) { if (StringUtils.isEmpty(ds)) { return determinePrimaryDataSource(); } else if (!groupDataSources.isEmpty() && groupDataSources.containsKey(ds)) { log.debug("dynamic-datasource switch to the datasource named [{}]", ds); return groupDataSources.get(ds).determineDataSource(); } else if (dataSourceMap.containsKey(ds)) { log.debug("dynamic-datasource switch to the datasource named [{}]", ds); return dataSourceMap.get(ds); } if (strict) { throw new RuntimeException("dynamic-datasource could not find a datasource named" + ds); } return determinePrimaryDataSource(); }
五、总结
支持多个数据源,支持动态添加、删除数据源,而且有一些兜底策略;可以根据条件,用代码动态选择数据源,支持主备读写分离。 感谢原作者的贡献和开源。
和我具体业务的需求的有一点不同:需要动态控制数据源的创建、检测和删除,暂时空闲的数据源连接池要删除;提供一个批量任务执行框架,可以批量并发执行SQL操作,而且控制其并发度,不超过本地连接池的连接数。