Spring基于MyBatis实现多数据源

目录

多数据源实现

yml配置文件

配置类

业务代码

案例演示

 多数据源事务控制

第一种方式

第二种方式

第三种方式


多数据源实现

yml配置文件
spring:
  datasource:
    type: com.alibaba.druid.pool.DruidDataSource
    datasource1:
      url: jdbc:mysql://127.0.0.1:3306/datasource1?serverTimezone=UTC&useUnicode=true&characterEncoding=UTF8&useSSL=false
      username: root
      password: 123456
      initial-size: 1
      min-idle: 1
      max-active: 20
      test-on-borrow: true
      driver-class-name: com.mysql.cj.jdbc.Driver
    datasource2:
      url: jdbc:mysql://127.0.0.1:3306/datasource2?serverTimezone=UTC&useUnicode=true&characterEncoding=UTF8&useSSL=false
      username: root
      password: 123456
      initial-size: 1
      min-idle: 1
      max-active: 20
      test-on-borrow: true
      driver-class-name: com.mysql.cj.jdbc.Driver
server:
  port: 8081
配置类
@Configuration
// 集成mybatis:
// 1. 指定扫描的mapper接口包(主库)
// 2. 指定使用sqlSessionFactory是哪个(主库)
@MapperScan(basePackages = "com.datasource.dynamic.mybatis.mapper.r",
        sqlSessionFactoryRef="rSqlSessionFactory")
public class RMyBatisConfig {
    @Bean
    @ConfigurationProperties(prefix = "spring.datasource.datasource2")
    public DataSource dataSource2() {
        // 底层会自动拿到spring.datasource中的配置, 创建一个DruidDataSource
        return DruidDataSourceBuilder.create().build();
    }

    @Bean
    @Primary
    public SqlSessionFactory rSqlSessionFactory()
            throws Exception {
        final SqlSessionFactoryBean sessionFactory = new SqlSessionFactoryBean();
        // 指定主库
        sessionFactory.setDataSource(dataSource2());
        // 指定主库对应的mapper.xml文件
        /*sessionFactory.setMapperLocations(new PathMatchingResourcePatternResolver()
                .getResources("classpath:mapper/r/*.xml"));*/
        return sessionFactory.getObject();
    }
}
@Configuration
// 集成mybatis:
// 1. 指定扫描的mapper接口包(主库)
// 2. 指定使用sqlSessionFactory是哪个(主库)
@MapperScan(basePackages = "com.datasource.dynamic.mybatis.mapper.w",
        sqlSessionFactoryRef="wSqlSessionFactory")
public class WMyBatisConfig {
    @Bean
    @ConfigurationProperties(prefix = "spring.datasource.datasource1")
    public DataSource dataSource1() {
        // 底层会自动拿到spring.datasource中的配置, 创建一个DruidDataSource
        return DruidDataSourceBuilder.create().build();
    }

    @Bean
    @Primary
    public SqlSessionFactory wSqlSessionFactory()
            throws Exception {
        final SqlSessionFactoryBean sessionFactory = new SqlSessionFactoryBean();
        // 指定主库
        sessionFactory.setDataSource(dataSource1());
        // 指定主库对应的mapper.xml文件
        /*sessionFactory.setMapperLocations(new PathMatchingResourcePatternResolver()
                .getResources("classpath:mapper/w/*.xml"));*/
        return sessionFactory.getObject();
    }
}

       理论上来讲,通过mybatis实现多数据源,就是创建出多套mybatis的数据库配置,上述通过两个文件配置了两个不同的数据源。每个数据源负责的mapper文件也是不同的,具体如下:

Spring基于MyBatis实现多数据源_第1张图片

public interface RFriendMapper {
    @Select("SELECT * FROM Frend")
    List list();

    @Insert("INSERT INTO  frend(`name`) VALUES (#{name})")
    void save(Friend frend);
}
public interface WFriendMapper {
    @Select("SELECT * FROM friend")
    List list();

    @Insert("INSERT INTO  friend(`name`) VALUES (#{name})")
    void save(Friend friend);
}
业务代码

控制层

@RestController
@RequestMapping("friend")
@Slf4j
public class FriendController {

    @Autowired
    private FriendService friendService;

    @GetMapping(value = "select")
    public List select(){
        return friendService.list();
    }

    @GetMapping(value = "insert")
    public void insert(){
        Friend friend = new Friend();
        friend.setName("张三");
        friendService.save(friend);
    }
}

服务层代码

@Service
public class FriendImplService implements FriendService {

    @Autowired
    private RFriendMapper rFrendMapper;

    @Autowired
    private WFriendMapper wFrendMapper;

    // 读-- 读库
    @Override
    public List list() {
        return rFrendMapper.list();
    }

    // 保存-- 写库
    @Override
    public void save(Friend frend) {
        wFrendMapper.save(frend);
    }
}

dao层代码上边已经贴过了

案例演示

执行insert方法,datasource1中的friend表新增了数据

Spring基于MyBatis实现多数据源_第2张图片

执行select方法,查询datasource2中的friend表为空

Spring基于MyBatis实现多数据源_第3张图片

 多数据源事务控制

      对于多数据源情况,单一的事务管理器是无法切换的,@Transactionnal是无法管理多个数据源的,需要配置多个事务管理器TransactionManager。

第一种方式

首先启动类开启事务支持

@SpringBootApplication
@EnableAspectJAutoProxy(exposeProxy = true)
@EnableTransactionManagement  // 开启事务
public class DynamicMybatisApplication {

    public static void main(String[] args) {
        SpringApplication.run(DynamicMybatisApplication.class, args);
    }
}

给之前的两个数据源都加上对应的事务管理器

    // datasource1的事务管理器
    @Bean
    @Primary
    public DataSourceTransactionManager wTransactionManager(){
        DataSourceTransactionManager dataSourceTransactionManager = new DataSourceTransactionManager();
        dataSourceTransactionManager.setDataSource(dataSource1());
        return dataSourceTransactionManager;
    }


    @Bean
    public TransactionTemplate wTransactionTemplate(){
        return new TransactionTemplate(wTransactionManager());
    }
    // datasource2的事务管理器
    @Bean
    public DataSourceTransactionManager rTransactionManager(){
        DataSourceTransactionManager dataSourceTransactionManager = new DataSourceTransactionManager();
        dataSourceTransactionManager.setDataSource(dataSource2());
        return dataSourceTransactionManager;
    }

    @Bean
    public TransactionTemplate rTransactionTemplate(){
        return new TransactionTemplate(rTransactionManager());
    }

控制层代码

    @GetMapping(value = "save")
    public void save() throws Exception {
        Friend friend = new Friend();
        friend.setName("张三");
        friendService.saveAll(friend);
    }

服务器层代码

    @Autowired
    TransactionTemplate wTransactionTemplate;
    @Autowired
    TransactionTemplate rTransactionTemplate;

    public void saveAll(Friend frend) {
        wTransactionTemplate.execute((wstatus) -> {
            rTransactionTemplate.execute((rstatus) -> {
                try {
                    saveW(frend);
                    saveR(frend);
                    //int a = 1 / 0;
                } catch (Exception e) {
                    e.printStackTrace();
                    wstatus.setRollbackOnly();
                    rstatus.setRollbackOnly();
                    return false;
                }
                return true;
            });
            return true;
        });
    }

以上方法通过编程式事务完成事务一致性。


第二种方式
    @Transactional(transactionManager = "wTransactionManager")
    public void saveAll(Friend frend) throws Exception {
        FriendService frendService = (FriendService) AopContext.currentProxy();
        frendService.saveAllR(frend);
    }

    @Transactional(transactionManager = "rTransactionManager")
    public void saveAllR(Friend frend) {
        saveW(frend);
        saveR(frend);
        int a = 1 / 0;
    }

此种方式需要在启动类上增加注解

@EnableAspectJAutoProxy(exposeProxy = true)

通过上述这种方式,读库写库的异常都会回滚。

第三种方式

       通过新增注解的方式,在业务方法上将相关事务管理器写入,在执行方法时通过aop扫描出相关的事务管理器,然后代理执行具体业务方法,当出现异常时,通过循环事务管理器来回滚事务。
具体代码如下:

自定义注解及aop

@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface MultiTransactional {

    String[] value() default {};
}
@Aspect
@Component
public class MultiTransactionAop {

    private final ComboTransaction comboTransaction;

    @Autowired
    public MultiTransactionAop(ComboTransaction comboTransaction) {
        this.comboTransaction = comboTransaction;
    }

    @Pointcut("within(com.tuling.datasource.dynamic.mybatis.service.impl.*)")
    public void pointCut() {
    }

    @Around("pointCut() && @annotation(multiTransactional)")
    public Object inMultiTransactions(ProceedingJoinPoint pjp, MultiTransactional multiTransactional) {
        return comboTransaction.inCombinedTx(() -> {
            try {
                return pjp.proceed();       //执行目标方法
            } catch (Throwable throwable) {
                if (throwable instanceof RuntimeException) {
                    throw (RuntimeException) throwable;
                }
                throw new RuntimeException(throwable);
            }
        }, multiTransactional.value());
    }
}

 aop方法中用到相关类如下

@Component
public class ComboTransaction {

    @Autowired
    private Db1TxBroker db1TxBroker;

    @Autowired
    private Db2TxBroker db2TxBroker;

    public  V inCombinedTx(Callable callable, String[] transactions) {
        if (callable == null) {
            return null;
        }
        // 相当于循环 [wTransactionManager,wTransactionManager]
        Callable combined = Stream.of(transactions)
                .filter(ele -> !StringUtils.isEmpty(ele))
                .distinct()
                .reduce(callable, (r, tx) -> {
                    switch (tx) {
                        case DbTxConstants.DB1_TX:
                            return () -> db1TxBroker.inTransaction(r);
                        case DbTxConstants.DB2_TX:
                            return () -> db2TxBroker.inTransaction(r);
                        default:
                            return null;
                    }
                }, (r1, r2) -> r2);

        try {
            return combined.call();
        } catch (RuntimeException e) {
            throw e;
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }
}
@Component
public class Db1TxBroker {

    @Transactional(DbTxConstants.DB1_TX)
    public  V inTransaction(Callable callable) {
        try {
            return callable.call();
        } catch (RuntimeException e) {
            throw e;
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }
}
@Component
public class Db2TxBroker {

    @Transactional(DbTxConstants.DB2_TX)
    public  V inTransaction(Callable callable) {
        try {
            return callable.call();
        } catch (RuntimeException e) {
            throw e;
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }
}
public class DbTxConstants {

    public static final String DB1_TX = "wTransactionManager";

    public static final String DB2_TX = "rTransactionManager";
}

业务代码如下

控制层代码

    @GetMapping(value = "save")
    public void save() throws Exception {
        Friend friend = new Friend();
        friend.setName("张三");
        friendService.saveAll(friend);
    }

服务层代码

    @Autowired
    private RFriendMapper rFrendMapper;

    @Autowired
    private WFriendMapper wFrendMapper;

    @Override
    @MultiTransactional(value = {DbTxConstants.DB1_TX, DbTxConstants.DB2_TX})
    public void saveAll(Friend frend) {
        saveW(frend);
        saveR(frend);
        int a = 1 / 0;
    }

    // 保存-- 写库
    @Override
    public void saveW(Friend frend) {
        frend.setName("张三");
        wFrendMapper.save(frend);
    }

    // 保存-- 读库
    @Override
    public void saveR(Friend frend) {
        frend.setName("张三");
        rFrendMapper.save(frend);
    }

案例演示

先清空数据库,然后执行save方法后,报错如下

Spring基于MyBatis实现多数据源_第4张图片

 数据库friend表为空,事务回滚成功

Spring基于MyBatis实现多数据源_第5张图片

Spring基于MyBatis实现多数据源_第6张图片

你可能感兴趣的:(分布式中间件,spring,mybatis,java,数据库,后端)