在Spring Boot和MyBatis中,我们有时需要在方法中同时使用两个不同的数据库,但使用
@Transactional
注解会变得复杂。这时我们可以用一种更灵活的方法来处理。
想象一下这样的场景:我们有两个数据库,我们希望在一个方法中同时操作它们,但是普通的@Transactional
注解变得不太适用。
我们可以采用一种类似于“双提交”的策略来解决这个问题。首先,我们让两个数据库执行所需的操作,然后立即提交。接下来,如果整个方法执行成功,我们就提交这两个数据库的事务。但是,如果在方法执行过程中出现了问题,我们会回滚这两个数据库的事务。
简单来说,我们先让两个数据库做好准备,等到方法完成后,如果一切顺利,我们正式确认这两个数据库的操作。如果出现了错误,我们撤销之前的操作,就像玩一个双关游戏一样。
通过这种方法,我们能够更加灵活地在方法中操作多个数据库,而不用被注解的方式束缚。这种方式让事务的控制更加精准,保证了数据的一致性。
文章目录
1. 使用实例
首先看一下如何使用,下面的方法里有两条sql,分别向两个不同的数据库插入数据,我们在方法上加自定义注解`@MoreTransaction`,里面传入两个事务管理器的beann名称,当有异常时,自定义注解的切面方法拦截到异常,两条插入语句sql都会被回滚。
@MoreTransaction(value = {"transactionManagerOne","transactionManagerTwo"})
public ResultData getDataSourceList(){
//向第一个数据库插入数据
int i=userService.addUser(new User().setUserName("数据库1"));
//故意制造异常,抛出给事务切面
int a=1/0;
//向第二个是数据库插入数据
int k=userService.addUserInfo(new UserInfo().setUserAccount("数据库2"));
Map map=new HashMap();
map.put("k",k);
return ResultData.success(map);
}
2. 首先分别配置两个数据库的数据源和事务管理器。
- 定义第一个第一个数据源
DataSourceOne
,定义事务管理器的bean为transactionManagerOne
/**
* 数据源1
*/
@Configuration
@MapperScan(basePackages = "com.example.mybatis.mapper",sqlSessionFactoryRef = "sqlSessionFactoryOne")
public class DataSourceConfigOne {
//配置第一个数据源的事务管理器,定义bean名称为 transactionManagerOne
@Bean(name = "transactionManagerOne")
public PlatformTransactionManager transactionManagerOne(@Qualifier("dataSourceOne") DataSource dataSourceOne) {
return new DataSourceTransactionManager(dataSourceOne);
}
// --- 下面是配置数据源的代码 --
@Bean(name = "dataSourceOne")
@Primary// 表示这个数据源是默认数据源
// 读取application.properties中的配置参数映射成为一个对象,prefix表示参数的前缀
@ConfigurationProperties(prefix = "spring.datasource.one")
public DataSource dataSourceOne() {
return DataSourceBuilder.create().build();
}
@Primary
public SqlSessionTemplate sqlsessiontemplateOne(@Qualifier("sqlsessiontemplateOne") SqlSessionFactory sessionfactory) {
return new SqlSessionTemplate(sessionfactory);
}
@Bean(name = "sqlSessionFactoryOne")
@Primary
public SqlSessionFactory sqlSessionFactoryOne(@Qualifier("dataSourceOne") DataSource datasource)throws Exception {
SqlSessionFactoryBean bean = new SqlSessionFactoryBean();
bean.setDataSource(datasource);
bean.setMapperLocations(
new PathMatchingResourcePatternResolver().getResources("classpath:mapper/*.xml"));
bean.getObject().getConfiguration().setMapUnderscoreToCamelCase(true);
return bean.getObject();
}
}
- 定义第一个第一个数据源
DataSourceTwo
,定义事务管理器的bean为transactionManagerTwo
/**
* 数据源2
*/
@Configuration
@MapperScan(basePackages = "com.example.mybatis.mapper2",sqlSessionFactoryRef = "sqlSessionFactoryTwo")
public class DataSourceConfigTwo {
//配置第一个数据源的事务管理器,定义bean名称为 transactionManagerOne
@Bean(name = "transactionManagerTwo")
public PlatformTransactionManager transactionManagerTwo(@Qualifier("dataSourceTwo") DataSource dataSourceTwo) {
return new DataSourceTransactionManager(dataSourceTwo);
}
// --- 下面是配置数据源的代码 --
@Bean(name = "dataSourceTwo")
@ConfigurationProperties(prefix = "spring.datasource.two")
public DataSource dataSourceTwo() {
return DataSourceBuilder.create().build();
}
@Bean(name = "sqlSessionFactoryTwo")
public SqlSessionFactory sqlSessionFactoryTwo(@Qualifier("dataSourceTwo") DataSource datasource)throws Exception {
SqlSessionFactoryBean bean = new SqlSessionFactoryBean();
bean.setDataSource(datasource);
bean.setMapperLocations(
// 设置mybatis的xml所在位置
new PathMatchingResourcePatternResolver().getResources("classpath:mapper2/*.xml"));
bean.getObject().getConfiguration().setMapUnderscoreToCamelCase(true);//下划线-驼峰映射
return bean.getObject();
}
public SqlSessionTemplate sqlsessiontemplateTwo(@Qualifier("sqlsessiontemplateTwo") SqlSessionFactory sessionfactory) {
return new SqlSessionTemplate(sessionfactory);
}
}
3. 使用自定义注解
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.METHOD,ElementType.PARAMETER})
public @interface MoreTransaction {
String[] value() default {};
}
4. 事务切面方法,多数据源事务的实现(重点)
@Aspect
@Component
public class TransactionAop {
@Pointcut("@annotation(com.example.mybatis.config.aop.annotation.MoreTransaction)")
public void MoreTransaction() {
}
@Pointcut("execution(* com.example.mybatis.controller.*.*(..))")
public void excudeController() {
}
@Around(value = "MoreTransaction()&&excudeController()&&@annotation(annotation)")
public Object twiceAsOld(ProceedingJoinPoint thisJoinPoint, MoreTransaction annotation) throws Throwable {
//存放事务管理器的栈
Stack<DataSourceTransactionManager> dataSourceTransactionManagerStack = new Stack<>();
//存放事务的状态,每一个DataSourceTransactionManager 对应一个 TransactionStatus
Stack<TransactionStatus> transactionStatuStack = new Stack<>();
try {
//判断自定义注解@MoreTransaction 是否传入事务管理器的名字,将自定义注解的值对应的事务管理器入栈
if (!openTransaction(dataSourceTransactionManagerStack, transactionStatuStack, annotation)) {
return null;
}
//执行业务方法
Object ret = thisJoinPoint.proceed();
//如果没有异常,说明两个sql都执行成功,两个数据源的sql全部提交事务
commit(dataSourceTransactionManagerStack, transactionStatuStack);
return ret;
} catch (Throwable e) {
//业务代码发生异常,回滚两个数据源的事务
rollback(dataSourceTransactionManagerStack, transactionStatuStack);
log.error(String.format("MultiTransactionalAspect, method:%s-%s occors error:",
thisJoinPoint.getTarget().getClass().getSimpleName(), thisJoinPoint.getSignature().getName()), e);
throw e;
}
}
/**
* 开启事务处理方法
*
* @param dataSourceTransactionManagerStack
* @param transactionStatuStack
* @param multiTransactional
* @return
*/
private boolean openTransaction(Stack<DataSourceTransactionManager> dataSourceTransactionManagerStack,
Stack<TransactionStatus> transactionStatuStack,MoreTransaction multiTransactional) {
// 获取需要开启事务的事务管理器名字
String[] transactionMangerNames = multiTransactional.value();
// 检查是否有需要开启事务的事务管理器名字
if (ArrayUtils.isEmpty(multiTransactional.value())) {
return false;
}
// 遍历事务管理器名字数组,逐个开启事务并将事务状态和管理器存入栈中
for (String beanName : transactionMangerNames) {
// 从Spring上下文中获取事务管理器
DataSourceTransactionManager dataSourceTransactionManager =(DataSourceTransactionManager) SpringContextUtil.getBean(beanName);
// 创建新的事务状态
TransactionStatus transactionStatus = dataSourceTransactionManager
.getTransaction(new DefaultTransactionDefinition());
// 将事务状态和事务管理器存入对应的栈中
transactionStatuStack.push(transactionStatus);
dataSourceTransactionManagerStack.push(dataSourceTransactionManager);
}
return true;
}
/**
* 提交处理方法
*
* @param dataSourceTransactionManagerStack
* @param transactionStatuStack
*/
private void commit(Stack<DataSourceTransactionManager> dataSourceTransactionManagerStack,
Stack<TransactionStatus> transactionStatuStack) {
// 循环,直到事务管理器栈为空
while (!dataSourceTransactionManagerStack.isEmpty()) {
// 从事务管理器栈和事务状态栈中分别弹出当前的事务管理器和事务状态
// 提交当前事务状态
dataSourceTransactionManagerStack.pop()
.commit(transactionStatuStack.pop());
}
}
/**
* 回滚处理方法
* @param dataSourceTransactionManagerStack
* @param transactionStatuStack
*/
private void rollback(Stack<DataSourceTransactionManager> dataSourceTransactionManagerStack,
Stack<TransactionStatus> transactionStatuStack) {
// 循环,直到事务管理器栈为空
while (!dataSourceTransactionManagerStack.isEmpty()) {
// 从事务管理器栈和事务状态栈中分别弹出当前的事务管理器和事务状态
// 回滚当前事务状态
dataSourceTransactionManagerStack.pop().rollback(transactionStatuStack.pop());
}
}
}
5. 使用事务注解,将两个数据源的事务管理器名字作为参数传入。
@MoreTransaction(value = {"transactionManagerOne","transactionManagerTwo"})
public ResultData getDataSourceList(){
//向第一个数据库插入数据
int i=userService.addUser(new User().setUserName("数据库1"));
//故意制造异常,抛出给事务切面
int a=1/0;
//向第二个是数据库插入数据
int k=userService.addUserInfo(new UserInfo().setUserAccount("数据库2"));
Map map=new HashMap();
map.put("k",k);
return ResultData.success(map);
}
6. 提交请求后会发现控制台报错,但是数据库里面并没有插入数据。