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操作,而且控制其并发度,不超过本地连接池的连接数。