1 Star 0 Fork 0

sunwul / dynamic-datasource

加入 Gitee
与超过 1200万 开发者一起发现、参与优秀开源项目,私有仓库也完全免费 :)
免费加入
该仓库未声明开源许可证文件(LICENSE),使用请关注具体项目描述及其代码上游依赖。
克隆/下载
贡献代码
同步代码
取消
提示: 由于 Git 不支持空文件夾,创建文件夹后会生成空的 .keep 文件
Loading...
README

动态数据源、多数据源切换

集成Mybatis框架及事务控制

一,多数据源的典型使用场景

1.业务复杂(数据量大)

数据分布在不同的数据库中,根据业务拆分了数据,应用没有拆分.

一个公司多个子项目,不同业务各用各的数据库,涉及数据共享...

2.读写分离

为了解决数据库的性能瓶颈(读比写性能更高, 写锁会影响读阻塞, 从而影响读的性能)

当读与写都放在一个数据库中操作, 在高并发的情况下, 会出现读锁与写锁互斥的情况, 因为写锁具有排他性, 当对一个数据进行写操作时, 会上一把写锁, 如果同时又对此数据进行读操作, 此时读操作的连接会被阻塞, 等待写操作完成, 写锁被释放后, 才会进行读操作.此时就需要对数据库进行读写分离

很多数据库用主从架构, 即 一台主数据库服务器, 对外提供增删改业务, 一台或多台从数据库服务器, 主要进行读的操作. 在读写分离中, 主从库的数据是一致的. 数据更新操作都在主库上完成, 主库将数据变更信息同步给从库, 查询操作交给从库, 分担主库的压力

实现读写分离可以借助中间件(ShardingSphere, mycat, mysql-proxy, TDDL....) , 应用将数据操作请求发送给中间件, 由中间件根据读写请求进行分发, 当没有搭建读写分离的基础设施时, 就需要自行实现

二,如何实现多数据源

AbstractRoutingDataSource动态指定数据源

spring框架中, spring-jdbc模块提供了AbstractRoutingDataSource, 其内部可以包含多个DataSource, 然后在运行时来动态的访问哪个数据库

这种方式访问数据库的架构图如下所示:

应用直接操作的是AbstractRoutingDataSource的子类, 子类重写特定方法, 装载配置, 设置标识,告诉AbstractRoutingDataSource访问哪个数据库, 然后由AbstractRoutingDataSource从配置的数据源(DataSource1, DataSource2...)中选择一个, 访问对应的数据库

底层原理如下(方式二):

/*****
 * @author sunwul
 * @date 2022/2/15 9:53
 * @description 数据库操作类型
 */
public enum DatabaseOperation {

    Read(OperationConstant.OPERATION_TYPE_R, "读取"),
    Write(OperationConstant.OPERATION_TYPE_W, "写入");

    private String name;
    private String desc;

    DatabaseOperation(String name, String desc) {
        this.name = name;
        this.desc = desc;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public String getDesc() {
        return desc;
    }

    public void setDesc(String desc) {
        this.desc = desc;
    }

}
import com.alibaba.druid.spring.boot.autoconfigure.DruidDataSourceBuilder;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

import javax.sql.DataSource;
import java.sql.SQLException;

/*****
 * @author sunwul
 * @date 2022/2/14 19:32
 * @description  数据源配置
 */
@Configuration
public class DatasourceConfig {

    @Bean("dataSource_write")
    @ConfigurationProperties(prefix = "spring.datasource.datasource-write")
    public DataSource dataSource_write() {
        // 通过绑定全局配置,拿到属性后自动创建一个DruidDataSource
        return DruidDataSourceBuilder.create().build();
    }

    @Bean("dataSource_read")
    @ConfigurationProperties(prefix = "spring.datasource.datasource-read")
    public DataSource dataSource_read() throws SQLException {
        // 通过绑定全局配置,拿到属性后自动创建一个DruidDataSource
        return DruidDataSourceBuilder.create().build();
    }
}
# 配置文件
spring:
  datasource:
    type: com.alibaba.druid.pool.DruidDataSource
    # druid连接池
    druid:
      initial-size: 1
      min-idle: 1
      max-active: 20
      test-on-borrow: true
      validation-query: SELECT 1 FROM DUAL
    # 写库
    datasource-write:
      url: jdbc:mysql://localhost:3306/write?useUnicode=true&characterEncoding=utf8&serverTimezone=GMT%2b8
      username: root
      password: 123
      driver-class-name: com.mysql.cj.jdbc.Driver
    # 读库
    datasource-read:
      url: jdbc:mysql://localhost:3306/read?useUnicode=true&characterEncoding=utf8&serverTimezone=GMT%2b8
      username: root
      password: 123
      driver-class-name: com.mysql.cj.jdbc.Driver
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Primary;
import org.springframework.jdbc.datasource.lookup.AbstractRoutingDataSource;
import org.springframework.stereotype.Component;
import sunwul.datasource.emun.DatabaseOperation;

import javax.sql.DataSource;
import java.util.HashMap;
import java.util.Map;

/*****
 * @author sunwul
 * @date 2022/2/21 16:20
 * @description 动态数据源
 * 继承AbstractRoutingDataSource抽象类
 *      - 重写determineCurrentLookupKey()方法, 返回当前数据源标识
 *      - 重写afterPropertiesSet()方法, 在Bean初始化时装载配置[设置数据源列表,默认数据源], 调用父类方法, 使配置生效
 *
 * 此方法继承了Spring框架的抽象类, 只需要重写determineCurrentLookupKey()与afterPropertiesSet()方法
 * 其它方法使用Spring的默认实现, 与Spring框架更契合
 */
@Component
@Primary  // 作为主要注入Bean, 当出现相同类型的Bean时,会使用含此注解的Bean
public class DynamicDatasource extends AbstractRoutingDataSource {

    // 当前使用的数据源标识  线程安全(多线程对这个name进行同时读写时,会出现单独或篡改的情况)
    public static ThreadLocal<String> name = new ThreadLocal<>();

    // 写
    @Autowired
    DataSource dataSource_write;

    // 读
    @Autowired
    DataSource dataSource_read;

    /**
     * 返回当前数据源标识
     */
    @Override
    protected Object determineCurrentLookupKey() {
        return name.get();
    }

    /**
     * 初始化设置
     * 1. 设置数据源列表
     * 2. 设置默认的数据源
     * 3. 调用父类的afterPropertiesSet方法, 使设置生效
     * (具体可以查看父类afterPropertiesSet方法,可以看到此方法中判断了数据源列表[必要,否则会异常]和默认数据源[非必要])
     */
    @Override
    public void afterPropertiesSet() {
        // 所有数据源Map   将数据源标识与数据源put进来
        Map<Object, Object> targetDataSources = new HashMap<>();
        targetDataSources.put(DatabaseOperation.Write.getName(), dataSource_write);
        targetDataSources.put(DatabaseOperation.Read.getName(), dataSource_read);

        // targetDataSources 初始化所有数据源
        super.setTargetDataSources(targetDataSources);

        // defaultTargetDataSource 设置默认的数据源
        super.setDefaultTargetDataSource(dataSource_write);

        // 调用父类的 afterPropertiesSet 方法, 使设置生效
        super.afterPropertiesSet();
    }
}
动态数据源切换方式
Mybatis插件(适用于读写分离)

mybatis会通过Executor来执行数据库操作, 那么我们只需要拦截Executor中的方法,就可以在mybatis执行操作的时候进行增强

image-20220222101802030

import org.apache.ibatis.executor.Executor;
import org.apache.ibatis.mapping.MappedStatement;
import org.apache.ibatis.mapping.SqlCommandType;
import org.apache.ibatis.plugin.*;
import org.apache.ibatis.session.ResultHandler;
import org.apache.ibatis.session.RowBounds;
import org.springframework.stereotype.Component;
import sunwul.datasource.db.DynamicDatasource;
import sunwul.datasource.emun.DatabaseOperation;

import java.util.Properties;

/*****
 * @author sunwul
 * @date 2022/2/21 21:21
 * @description mybatis插件的动态数据源切换
 * * @Intercepts 标明这是一个mybatis插件(固定写法,插件是基于动态代理的)
 * * @Signature  设置要代理的对象, 方法, 参数列表(参数列表可以参考Executor源码中对应方法的参数列表)
 * 在mybatis执行操作的时候进行增强, mybatis会通过Executor来执行数据库操作(增删改/查 对应Executor中的update/query方法)
 * 因此,只需要代理Executor中的update与query方法
 */
// @Component // 只需要成为SpringBean, 在MybatisAutoConfiguration实例化时,会扫描到此Bean,自动注入  (这里不使用注解的方式,新建一个配置类创建Bean,便于管理)
@Intercepts({
        @Signature(type = Executor.class, method = "update", args = {MappedStatement.class, Object.class}),
        @Signature(type = Executor.class, method = "query", args = {MappedStatement.class, Object.class,
                RowBounds.class, ResultHandler.class})
})
public class DynamicDataSourcePlugin implements Interceptor {

    @Override
    public Object intercept(Invocation invocation) throws Throwable {
        // 获取当前方法的参数列表
        Object[] objects = invocation.getArgs();
        // 根据Executor类的源码可知, 不管是update还是query方法,参数列表中第一个参数都是MappedStatement
        // MappedStatement中封装了一个完整的SQL所对应的元素(参考我们平时写的**Mapper.xml, 有命令类型,id,返回结果类型...)
        MappedStatement ms = (MappedStatement) objects[0];

        // 根据SQL类型改变数据源标识,这样在执行数据库命令时,动态数据源就会根据数据源标识切换到对应数据源
        if (ms.getSqlCommandType().equals(SqlCommandType.SELECT)) {
            // 读
            DynamicDatasource.name.set(DatabaseOperation.Read.getName());
        } else {
            // 写
            DynamicDatasource.name.set(DatabaseOperation.Write.getName());
        }
        return invocation.proceed();
    }

    /**
     * 在新建可代理对象时,会通过此方法来决定是返回目标对象本身还是代理对象
     */
    @Override
    public Object plugin(Object target) {
        if (target instanceof Executor) {
            // 参考Interceptor类源码
            return Plugin.wrap(target, this);
        }
        return target;
    }

    /**
     * 此方法会在Configuration初始化当前的Interceptor时执行
     */
    @Override
    public void setProperties(Properties properties) {

    }
}
import org.apache.ibatis.plugin.Interceptor;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import sunwul.datasource.common.plugin.DynamicDataSourcePlugin;

/*****
 * @author sunwul
 * @date 2022/2/21 21:53
 * @description
 */
@Configuration
public class MybatisConfig {

    /**
     * 自定义的Bean会在MybatisAutoConfiguration实例化钱创建完成
     * 在MybatisAutoConfiguration实例化时,会自动扫描并注入所用到的所有Bean,此时就注入了我们自定义的Bean
     */
    @Bean
    public Interceptor dynamicDataSourcePlugin() {
        return new DynamicDataSourcePlugin();
    }
}
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import sunwul.datasource.entity.TAdmin;
import sunwul.datasource.service.TAdminService;

import java.util.List;

/*****
 * @author sunwul
 * @date 2022/2/21 21:54
 * @description
 */
@RestController
public class TestController {

    @Autowired
    TAdminService tAdminServiceImpl;

    @RequestMapping("/sel")
    public List<TAdmin> sel_admin() {
        // 切换数据源标识 - 不在需要手动切换数据源标识, 使用Mybatis插件代理Executor拦截特定方法来自动切换数据源标识
//        DynamicDatasource.name.set(DatabaseOperation.Read.getName());
        return tAdminServiceImpl.sel_admin();
    }

    @RequestMapping("/selOne")
    public TAdmin selOne_admin() {
//        DynamicDatasource.name.set(DatabaseOperation.Read.getName());
        return tAdminServiceImpl.sel_admin().get(0);
    }

    @RequestMapping("/add/{name}")
    public Integer add_admin(@PathVariable String name) {
//        DynamicDatasource.name.set(DatabaseOperation.Write.getName());
        return tAdminServiceImpl.add_admin(name);
    }
}
AOP(适用于业务复杂)

当一个项目中有多个子模块, 但数据库根据业务进行拆分了, 此时,可以使用AOP+自定义注解的方式, 根据注解信息来判断当前数据库操作使用的数据源标识, 提供给动态数据源切换对应数据源

/*****
 * @author sunwul
 * @date 2022/2/23 9:16
 * @description 操作类型常量
 */
public class OperationConstant {

    // 读
    public static final String OPERATION_TYPE_R = "R";
    // 写
    public static final String OPERATION_TYPE_W = "W";
}
import sunwul.datasource.emun.OperationConstant;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

/*****
 * @author sunwul
 * @date 2022/2/22 17:11
 * @description  自定义注解  数据源注解
 * * @Target 注解目标,指定注解可以声明在方法与类上
 * * @Retention 保留方式,设置为保留策略中的"运行时",这样就可以通过反射来判断注解是否存在
 */
@Target({ElementType.METHOD,ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
public @interface DataSource {

    // 用于保存数据源标识, 提供给动态数据源使用  默认写库
    String value() default OperationConstant.OPERATION_TYPE_W;

}
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.springframework.stereotype.Component;
import sunwul.datasource.annotaion.DataSource;
import sunwul.datasource.db.DynamicDatasource;

/*****
 * @author sunwul
 * @date 2022/2/23 9:23
 * @description  AOP
 * * 不管声明在方法还是类上, 有一个核心问题, 切换数据源的时机必须要在数据库操作前
 * * 由此可知, 只能使用前置通知或者环绕通知
 */
@Component
@Aspect
public class DynamicDataSourceAspect {

    // 前置
    @Before("within(sunwul.datasource.controller.*) && @annotation(dataSource)")
    public void before(JoinPoint point, DataSource dataSource){
        String value = dataSource.value();
        DynamicDatasource.name.set(value);
        System.out.println("注解获取的数据源标识: " + value);
    }
}
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import sunwul.datasource.annotaion.DataSource;
import sunwul.datasource.emun.OperationConstant;
import sunwul.datasource.entity.TAdmin;
import sunwul.datasource.service.TAdminService;

import java.util.List;

/*****
 * @author sunwul
 * @date 2022/2/21 21:58
 * @description
 */
@RestController
public class TestController {

    @Autowired
    TAdminService tAdminServiceImpl;

    @DataSource(OperationConstant.OPERATION_TYPE_R)
    @RequestMapping("/sel")
    public List<TAdmin> sel_admin() {
        // 切换数据源标识 - 不在需要手动切换数据源, 使用AOP拦截特定注解及属性切换数据源
//        DynamicDatasource.name.set(DatabaseOperation.Read.getName());
        return tAdminServiceImpl.sel_admin();
    }

    @DataSource(OperationConstant.OPERATION_TYPE_R)
    @RequestMapping("/selOne")
    public TAdmin selOne_admin() {
//        DynamicDatasource.name.set(DatabaseOperation.Read.getName());
        return tAdminServiceImpl.sel_admin().get(0);
    }

    @DataSource(OperationConstant.OPERATION_TYPE_W)
    @RequestMapping("/add/{name}")
    public Integer add_admin(@PathVariable String name) {
//        DynamicDatasource.name.set(DatabaseOperation.Write.getName());
        return tAdminServiceImpl.add_admin(name);
    }
}

SpringBoot集成多个Mybatis框架

通过集成多个Mybatis框架来实现多数据源, 这种方式不涉及到数据源的切换

其原理如下:

import org.apache.ibatis.logging.stdout.StdOutImpl;
import org.apache.ibatis.session.SqlSessionFactory;
import org.mybatis.spring.SqlSessionFactoryBean;
import org.springframework.core.io.support.PathMatchingResourcePatternResolver;

import javax.sql.DataSource;

/*****
 * @author sunwul
 * @date 2022/2/28 10:12
 * @description 工厂工具类
 */
public class FactoryUtil_manyMybatis {

    /**
     * 创建Mybatis SqlSessionFactory
     *
     * @param dataSource 数据源
     * @param mapperPath mapper.xml文件存放路径
     * @param isPrintLog 是否开启控制台日志  true-是  false-否
     * @return SqlSessionFactory
     * @throws Exception ex
     */
    public static SqlSessionFactory createFactory(DataSource dataSource, String mapperPath, boolean isPrintLog) throws Exception {
        final SqlSessionFactoryBean sqlSessionFactoryBean = new SqlSessionFactoryBean();
        // 指定数据源
        sqlSessionFactoryBean.setDataSource(dataSource);
        System.out.println("指定数据源:" + dataSource.getConnection().getMetaData().getURL());
        // 指定Mapper.xml文件
        sqlSessionFactoryBean.setMapperLocations(new PathMatchingResourcePatternResolver()
                .getResources(mapperPath));
        System.out.println("扫描并装载Mapper.xml文件: " + mapperPath);
        // 启用控制台日志
        if (isPrintLog) {
            org.apache.ibatis.session.Configuration configuration = new org.apache.ibatis.session.Configuration();
            configuration.setLogImpl(StdOutImpl.class);
            System.out.println("启用控制台日志...");
            sqlSessionFactoryBean.setConfiguration(configuration);
        }

        return sqlSessionFactoryBean.getObject();
    }
}
import com.alibaba.druid.spring.boot.autoconfigure.DruidDataSourceBuilder;
import org.apache.ibatis.session.SqlSessionFactory;
import org.mybatis.spring.annotation.MapperScan;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import sunwul.datasource.common.FactoryUtil_manyMybatis;

import javax.sql.DataSource;
import java.sql.SQLException;

/*****
 * @author sunwul
 * @date 2022/2/25 9:22
 * @description
 * * 指定扫描的包
 * * 指定使用的sqlSessionFactory
 */
@Configuration
@MapperScan(basePackages = "sunwul.datasource.mapper.read", sqlSessionFactoryRef = "sqlSessionFactory_r")
public class Read_MybatisConfig_manyMybatis {

    /**
     * 读库的数据源
     *
     * @return DataSource
     */
    @Bean("dataSource_read")
    @ConfigurationProperties(prefix = "spring.datasource.datasource-read")
    public DataSource dataSource_read() {
        System.out.println("==============读取配置信息,创建DataSource=============");
        // 通过绑定全局配置,拿到属性后自动创建一个DruidDataSource
        return DruidDataSourceBuilder.create().build();
    }

    /**
     * 配置读库的 SqlSessionFactory
     *
     * @return SqlSessionFactory
     * @throws Exception ex
     */
    @Bean
    public SqlSessionFactory sqlSessionFactory_r() throws Exception {
        return FactoryUtil_manyMybatis.createFactory(dataSource_read(), "classpath:mapper/read/*Mapper_manyMybatis.xml", true);
    }

}
import com.alibaba.druid.spring.boot.autoconfigure.DruidDataSourceBuilder;
import org.apache.ibatis.session.SqlSessionFactory;
import org.mybatis.spring.annotation.MapperScan;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Primary;
import sunwul.datasource.common.FactoryUtil_manyMybatis;

import javax.sql.DataSource;

/*****
 * @author sunwul
 * @date 2022/2/25 11:27
 * @description
 */
@Configuration
@MapperScan(basePackages = "sunwul.datasource.mapper.write", sqlSessionFactoryRef = "sqlSessionFactory_w")
public class Write_MybatisConfig_manyMybatis {

    /**
     * 写库的数据源
     *
     * @return DataSource
     */
    @Bean("dataSource_write")
    @ConfigurationProperties(prefix = "spring.datasource.datasource-write")
    public DataSource dataSource_write() {
        System.out.println("==============读取配置信息,创建DataSource=============");
        // 通过绑定全局配置,拿到属性后自动创建一个DruidDataSource
        return DruidDataSourceBuilder.create().build();
    }

    /**
     * 配置写库的 SqlSessionFactory
     *
     * @return SqlSessionFactory
     * @throws Exception ex
     */
    @Bean
    @Primary
    public SqlSessionFactory sqlSessionFactory_w() throws Exception {
        return FactoryUtil_manyMybatis.createFactory(dataSource_write(), "classpath:mapper/write/*Mapper_manyMybatis.xml", true);
    }

}
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import sunwul.datasource.entity.TAdmin_manyMybatis;

import java.util.List;
import java.util.stream.Collectors;

/*****
 * @author sunwul
 * @date 2022/2/28 9:33
 * @description
 */
@RestController
public class TestController_manyMybatis {

    @Autowired
//    @Qualifier("TAdminMapper_R")
    sunwul.datasource.mapper.read.TAdminMapper_manyMybatis mapper_r;

    @Autowired
//    @Qualifier("TAdminMapper_W")
    sunwul.datasource.mapper.write.TAdminMapper_manyMybatis mapper_w;

    @RequestMapping("/sel")
    public List<TAdmin_manyMybatis> toSel(){
        return mapper_r.sel_admin();
    }

    @RequestMapping("/selOne/{id}")
    public TAdmin_manyMybatis toSelOne(@PathVariable("id") Integer id){
        return mapper_r.sel_admin().stream().filter(s -> s.getId() == id).distinct().collect(Collectors.toList()).get(0);
    }

    @RequestMapping("/add/{name}")
    public Integer toAdd(@PathVariable("name") String name){
        return mapper_w.add_admin(name);
    }
}

三,多数据源事务控制

在多数据源的情况下, 由于设计到多个数据库的读写. 一旦发生异常就可能会导致数据不一致的情况 在这种情况希望使用事务进行回退, 但是Spring的声明式事务在一次请求线程中只能使用一个数据源进行控制

对于多源数据库来说:

  • 单一事务管理器(TransactionManager)无法切换数据源, 需要配置多个TransactionManager
  • @TransactionManager 是无法管理多个数据源的, 如果想真正实现多源数据库的事务控制, 肯定需要分布式事务.

以下仅仅只是多数据源事务控制的一种变通方式

Spring编程式事务

编程式事务可以在一个方法中对不同的数据源进行事务控制,当存在异常时,对所有数据库的操作都会回滚

import com.alibaba.druid.spring.boot.autoconfigure.DruidDataSourceBuilder;
import org.apache.ibatis.session.SqlSessionFactory;
import org.mybatis.spring.annotation.MapperScan;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.jdbc.datasource.DataSourceTransactionManager;
import org.springframework.transaction.support.TransactionTemplate;
import sunwul.datasource.common.FactoryUtil_springTran;

import javax.sql.DataSource;

/*****
 * @author sunwul
 * @date 2022/2/25 9:22
 * @description
 * * 指定扫描的包
 * * 指定使用的sqlSessionFactory
 */
@Configuration
@MapperScan(basePackages = "sunwul.datasource.mapper.read", sqlSessionFactoryRef = "sqlSessionFactory_r")
public class Read_MybatisConfig_springTran {

    /**
     * 读库的数据源
     *
     * @return DataSource
     */
    @Bean("dataSource_read")
    @ConfigurationProperties(prefix = "spring.datasource.datasource-read")
    public DataSource dataSource_read() {
        System.out.println("==============读取配置信息,创建DataSource=============");
        // 通过绑定全局配置,拿到属性后自动创建一个DruidDataSource
        return DruidDataSourceBuilder.create().build();
    }

    /**
     * 配置读库的 SqlSessionFactory
     *
     * @return SqlSessionFactory
     * @throws Exception ex
     */
    @Bean
    public SqlSessionFactory sqlSessionFactory_r() throws Exception {
        return FactoryUtil_springTran.createFactory(dataSource_read(), "classpath:mapper/read/*Mapper_springTran.xml", true);
    }

    /**
     * 读库的事务管理
     *
     * @return DataSourceTransactionManager
     */
    @Bean
    public DataSourceTransactionManager dataSourceTransactionManager_r() {
        DataSourceTransactionManager dataSourceTransactionManager = new DataSourceTransactionManager();
        dataSourceTransactionManager.setDataSource(dataSource_read());
        return dataSourceTransactionManager;
    }

    /**
     * 事务模板  -  Spring编程式事务
     *
     * @return TransactionTemplate
     */
    @Bean
    public TransactionTemplate transactionTemplate_r() {
        return new TransactionTemplate(dataSourceTransactionManager_r());
    }

}
import com.alibaba.druid.spring.boot.autoconfigure.DruidDataSourceBuilder;
import org.apache.ibatis.session.SqlSessionFactory;
import org.mybatis.spring.annotation.MapperScan;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Primary;
import org.springframework.jdbc.datasource.DataSourceTransactionManager;
import org.springframework.transaction.support.TransactionTemplate;
import sunwul.datasource.common.FactoryUtil_springTran;

import javax.sql.DataSource;

/*****
 * @author sunwul
 * @date 2022/2/25 11:27
 * @description
 * * 指定扫描的包
 * * 指定使用的sqlSessionFactory
 */
@Configuration
@MapperScan(basePackages = "sunwul.datasource.mapper.write", sqlSessionFactoryRef = "sqlSessionFactory_w")
public class Write_MybatisConfig_springTran {

    /**
     * 写库数据源
     *
     * @return DataSource
     */
    @Bean("dataSource_write")
    @ConfigurationProperties(prefix = "spring.datasource.datasource-write")
    public DataSource dataSource_write() {
        System.out.println("==============读取配置信息,创建DataSource=============");
        // 通过绑定全局配置,拿到属性后自动创建一个DruidDataSource
        return DruidDataSourceBuilder.create().build();
    }

    /**
     * 配置写库的 SqlSessionFactory
     * 整合
     *
     * @return SqlSessionFactory
     * @throws Exception ex
     */
    @Bean
    @Primary
    public SqlSessionFactory sqlSessionFactory_w() throws Exception {
        return FactoryUtil_springTran.createFactory(dataSource_write(), "classpath:mapper/write/*Mapper_springTran.xml", true);
    }

    /**
     * 写库的事务管理
     *
     * @return DataSourceTransactionManager
     */
    @Bean
    @Primary
    public DataSourceTransactionManager dataSourceTransactionManager_w() {
        DataSourceTransactionManager dataSourceTransactionManager = new DataSourceTransactionManager();
        dataSourceTransactionManager.setDataSource(dataSource_write());
        return dataSourceTransactionManager;
    }

    /**
     * 事务模板  -  Spring编程式事务
     *
     * @return TransactionTemplate
     */
    @Bean
    public TransactionTemplate transactionTemplate_w() {
        return new TransactionTemplate(dataSourceTransactionManager_w());
    }

}
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.transaction.support.TransactionTemplate;
import sunwul.datasource.entity.TAdmin_springTran;
import sunwul.datasource.service.TAdminService_springTran;

import java.util.List;

/**
 * @author sunwul
 * @date 2022/3/10 22:25:46
 * @description
 */
@Service
public class TAdminServiceImpl_springTran implements TAdminService_springTran {

    @Autowired
    sunwul.datasource.mapper.read.TAdminMapper_springTran TAdminMapper_R;

    @Autowired
    sunwul.datasource.mapper.write.TAdminMapper_springTran TAdminMapper_W;

    @Autowired
    TransactionTemplate transactionTemplate_w;

    @Autowired
    TransactionTemplate transactionTemplate_r;

    @Override
    public List<TAdmin_springTran> sel_admin() {
        return TAdminMapper_R.sel_admin();
    }

    /**
     * 添加数据, 制造异常,触发事务
     * 由于此时写库的事务管理添加了@Primary注解, 因此此时只有写库的事务会生效
     * 此时会因为数据库操作的顺序出现以下几种情况:
     * 1. 当写库操作或读库操作位于异常代码之前时:  读库正常插入数据, 写库回滚操作
     * 2. 当读库操作位于异常代码之前,写库操作位于异常代码之后时:  读库正常插入数据, 不会执行到写库操作, 直接抛出异常
     * 3. 当写库操作位于异常代码之前,读库操作位于异常代码之后时:  写库回滚操作, 不会执行到读库操作, 直接抛出异常
     * 综上:
     * 开启了对应数据库的事务管理后, 当出现异常情况时, 会回滚对应的数据库操作
     */
    @Transactional
//    @Override
    public Integer add_admin_bak(String name) {
        TAdminMapper_W.add_admin(name);
        TAdminMapper_R.add_admin(name);
        int a = 1 / 0;
        return a;
    }

    /**
     * 通过Spring编程式事务对不同数据源进行事务控制
     */
    @Override
    public Integer add_admin(String name) {
        transactionTemplate_w.execute((status_w) -> {
            transactionTemplate_r.execute((status_r) -> {
                try {
                    TAdminMapper_W.add_admin(name);
                    TAdminMapper_R.add_admin(name);
                    int a = 1 / 0; // 制造异常,触发事务
                } catch (Exception e) {
                    e.printStackTrace();
                    status_w.setRollbackOnly(); // 调用写库的回滚
                    status_r.setRollbackOnly(); // 调用读库的回滚
                    return false;
                }
                return true;
            });
            return true;
        });
        return 999;
    }
}

Spring声明式事务

声明式事务通过注解来进行事务控制,但由于@Transactional注解无法同时使用多个,因此只能通过Spring的代理对象来解决这个问题

声明式事务

四,dynamic-datasource多数据源组件

...等待探索

空文件

简介

关于动态数据源的不同实现方式, 以及多数据源事务管理相关的实现 展开 收起
取消

发行版

暂无发行版

贡献者

全部

近期动态

加载更多
不能加载更多了
1
https://gitee.com/sunwul/dynamic-datasource.git
git@gitee.com:sunwul/dynamic-datasource.git
sunwul
dynamic-datasource
dynamic-datasource
master

搜索帮助