传统springboot实现事务只需要在方法上添加@Transactional注解,但是需要在所有的service都加上事务,相对比较麻烦,随着项目的庞大,功能模块会随之增多,所以就需要采用AOP的方式实现全局事务处理。
全局事务配置通过AOP切面指定方法前缀切入点,从而对指定的方法统一进行事务控制,根据方法名前缀来匹配到具体方法,进行事务配置
提到事务,你肯定不陌生,和数据库打交道的时候,我们总会用到事务
你要给朋友小王转 100 块钱,而此时你的银行卡只有 100 块钱。转账过程具体到程序里会有一系列的操作,比如查询余额、做加减法、更新余额等,这些操作必
须保证是一体的,不然等程序查完之后,还没做减法之前,你这 100 块钱,完全可以借着这个时间差再查一次,然后再给另外一个朋友转账,如果银行这么整,不就乱了么?这时就要用到 “ 事务 ” 这个概念了。
简单来说,事务就是保证数据操作,要么全部成功,要么全部失败。MySQL 虽然支持多引擎系统,但并不是所有的引擎都支持事务。比如 MySQL 原生的 MyISAM 引擎就不支持事务,这也是 MyISAM 被 InnoDB 取代的重要原因之一。
当数据库上有多个事务同时执行时候,就会出现并发问题:
为了解决这些问题,就有了 “ 隔离级别 ” 的概念。
重点: 隔离得越严实,效率就会越低。
SQL 标准的事务隔离级别包括:
其中 “ 读提交 ” 和 “ 可重复读 ” 比较难理解,所以我用一个例子说明这几种隔离级别。假设数据表 T 中只有一列,其中一行的值为 1 ,下面是按照时间顺序执行两个事务的行为。
mysql> create table T(c int) engine=InnoDB;
insert into T(c) values(1);
在不同的隔离级别下,事务 A 会有哪些不同的返回结果,也就是图里面 V1 、 V2 、 V3 的返回值分别是什么。
好了,概念说完了,下面进入主题,SpringBoot 全局事务配置。
/**
* @author lanys
* @author Think
* @title: SpringTxAspect
* @projectName material_cloud
* @description: Spring 全局事务切面配置
* @date 2022/11/2 12:18
*/
@Aspect
@Configuration
public class SpringTxAspect {
/** 切面,根据自己的项目定义不同的表达式execution **/
private static final String AOP_POINTCUT_EXPRESSION = "execution(* com.material_cloud.short_link.modules.service.impl.*.*(..))";
@Resource
private PlatformTransactionManager transactionManager;
/**
* 增强(事务)的属性的配置
*
* @title: txAdvice
* @author lanys 2022-11-02
* @Description: 配置
* @param
* @return TransactionInterceptor
*/
@SuppressWarnings({ "unchecked", "rawtypes" })
@Bean
public TransactionInterceptor txAdvice() {
NameMatchTransactionAttributeSource txAttributeS = new NameMatchTransactionAttributeSource();
RuleBasedTransactionAttribute requiredAttr = new RuleBasedTransactionAttribute();
// PROPAGATION_REQUIRED:如果当前没有事务,就新建一个事务,如果已经存在一个事务中,加入到这个事务中
requiredAttr.setPropagationBehavior(TransactionDefinition.PROPAGATION_REQUIRED);
// 抛出异常后执行切点回滚
requiredAttr.setRollbackRules(Collections.singletonList(new RollbackRuleAttribute(Exception.class)));
//
RuleBasedTransactionAttribute supportsAttr = new RuleBasedTransactionAttribute();
// PROPAGATION_SUPPORTS:支持当前事务,如果当前没有事务,就以非事务方式执行
supportsAttr.setPropagationBehavior(TransactionDefinition.PROPAGATION_SUPPORTS);
// 只读事务,不做更新操作
supportsAttr.setReadOnly(true);
// 注意:方法名称来自类匹配的到方法【save*, “*”表示匹配任意個字符】
Map txMethod = new HashMap();
txMethod.put("insert*", requiredAttr);
txMethod.put("add*", requiredAttr);
txMethod.put("update*", requiredAttr);
txMethod.put("modify*", requiredAttr);
txMethod.put("remove*", requiredAttr);
txMethod.put("delete*", requiredAttr);
txMethod.put("bind*", requiredAttr);
txMethod.put("unbind*", requiredAttr);
// readOnly = true
txMethod.put("select*", supportsAttr);
txMethod.put("get*", supportsAttr);
txMethod.put("find*", supportsAttr);
txMethod.put("query*", supportsAttr);
txMethod.put("read*", supportsAttr);
txMethod.put("check*", supportsAttr);
//
txAttributeS.setNameMap(txMethod);
TransactionInterceptor txAdvice = new TransactionInterceptor(transactionManager, txAttributeS);
return txAdvice;
}
/**
* AOP配置定义切面和切点的信息
*
* @title: txAdviceAdvisor
* @author lanys 2022-11-02
* @Description: AdvisorBean
* @return Advisor
*/
@Bean
public Advisor txAdviceAdvisor() {
AspectJExpressionPointcut pointcut = new AspectJExpressionPointcut();
pointcut.setExpression(AOP_POINTCUT_EXPRESSION);
return new DefaultPointcutAdvisor(pointcut, txAdvice());
}
}