Spring 事务管理分为编程式和声明式两种 编程式事务指的是通过编码方式实现事务;声明式事务基于 AOP,将具体的逻辑与事务处理解耦(编程式事务在这边不做过多陈述)。
声明式事务有两种方式 ,一种是在配置文件 (XML)中做相关的事务规则声明,另一种是基于 @Transactional 注解的方式。
先看一张思维导图(来自互联网)
@Transactional注解
@Transactional实质是使用了JDBC的事务来进行事务控制的
@Transactional基于Spring的动态代理的机制
@Transactional实现原理
1:事务开始时,通过AOP机制,生成一个代理connection对象,并将其放入DataSource实例的某个与DataSourceTransactionManager相关的某处容器中。
在接下来的整个事务中,客户代码都应该使用该connection连接数据库,执行所有数据库命令[不使用该connection连接数据库执行的数据库命令,在本事务回滚的时候得不到回滚](物理连接connection逻辑上新建一个会话session;DataSource与TransactionManager配置相同的数据源)
2:事务结束时,回滚在第1步骤中得到的代理connection对象上执行的数据库命令,然后关闭该代理connection对象(事务结束后,回滚操作不会对已执行完毕的SQL操作命令起作用)
事务失效
通过@Transactional实现原理可以发现;
- Spring@Transactional不支持跨数据源事物,Spring 事物控制是基于数据库链接进行的,当数据源切换后,数据库链接切换,事物回滚只能回退,当前持有的链接。
- Spring开启事物后,会将当前数据库及数据库链接资源进行线程绑定,导致数据源切换失效(数据源切换执行后,并未获取到新的数据库链接)
怎么解决多数据源问题
新平台目前使用的是@DS注解来切换数据源,通过DynamicDataSourceAnnotationInterceptor我们可以看到,实现的是MethodInterceptor,在通过注解进行数据源切换,由于@Transactional对数据库连接绑定,导致@DS切换不生效;
java
public Object invoke(MethodInvocation invocation) throws Throwable {
String dsKey = this.determineDatasourceKey(invocation);
DynamicDataSourceContextHolder.push(dsKey);
Object var3;
try {
var3 = invocation.proceed();
} finally {
DynamicDataSourceContextHolder.poll();
}
return var3;
}
private String determineDatasourceKey(MethodInvocation invocation) {
String key = this.dataSourceClassResolver.findKey(invocation.getMethod(), invocation.getThis());
return key.startsWith("#") ? this.dsProcessor.determineDatasource(invocation, key) : key;
}
解决方案
由于事务的开启是通过AOP实现的,而service 方法内调用绕过了AOP拦截,为了解决这个问题可以使用
java
(Tservice)AopContext.currentProxy().test1();
通过@Transactional原理可知@Transactional是单事务,因此在具体的方法上
```java
@Transactional(propagation = Propagation.REQUIRES_NEW)
public void test1(){
但是这样就没法控制多数据源事务;
mybatis-plus解决方案
mybatis-plus官方提供了@DSTransactional注解
java
@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface DSTransactional {
}
在没有seata的情况下,通过DynamicDataSourceAnnotationAdvisor 管理事务,DynamicDataSourceAnnotationAdvisor的拦截器是DynamicLocalTransactionInterceptor
java
@Role(2)
@Bean
@ConditionalOnProperty(
prefix = "spring.datasource.dynamic",
name = {"seata"},
havingValue = "false",
matchIfMissing = true
)
public Advisor dynamicTransactionAdvisor() {
DynamicLocalTransactionInterceptor interceptor = new DynamicLocalTransactionInterceptor();
return new DynamicDataSourceAnnotationAdvisor(interceptor, DSTransactional.class);
}
具体的大家可以自行了解一下;
应该怎么做
通过上面的说明,想必大家已经知道,我们应该怎样做
java
@DSTransactional
public int addEn(Users users) {
(Tservice)AopContext.currentProxy().insertUser(users);
(Tservice)AopContext.currentProxy().insertUser1(users);
if ("aa".equals(users.getName())) {
throw new RuntimeException();
}
return 1;
}
public void insertUser(Users users) {
usersMapper.insertUser(users);
}
public void insertUser1(Users users) {
usersMapper.insertUser1(users);
}
usermapper
java
@DS("master")
Long insertUser(Users users);
@DS("slave")
Long insertUser1(Users users);
后续改进
Spring Boot整合了Atomikos和Bitronix两个JTA(Java Transaction API)的实现,但在2.3.0的版本中Bitronix不推荐使用Bitronix了。Atomikos提供了两款分布式事务产品级解决方案,分别是ExtremeTransactions和TransactionsEssentials,前者需要商业授权,后者是开源免费使用版本,TransactionsEssentials是免费的,可无缝升级为ExtremeTransactions版本;后续我们可以整合Atomikos,目前若依也给出了整合方案;