SpringBoot实现多数据源(五)【多数据源事务控制】
admin
2024-02-21 13:43:45

上一篇文章《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中的配置,创建一个DruidDataSourcereturn DruidDataSourceBuilder.create().build();}// SqlSessionFactory(ibatis会话工厂)@Bean@Primarypublic 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(事务管理者)@Beanpublic DataSourceTransactionManager rTransactionManager() {DataSourceTransactionManager transactionManager = new DataSourceTransactionManager();transactionManager.setDataSource(readDatasource());return transactionManager;}// TransactionTemplate(事务模板)@Beanpublic 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中的配置,创建一个DruidDataSourcereturn DruidDataSourceBuilder.create().build();}// SqlSessionFactory(ibatis会话工厂)@Bean@Primarypublic 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@Primarypublic DataSourceTransactionManager wTransactionManager() {DataSourceTransactionManager transactionManager = new DataSourceTransactionManager();transactionManager.setDataSource(writeDatasource());return transactionManager;}// TransactionTemplate(事务模板)@Beanpublic 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 {// 读mapperprivate final RPeopleMapper rPeopleMapper;// 写mapperprivate final WPeopleMapper wPeopleMapper;// 读事务模板private final TransactionTemplate rTransactionTemplate;// 写事务模板private final TransactionTemplate wTransactionTemplate;@Autowiredpublic PeopleServiceImpl(RPeopleMapper rPeopleMapper, WPeopleMapper wPeopleMapper, TransactionTemplate rTransactionTemplate, TransactionTemplate wTransactionTemplate) {this.rPeopleMapper = rPeopleMapper;this.wPeopleMapper = wPeopleMapper;this.rTransactionTemplate = rTransactionTemplate;this.wTransactionTemplate = wTransactionTemplate;}@Overridepublic List list() {return rPeopleMapper.list();}@Overridepublic 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);}// 主从库保存@Overridepublic 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;@Autowiredpublic PeopleController(PeopleService peopleService) {this.peopleService = peopleService;}@GetMapping("/list")public List 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 {// 读mapperprivate final RPeopleMapper rPeopleMapper;// 写mapperprivate final WPeopleMapper wPeopleMapper;// 读事务模板private final TransactionTemplate rTransactionTemplate;// 写事务模板private final TransactionTemplate wTransactionTemplate;@Autowiredpublic PeopleServiceImpl(RPeopleMapper rPeopleMapper, WPeopleMapper wPeopleMapper, TransactionTemplate rTransactionTemplate, TransactionTemplate wTransactionTemplate) {this.rPeopleMapper = rPeopleMapper;this.wPeopleMapper = wPeopleMapper;this.rTransactionTemplate = rTransactionTemplate;this.wTransactionTemplate = wTransactionTemplate;}@Overridepublic List list() {return rPeopleMapper.list();}@Overridepublic 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")@Overridepublic void saveAll(People people) {// 获取当前的service代理类对象,需要在主启动类开启@EnableAspectJAutoProxy(exposeProxy = true),暴露代理对象PeopleService peopleService = (PeopleService) AopContext.currentProxy();peopleService.saveAllR(people);}@Transactional(value = "rTransactional")@Overridepublic void saveAllR(People people) {wSave(people);rSave(people);int a = 1 / 0;}
}

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

相关内容

热门资讯

最新或2023(历届)中国传统... 元宵节习俗元宵节是中国的传统节日,所以全国各地都过,大部分地区的习俗是差不多的,但各地也还是有自己的...
最新或2023(历届)小学推广... 为了进一步深化普通话的推广工作,大力做好推普宣传,营造氛围,促进推普工作向纵深发展,11月24日,优...