上一篇文章《SpringBoot实现多数据源(四)【集成多个 Mybatis 框架】》
五、多数据源事务控制
在多数据源下,由于涉及到数据库的多个读写。一旦发生异常可能会导致数据不一致的情况,在这种情况希望使用事务进行回退
但是 Spring 的声明式事务在一次请求线程中只能使用一个数据源进行控制
对于多源数据库来讲:
- 单一事务管理器(TransactionManager)无法切换数据源,需要配置多个 TransactionManager
- @Transaction 是无法管理多个数据源的。如果想真正实现多源数据库事务的控制,肯定需要分布式事务。这里讲解多源数据库事务控制的一种变通方式
一个方法开启2个事务
1)编程式事务
- 修改读写Mybatis的配置类,为其添加事务管理者以及由Spring提供的事务模板
- RMybatisConfiguration
packagecom.vinjcent.config.mybatis;importcom.alibaba.druid.spring.boot.autoconfigure.DruidDataSourceBuilder;importorg.apache.ibatis.logging.stdout.StdOutImpl;importorg.apache.ibatis.session.SqlSessionFactory;importorg.mybatis.spring.SqlSessionFactoryBean;importorg.mybatis.spring.annotation.MapperScan;importorg.springframework.beans.factory.annotation.Qualifier;importorg.springframework.boot.context.properties.ConfigurationProperties;importorg.springframework.context.annotation.Bean;importorg.springframework.context.annotation.Configuration;importorg.springframework.context.annotation.Primary;importorg.springframework.core.io.support.PathMatchingResourcePatternResolver;importorg.springframework.jdbc.datasource.DataSourceTransactionManager;importorg.springframework.transaction.support.TransactionTemplate;importjavax.sql.DataSource;/**
* 读数据源配置
* 1. 指定扫描mapper接口包
* 2. 指定使用rSqlSessionFactory是哪个
*/@Configuration@MapperScan(basePackages ="com.vinjcent.mapper.read", sqlSessionFactoryRef ="rSqlSessionFactory")publicclassRMybatisConfiguration{// readDataSource(读数据源)@Bean(name ="readDatasource")@ConfigurationProperties(prefix ="spring.datasource.read")publicDataSourcereadDatasource(){// 底层会自动拿到spring.datasource中的配置,创建一个DruidDataSourcereturnDruidDataSourceBuilder.create().build();}// SqlSessionFactory(ibatis会话工厂)@Bean@PrimarypublicSqlSessionFactoryrSqlSessionFactory(@Qualifier("readDatasource")DataSource dataSource)throwsException{finalSqlSessionFactoryBean sqlSessionFactory =newSqlSessionFactoryBean();
sqlSessionFactory.setDataSource(dataSource);
sqlSessionFactory.setMapperLocations(newPathMatchingResourcePatternResolver().getResources("classpath:com/vinjcent/mapper/read/*.xml"));/*主库设置sql控制台打印*/org.apache.ibatis.session.Configuration configuration =neworg.apache.ibatis.session.Configuration();
configuration.setLogImpl(StdOutImpl.class);
sqlSessionFactory.setConfiguration(configuration);
sqlSessionFactory.setTypeAliasesPackage("com.vinjcent.pojo");return sqlSessionFactory.getObject();}// TransactionManager(事务管理者)@BeanpublicDataSourceTransactionManagerrTransactionManager(){DataSourceTransactionManager transactionManager =newDataSourceTransactionManager();
transactionManager.setDataSource(readDatasource());return transactionManager;}// TransactionTemplate(事务模板)@BeanpublicTransactionTemplaterTransactionTemplate(){returnnewTransactionTemplate(rTransactionManager());}}
- WMybatisConfiguration
packagecom.vinjcent.config.mybatis;importcom.alibaba.druid.spring.boot.autoconfigure.DruidDataSourceBuilder;importorg.apache.ibatis.logging.stdout.StdOutImpl;importorg.apache.ibatis.session.SqlSessionFactory;importorg.mybatis.spring.SqlSessionFactoryBean;importorg.mybatis.spring.annotation.MapperScan;importorg.springframework.beans.factory.annotation.Qualifier;importorg.springframework.boot.context.properties.ConfigurationProperties;importorg.springframework.context.annotation.Bean;importorg.springframework.context.annotation.Configuration;importorg.springframework.context.annotation.Primary;importorg.springframework.core.io.support.PathMatchingResourcePatternResolver;importorg.springframework.jdbc.datasource.DataSourceTransactionManager;importorg.springframework.transaction.support.TransactionTemplate;importjavax.sql.DataSource;/**
* 写数据源配置
* 1. 指定扫描mapper接口包
* 2. 指定使用wSqlSessionFactory是哪个
*/@Configuration@MapperScan(basePackages ="com.vinjcent.mapper.write", sqlSessionFactoryRef ="wSqlSessionFactory")publicclassWMybatisConfiguration{// writeDataSource(写数据源)@Bean(name ="writeDatasource")@ConfigurationProperties(prefix ="spring.datasource.write")publicDataSourcewriteDatasource(){// 底层会自动拿到spring.datasource中的配置,创建一个DruidDataSourcereturnDruidDataSourceBuilder.create().build();}// SqlSessionFactory(ibatis会话工厂)@Bean@PrimarypublicSqlSessionFactorywSqlSessionFactory(@Qualifier("writeDatasource")DataSource dataSource)throwsException{finalSqlSessionFactoryBean sqlSessionFactory =newSqlSessionFactoryBean();
sqlSessionFactory.setDataSource(dataSource);
sqlSessionFactory.setMapperLocations(newPathMatchingResourcePatternResolver().getResources("classpath:com/vinjcent/mapper/write/*.xml"));/* 主库设置sql控制台打印 */org.apache.ibatis.session.Configuration configuration =neworg.apache.ibatis.session.Configuration();
configuration.setLogImpl(StdOutImpl.class);
sqlSessionFactory.setConfiguration(configuration);
sqlSessionFactory.setTypeAliasesPackage("com.vinjcent.pojo");return sqlSessionFactory.getObject();}// TransactionManager(事务管理者)@Bean@PrimarypublicDataSourceTransactionManagerwTransactionManager(){DataSourceTransactionManager transactionManager =newDataSourceTransactionManager();
transactionManager.setDataSource(writeDatasource());return transactionManager;}// TransactionTemplate(事务模板)@BeanpublicTransactionTemplatewTransactionTemplate(){returnnewTransactionTemplate(wTransactionManager());}}
- 修改Service层 - PeopleServiceImpl
packagecom.vinjcent.service.impl;importcom.vinjcent.mapper.read.RPeopleMapper;importcom.vinjcent.mapper.write.WPeopleMapper;importcom.vinjcent.pojo.People;importcom.vinjcent.service.PeopleService;importorg.springframework.beans.factory.annotation.Autowired;importorg.springframework.stereotype.Service;importorg.springframework.transaction.support.TransactionTemplate;importjava.util.List;@ServicepublicclassPeopleServiceImplimplementsPeopleService{// 读mapperprivatefinalRPeopleMapper rPeopleMapper;// 写mapperprivatefinalWPeopleMapper wPeopleMapper;// 读事务模板privatefinalTransactionTemplate rTransactionTemplate;// 写事务模板privatefinalTransactionTemplate wTransactionTemplate;@AutowiredpublicPeopleServiceImpl(RPeopleMapper rPeopleMapper,WPeopleMapper wPeopleMapper,TransactionTemplate rTransactionTemplate,TransactionTemplate wTransactionTemplate){this.rPeopleMapper = rPeopleMapper;this.wPeopleMapper = wPeopleMapper;this.rTransactionTemplate = rTransactionTemplate;this.wTransactionTemplate = wTransactionTemplate;}@OverridepublicList<People>list(){return rPeopleMapper.list();}@Overridepublicbooleansave(People people){return wPeopleMapper.save(people);}// 从库保存publicbooleanrSave(People people){return rPeopleMapper.save(people);}// 主库保存publicbooleanwSave(People people){return wPeopleMapper.save(people);}// 主从库保存@OverridepublicvoidsaveAll(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();returnfalse;}returntrue;});returntrue;});}}
- 在Controller当中添加接口并测试 - PeopleController
packagecom.vinjcent.controller;importcom.vinjcent.pojo.People;importcom.vinjcent.service.PeopleService;importorg.springframework.beans.factory.annotation.Autowired;importorg.springframework.web.bind.annotation.GetMapping;importorg.springframework.web.bind.annotation.RequestMapping;importorg.springframework.web.bind.annotation.RestController;importjava.util.List;@RestController@RequestMapping("people")publicclassPeopleController{privatefinalPeopleService peopleService;@AutowiredpublicPeopleController(PeopleService peopleService){this.peopleService = peopleService;}@GetMapping("/list")publicList<People>getAllPeople(){//...}@GetMapping("/insert")publicStringaddPeople(){//...}// 添加位置@GetMapping("/save")publicStringaddPeopleForWriteAndRead(){
peopleService.saveAll(newPeople("ReadAndWrite"));return"读写库添加成功~";}}
- 运行并测试接口
2)声明式事务
修改Service层
packagecom.vinjcent.service.impl;importcom.vinjcent.mapper.read.RPeopleMapper;importcom.vinjcent.mapper.write.WPeopleMapper;importcom.vinjcent.pojo.People;importcom.vinjcent.service.PeopleService;importorg.springframework.aop.framework.AopContext;importorg.springframework.beans.factory.annotation.Autowired;importorg.springframework.stereotype.Service;importorg.springframework.transaction.annotation.Propagation;importorg.springframework.transaction.annotation.Transactional;importorg.springframework.transaction.support.TransactionTemplate;importjava.util.List;@ServicepublicclassPeopleServiceImplimplementsPeopleService{// 读mapperprivatefinalRPeopleMapper rPeopleMapper;// 写mapperprivatefinalWPeopleMapper wPeopleMapper;// 读事务模板privatefinalTransactionTemplate rTransactionTemplate;// 写事务模板privatefinalTransactionTemplate wTransactionTemplate;@AutowiredpublicPeopleServiceImpl(RPeopleMapper rPeopleMapper,WPeopleMapper wPeopleMapper,TransactionTemplate rTransactionTemplate,TransactionTemplate wTransactionTemplate){this.rPeopleMapper = rPeopleMapper;this.wPeopleMapper = wPeopleMapper;this.rTransactionTemplate = rTransactionTemplate;this.wTransactionTemplate = wTransactionTemplate;}@OverridepublicList<People>list(){return rPeopleMapper.list();}@Overridepublicbooleansave(People people){return wPeopleMapper.save(people);}// 从库保存publicbooleanrSave(People people){return rPeopleMapper.save(people);}// 主库保存publicbooleanwSave(People people){return wPeopleMapper.save(people);}// 主从库保存@Transactional("wTransactional")@OverridepublicvoidsaveAll(People people){// 获取当前的service代理类对象,需要在主启动类开启@EnableAspectJAutoProxy(exposeProxy = true),暴露代理对象PeopleService peopleService =(PeopleService)AopContext.currentProxy();
peopleService.saveAllR(people);}@Transactional(value ="rTransactional")@OverridepublicvoidsaveAllR(People people){wSave(people);rSave(people);int a =1/0;}}
下一篇文章《SpringBoot实现多数据源(六)【dynamic-datasource 多数据源组件】》
版权归原作者 Naijia_OvO 所有, 如有侵权,请联系我们删除。