多数据源 (2)DynamicRoutingDataSource

7,393 阅读2分钟

上一篇的DynamicDataSource展示了实现多数据源切换的思路,实际上MP已经有封装好的多数据源切换框架了,它比手动实现的功能要多一些。

实现与原理

引入jar包

    <dependency>
        <groupId>com.baomidou</groupId>
        <artifactId>dynamic-datasource-spring-boot-starter</artifactId>
        <version>3.3.2</version>
    </dependency>

手动创建master和demo两个数据源,配置DynamicRoutingDataSource

    @Bean(name = "dynamicDataSource")
    @DependsOn({"master", "demo"})
    @Primary
    public DynamicRoutingDataSource dataSource(DynamicDataSourceProvider dynamicDataSourceProvider) {
        DynamicRoutingDataSource dynamicDataSource = new DynamicRoutingDataSource();
        // 配置多数据源
        dynamicDataSource.addDataSource("master", mainSource());
        dynamicDataSource.addDataSource("demo", demoSource());
        dynamicDataSource.setPrimary("master");
        // 读取yml中配置的数据源,本篇没有用到
        dynamicDataSource.setProvider(dynamicDataSourceProvider);
        return dynamicDataSource;
    }

关于DynamicRoutingDataSource读取yml配置自动添加数据源,参见官方文档

获取连接时,入口方法仍然是getConnection()

DynamicRoutingDataSource->getConnection():
    String xid = TransactionContext.getXID();
    // 此时必然进if分支
    if (StringUtils.isEmpty(xid)) {
        // 获得数据源并调用它的getConnection()方法
        return determineDataSource().getConnection();
    } else {
        ......
    }
DynamicRoutingDataSource->determineDataSource():
    String dsKey = DynamicDataSourceContextHolder.peek();
    return getDataSource(dsKey);
    
// 根据key返回数据源
DynamicRoutingDataSource->getDataSource():
    if (StringUtils.isEmpty(ds)) {
        return determinePrimaryDataSource();
    } else if {
        ......
    }
    } else if (dataSourceMap.containsKey(ds)) {
        return dataSourceMap.get(ds);
    }

DynamicDataSourceContextHolder内部以栈形式存储,后进先出,使用push(String ds)设置当前数据源(入栈),使用peek()获得当前线程数据源(获得栈顶值),没有设置的话则返回主数据源,使用poll()清空当前数据源(出栈),使用clear()清空栈值,调用push需要确保最终清空(入栈需出栈)

@DS

@DS注解可用在类或方法,作用是自动切换数据源。

它的Advisor是DynamicDataSourceAnnotationAdvisor,当类或方法存在@DS注解时满足切面表达式,拦截器是DynamicDataSourceAnnotationInterceptor

DynamicDataSourceAnnotationInterceptor->invoke():
    // 获得注解值
    String dsKey = determineDatasourceKey(invocation);
    // 设置数据源
    DynamicDataSourceContextHolder.push(dsKey);
    try {
        // 执行过程
        return invocation.proceed();
    } finally {
        // 清空数据源
        DynamicDataSourceContextHolder.poll();
    }

@DSTransactional

@DSTransactional是多数据源事务,多个数据源一起提交或者一起回滚,可用在类或方法。

它的定义位于DynamicDataSourceAutoConfiguration类,当类或方法存在@DSTransactional注解时满足切面表达式,拦截器是DynamicLocalTransactionAdvisor

image.png

DynamicLocalTransactionAdvisor->invoke():
    // xid是随机字符串,只要有值就是事务
    if (!StringUtils.isEmpty(TransactionContext.getXID())) {
        return methodInvocation.proceed();
    }
    boolean state = true;
    // 设置随机字符串xid
    String xid = UUID.randomUUID().toString();
    TransactionContext.bind(xid);
    try {
        o = methodInvocation.proceed();
    } catch (Exception e) {
        state = false;
        throw e;
    } finally {
        ConnectionFactory.notify(state); // --1
        TransactionContext.remove();
    }
    return o;

多数据源事务获取连接时,入口方法仍然是getConnection()

DynamicRoutingDataSource->getConnection():
    String xid = TransactionContext.getXID();
    // 此时必然进else分支
    ......
    } else {
        // 当前数据源
        String ds = DynamicDataSourceContextHolder.peek();
        // 缓存Map集合,key是数据源名称,value是数据源
        ConnectionProxy connection = ConnectionFactory.getConnection(ds);
        // 缓存Map集合没有的话,实时获取
        return connection == null ? getConnectionProxy(ds, determineDataSource().getConnection()) : connection;
    }
AbstractRoutingDataSource->getConnectionProxy():
    ConnectionProxy connectionProxy = new ConnectionProxy(connection, ds);
    ConnectionFactory.putConnection(ds, connectionProxy); // --2
    return connectionProxy;
ConnectionFactory->putConnection(): // --2
    Map<String, ConnectionProxy> concurrentHashMap = CONNECTION_HOLDER.get();
    if (!concurrentHashMap.containsKey(ds)) {
        try {
            // 设置自动提交为false
            connection.setAutoCommit(false);
        } catch (SQLException e) {
            e.printStackTrace();
        }
        concurrentHashMap.put(ds, connection);
    }
ConnectionFactory->notify(): // --1
    Map<String, ConnectionProxy> concurrentHashMap = CONNECTION_HOLDER.get();
    // 遍历所涉及到的数据源
    for (ConnectionProxy connectionProxy : concurrentHashMap.values()) {
        connectionProxy.notify(state);
    }
    
ConnectionProxy->notify():
    // 根据state的值决定是提交还是回滚
    if (commit) {
        connection.commit();
    } else {
        connection.rollback();
    }
    // 回收的时候会将自动提交属性还原
    connection.close();

@DSTransactional不像@Transactional那样可以定义回滚异常与非回滚异常,也没有事务的传播机制,但是它提供了对多数据源事务的支持。