SpringBoot实现多数据源(五)【多数据源事务控制】

上一篇文章SpringBoot实现多数据源(四)【集成多个 Mybatis 框架】

五、多数据源事务控制


在多数据源下,由于涉及到数据库的多个读写。一旦发生异常可能会导致数据不一致的情况,在这种情况希望使用事务进行回退

但是 Spring 的声明式事务在一次请求线程中只能使用一个数据源进行控制

对于多源数据库来讲:

  1. 单一事务管理器(TransactionManager)无法切换数据源,需要配置多个 TransactionManager
  2. @Transaction 是无法管理多个数据源的。如果想真正实现多源数据库事务的控制,肯定需要分布式事务。这里讲解多源数据库事务控制的一种变通方式

一个方法开启2个事务

1)编程式事务

  1. 修改读写Mybatis的配置类,为其添加事务管理者以及由Spring提供的事务模板
  • RMybatisConfiguration
package com.vinjcent.config.mybatis;

import com.alibaba.druid.spring.boot.autoconfigure.DruidDataSourceBuilder;
import org.apache.ibatis.logging.stdout.StdOutImpl;
import org.apache.ibatis.session.SqlSessionFactory;
import org.mybatis.spring.SqlSessionFactoryBean;
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 org.springframework.core.io.support.PathMatchingResourcePatternResolver;
import org.springframework.jdbc.datasource.DataSourceTransactionManager;
import org.springframework.transaction.support.TransactionTemplate;

import javax.sql.DataSource;


/**
 * 读数据源配置
 * 1. 指定扫描mapper接口包
 * 2. 指定使用rSqlSessionFactory是哪个
 */
@Configuration
@MapperScan(basePackages = "com.vinjcent.mapper.read", sqlSessionFactoryRef = "rSqlSessionFactory")
public class RMybatisConfiguration {
    
    


    // readDataSource(读数据源)
    @Bean(name = "readDatasource")
    @ConfigurationProperties(prefix = "spring.datasource.read")
    public DataSource readDatasource() {
    
    
        // 底层会自动拿到spring.datasource中的配置,创建一个DruidDataSource
        return DruidDataSourceBuilder.create().build();
    }

    // SqlSessionFactory(ibatis会话工厂)
    @Bean
    @Primary
    public SqlSessionFactory rSqlSessionFactory(@Qualifier("readDatasource") DataSource dataSource) throws Exception {
    
    
        final SqlSessionFactoryBean sqlSessionFactory = new SqlSessionFactoryBean();
        sqlSessionFactory.setDataSource(dataSource);
        sqlSessionFactory.setMapperLocations(new PathMatchingResourcePatternResolver()
                .getResources("classpath:com/vinjcent/mapper/read/*.xml"));
        /*主库设置sql控制台打印*/
        org.apache.ibatis.session.Configuration configuration = new org.apache.ibatis.session.Configuration();
        configuration.setLogImpl(StdOutImpl.class);
        sqlSessionFactory.setConfiguration(configuration);
        sqlSessionFactory.setTypeAliasesPackage("com.vinjcent.pojo");
        return sqlSessionFactory.getObject();
    }

    // TransactionManager(事务管理者)
    @Bean
    public DataSourceTransactionManager rTransactionManager() {
    
    
        DataSourceTransactionManager transactionManager = new DataSourceTransactionManager();
        transactionManager.setDataSource(readDatasource());
        return transactionManager;
    }

    // TransactionTemplate(事务模板)
    @Bean
    public TransactionTemplate rTransactionTemplate() {
    
    
        return new TransactionTemplate(rTransactionManager());
    }
}
  • WMybatisConfiguration
package com.vinjcent.config.mybatis;

import com.alibaba.druid.spring.boot.autoconfigure.DruidDataSourceBuilder;
import org.apache.ibatis.logging.stdout.StdOutImpl;
import org.apache.ibatis.session.SqlSessionFactory;
import org.mybatis.spring.SqlSessionFactoryBean;
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 org.springframework.core.io.support.PathMatchingResourcePatternResolver;
import org.springframework.jdbc.datasource.DataSourceTransactionManager;
import org.springframework.transaction.support.TransactionTemplate;

import javax.sql.DataSource;


/**
 * 写数据源配置
 * 1. 指定扫描mapper接口包
 * 2. 指定使用wSqlSessionFactory是哪个
 */
@Configuration
@MapperScan(basePackages = "com.vinjcent.mapper.write", sqlSessionFactoryRef = "wSqlSessionFactory")
public class WMybatisConfiguration {
    
    


    // writeDataSource(写数据源)
    @Bean(name = "writeDatasource")
    @ConfigurationProperties(prefix = "spring.datasource.write")
    public DataSource writeDatasource() {
    
    
        // 底层会自动拿到spring.datasource中的配置,创建一个DruidDataSource
        return DruidDataSourceBuilder.create().build();
    }

    // SqlSessionFactory(ibatis会话工厂)
    @Bean
    @Primary
    public SqlSessionFactory wSqlSessionFactory(@Qualifier("writeDatasource") DataSource dataSource) throws Exception {
    
    
        final SqlSessionFactoryBean sqlSessionFactory = new SqlSessionFactoryBean();
        sqlSessionFactory.setDataSource(dataSource);
        sqlSessionFactory.setMapperLocations(new PathMatchingResourcePatternResolver()
                .getResources("classpath:com/vinjcent/mapper/write/*.xml"));
        /* 主库设置sql控制台打印 */
        org.apache.ibatis.session.Configuration configuration = new org.apache.ibatis.session.Configuration();
        configuration.setLogImpl(StdOutImpl.class);
        sqlSessionFactory.setConfiguration(configuration);
        sqlSessionFactory.setTypeAliasesPackage("com.vinjcent.pojo");
        return sqlSessionFactory.getObject();
    }

    // TransactionManager(事务管理者)
    @Bean
    @Primary
    public DataSourceTransactionManager wTransactionManager() {
    
    
        DataSourceTransactionManager transactionManager = new DataSourceTransactionManager();
        transactionManager.setDataSource(writeDatasource());
        return transactionManager;
    }

    // TransactionTemplate(事务模板)
    @Bean
    public TransactionTemplate wTransactionTemplate() {
    
    
        return new TransactionTemplate(wTransactionManager());
    }
}
  1. 修改Service层
    • PeopleServiceImpl
package com.vinjcent.service.impl;

import com.vinjcent.mapper.read.RPeopleMapper;
import com.vinjcent.mapper.write.WPeopleMapper;
import com.vinjcent.pojo.People;
import com.vinjcent.service.PeopleService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.support.TransactionTemplate;

import java.util.List;

@Service
public class PeopleServiceImpl implements PeopleService {
    
    


    // 读mapper
    private final RPeopleMapper rPeopleMapper;

    // 写mapper
    private final WPeopleMapper wPeopleMapper;

    // 读事务模板
    private final TransactionTemplate rTransactionTemplate;

    // 写事务模板
    private final TransactionTemplate wTransactionTemplate;

    @Autowired
    public PeopleServiceImpl(RPeopleMapper rPeopleMapper, WPeopleMapper wPeopleMapper, TransactionTemplate rTransactionTemplate, TransactionTemplate wTransactionTemplate) {
    
    
        this.rPeopleMapper = rPeopleMapper;
        this.wPeopleMapper = wPeopleMapper;
        this.rTransactionTemplate = rTransactionTemplate;
        this.wTransactionTemplate = wTransactionTemplate;
    }


    @Override
    public List<People> list() {
    
    
        return rPeopleMapper.list();
    }

    @Override
    public boolean save(People people) {
    
    
        return wPeopleMapper.save(people);
    }


    // 从库保存
    public boolean rSave(People people) {
    
    
        return rPeopleMapper.save(people);
    }

    // 主库保存
    public boolean wSave(People people) {
    
    
        return wPeopleMapper.save(people);
    }

    // 主从库保存
    @Override
    public void saveAll(People people) {
    
    
        // 写事务模板
        wTransactionTemplate.execute(wStatus -> {
    
    
            // 读事务模板
            rTransactionTemplate.execute(rStatus -> {
    
    
                try {
    
    
                    rSave(people);
                    wSave(people);
                    // 模拟异常
                    int a = 1 /0;
                } catch (Exception e) {
    
    
                    e.printStackTrace();
                    // 出现异常回滚"写"事务
                    wStatus.setRollbackOnly();
                    // 出现异常回滚"读"事务
                    rStatus.setRollbackOnly();
                    return false;
                }
                return true;
            });
            return true;
        });

    }


}
  1. 在Controller当中添加接口并测试
    • PeopleController
package com.vinjcent.controller;

import com.vinjcent.pojo.People;
import com.vinjcent.service.PeopleService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import java.util.List;

@RestController
@RequestMapping("people")
public class PeopleController {
    
    


    private final PeopleService peopleService;

    @Autowired
    public PeopleController(PeopleService peopleService) {
    
    
        this.peopleService = peopleService;
    }


    @GetMapping("/list")
    public List<People> getAllPeople() {
    
    
        //...
    }

    @GetMapping("/insert")
    public String addPeople() {
    
    
        //...
    }
	
    // 添加位置
    @GetMapping("/save")
    public String addPeopleForWriteAndRead() {
    
    
        peopleService.saveAll(new People("ReadAndWrite"));
        return "读写库添加成功~";
    }

}
  1. 运行并测试接口

2)声明式事务

修改Service层

package com.vinjcent.service.impl;

import com.vinjcent.mapper.read.RPeopleMapper;
import com.vinjcent.mapper.write.WPeopleMapper;
import com.vinjcent.pojo.People;
import com.vinjcent.service.PeopleService;
import org.springframework.aop.framework.AopContext;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.transaction.support.TransactionTemplate;

import java.util.List;

@Service
public class PeopleServiceImpl implements PeopleService {
    
    


    // 读mapper
    private final RPeopleMapper rPeopleMapper;

    // 写mapper
    private final WPeopleMapper wPeopleMapper;

    // 读事务模板
    private final TransactionTemplate rTransactionTemplate;

    // 写事务模板
    private final TransactionTemplate wTransactionTemplate;

    @Autowired
    public PeopleServiceImpl(RPeopleMapper rPeopleMapper, WPeopleMapper wPeopleMapper, TransactionTemplate rTransactionTemplate, TransactionTemplate wTransactionTemplate) {
    
    
        this.rPeopleMapper = rPeopleMapper;
        this.wPeopleMapper = wPeopleMapper;
        this.rTransactionTemplate = rTransactionTemplate;
        this.wTransactionTemplate = wTransactionTemplate;
    }


    @Override
    public List<People> list() {
    
    
        return rPeopleMapper.list();
    }

    @Override
    public boolean save(People people) {
    
    
        return wPeopleMapper.save(people);
    }


    // 从库保存
    public boolean rSave(People people) {
    
    
        return rPeopleMapper.save(people);
    }

    // 主库保存
    public boolean wSave(People people) {
    
    
        return wPeopleMapper.save(people);
    }

    // 主从库保存
    @Transactional("wTransactional")
    @Override
    public void saveAll(People people) {
    
    
        // 获取当前的service代理类对象,需要在主启动类开启@EnableAspectJAutoProxy(exposeProxy = true),暴露代理对象
        PeopleService peopleService = (PeopleService) AopContext.currentProxy();
        peopleService.saveAllR(people);
    }
    
    
    @Transactional(value = "rTransactional")
    @Override
    public void saveAllR(People people) {
    
    
        wSave(people);
        rSave(people);
        int a = 1 / 0;
    }
}

下一篇文章SpringBoot实现多数据源(六)【dynamic-datasource 多数据源组件】

猜你喜欢

转载自blog.csdn.net/Wei_Naijia/article/details/128069840
今日推荐