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 多数据源组件】

  • 3
    点赞
  • 20
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
可以通过以下步骤来实现Spring Boot Druid多数据源实现分布式事务: 1. 引入相关依赖 在pom.xml文件中引入以下依赖: ``` <dependency> <groupId>com.alibaba</groupId> <artifactId>druid-spring-boot-starter</artifactId> <version>1.1.10</version> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-jdbc</artifactId> </dependency> <dependency> <groupId>org.mybatis.spring.boot</groupId> <artifactId>mybatis-spring-boot-starter</artifactId> <version>2.1.3</version> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-transaction</artifactId> </dependency> <dependency> <groupId>org.springframework.retry</groupId> <artifactId>spring-retry</artifactId> <version>1.3.1</version> </dependency> ``` 2. 配置Druid数据源 在application.yml文件中配置Druid数据源,例如: ``` spring: datasource: druid: default: url: jdbc:mysql://localhost:3306/db1 username: root password: root driver-class-name: com.mysql.jdbc.Driver second: url: jdbc:mysql://localhost:3306/db2 username: root password: root driver-class-name: com.mysql.jdbc.Driver ``` 3. 配置MyBatis 在application.yml文件中配置MyBatis,例如: ``` mybatis: configuration: map-underscore-to-camel-case: true mapper-locations: classpath*:mapper/*.xml ``` 4. 配置分布式事务 使用Spring Boot事务管理器来管理分布式事务。在配置类中添加注解@EnableTransactionManagement来启用事务管理器,并使用注解@Transaction来标记需要进行事务管理的方法,例如: ``` @Configuration @EnableTransactionManagement public class MyBatisConfig { @Autowired private DataSource dataSource; @Bean public SqlSessionFactory sqlSessionFactory() throws Exception { SqlSessionFactoryBean sessionFactory = new SqlSessionFactoryBean(); sessionFactory.setDataSource(dataSource); sessionFactory.setMapperLocations(new PathMatchingResourcePatternResolver() .getResources("classpath*:mapper/*.xml")); return sessionFactory.getObject(); } @Bean public PlatformTransactionManager platformTransactionManager() { return new DataSourceTransactionManager(dataSource); } @Bean public RetryTemplate retryTemplate() { RetryTemplate retryTemplate = new RetryTemplate(); SimpleRetryPolicy retryPolicy = new SimpleRetryPolicy(); retryPolicy.setMaxAttempts(3); FixedBackOffPolicy backOffPolicy = new FixedBackOffPolicy(); backOffPolicy.setBackOffPeriod(1000L); retryTemplate.setRetryPolicy(retryPolicy); retryTemplate.setBackOffPolicy(backOffPolicy); return retryTemplate; } } ``` 以上是Spring Boot Druid多数据源实现分布式事务的步骤,希望能对你有所帮助!

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Naijia_OvO

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值