Spring Boot项目业务代码中使用@Transactional事务失效踩坑点总结

Spring 是通过 AOP 技术对方法进行增强实现事务控制的,要调用增强过的方法必然是调用代理后的对象,而这里this是原生对象,并不是代理,自然就没有事务控制了 。1.概述接着之前我们对Spring AOP以及基于AOP实现事务控制的上文,今天我们来看看平时在项目业务开发中使用声明式事务@Transactional的失效场景,并分析其失效原因,从而帮助开发人员尽量避免踩坑 。
我们知道 Spring 声明式事务功能提供了极其方便的事务配置方式,配合 Spring Boot 的自动配置,大多数 Spring Boot 项目只需要在方法上标记 @Transactional 注解,即可一键开启方法的事务性配置 。当然后端开发人员对数据库事务这个概念并不陌生,也知道如果整体考虑多个数据库操作要么成功要么失败时,需要通过数据库事务来实现多个操作的一致性和原子性 。如下所示:
@Override@Transactional(rollbackFor = Exception.class)public void addUser(UserParam param) {User user = PtcBeanUtils.copy(param, User.class);userDAO.insert(user);if (!CollectionUtils.isEmpty(param.getRoleIds())) {userRoleService.addUserRole(user.getId(), param.getRoleIds());}}新增用户的同时还添加了用户角色,这里就是使用@Transactional来控制事务保证一致性的 。但大多数开发仅限于为方法标记 @Transactional来开启声明式事务,认为就可以高枕无忧了,不会去关注事务是否有效、出错后事务是否正确回滚,也不会考虑复杂的业务代码中涉及多个子业务逻辑时,怎么正确处理事务 。事务没有被正确处理,一般来说不会过于影响正常流程,也不容易在测试阶段被发现 。但当系统越来越复杂、压力越来越大之后,就会带来大量的数据不一致问题,随后就是大量的人工介入查看和修复数据 。
正是因为声明式事务@Transactional使用简单,所以很多开发人员不注重细节点,但是@Transactional条条框框还蛮多的,可谓是细节点拉满,如果不注意也不小心就会掉进坑里,今天就让我们一起来了解使用细节,把坑填平咯 。
2.@Transactional话不多说,先看看该注解定义
@Target({ElementType.TYPE, ElementType.METHOD})@Retention(RetentionPolicy.RUNTIME)@Inherited@Documentedpublic @interface Transactional { @AliasFor("transactionManager") String value() default ""; @AliasFor("value") String transactionManager() default ""; Propagation propagation() default Propagation.REQUIRED; Isolation isolation() default Isolation.DEFAULT; int timeout() default TransactionDefinition.TIMEOUT_DEFAULT; boolean readOnly() default false; Class<? extends Throwable>[] rollbackFor() default {}; String[] rollbackForClassName() default {}; Class<? extends Throwable>[] noRollbackFor() default {}; String[] noRollbackForClassName() default {};}从上面看出@Transactional既可以作用于类上,也可以作用于方法上,作用于类:表示所有该类的**public**方法都配置相同的事务属性信息 。接下来再看看其属性:
propagation: 设置事务的传播行为,主要解决是A方法调用B方法时,事务的传播方式问题的,默认值为 **Propagation.REQUIRED**,其他属性值信息如下:
事务传播行为
解释
REQUIRED(默认值)
A调用B,B需要事务,如果A有事务B就加入A的事务中,如果A没有事务,B就自己创建一个事务
REQUIRED_NEW
A调用B,B需要新事务,如果A有事务就挂起,B自己创建一个新的事务
SUPPORTS
A调用B,B有无事务无所谓,A有事务就加入到A事务中,A无事务B就以非事务方式执行
NOT_SUPPORTS
A调用B,B以无事务方式执行,A如有事务则挂起
NEVER
A调用B,B以无事务方式执行,A如有事务则抛出异常
MANDATORY
A调用B,B要加入A的事务中,如果A无事务就抛出异常
NESTED
A调用B,B创建一个新事务,A有事务就作为嵌套事务存在,A没事务就以创建的新事务执行
isolation :事务的隔离级别,默认值为 Isolation.DEFAULT 。指定事务的隔离级别,事务并发存在三大问题:脏读、不可重复读、幻读/虚读 。可以通过设置事务的隔离级别来保证并发问题的出现,常用的是READ_COMMITTED 和REPEATABLE_READ
isolation属性
解释
DEFAULT
默认隔离级别,取决于当前数据库隔离级别,例如MySQL默认隔离级别是REPEATABLE_READ


推荐阅读