java同一个类中,一个无事务方法调用一个有事务方法时,事务失效问题
作者:linab112
事务的使用
在spring项目的开发中,通过在方法上添加Transactional注解,实现事务的管理,在方法开始开启事务,出现异常进行事务的回滚,方法结束前提交事务。
事务的实现原理
Transactional
注解是 Spring 框架用来实现声明式事务管理的重要工具。其原理主要基于 AOP(面向切面编程),通过动态代理在方法执行前、后以及异常情况下进行事务的处理。
当你在一个方法上使用 @Transactional
注解时,Spring 会在运行时生成一个代理对象,该对象会拦截对该方法的调用。
在调用之前,代理会开始一个新的事务;在方法执行完成后,代理会提交或回滚事务,具体取决于方法是否抛出了未处理的异常。
具体流程如下:
- spring使用jdk动态代理技术或者cglib代理来创建目标类的代理对象
- 当方法被调用时,实际上调用的是代理类中的增强方法,而不是直接调用目标类中的方法
- 在调用方法之前,代理会根据注解的属性(传播行为和隔离级别)从
PlatformTransactionManager
中获取一个事务,在调用目标方法前开启事务 - 执行目标方法
- 目标方法执行成功,则提交事务,执行失败,则回滚事务
代理的两种方式:
- JDK 动态代理:当目标类实现至少一个接口时,Spring会使用JDK动态代理。这种代理仅适用于基于接口的代理。原理是通过Java反射机制,在运行时生成一个实现了目标类接口的代理类。
- CGLIB 代理:如果目标类没有实现任何接口,或者您强制配置为使用CGLIB,那么Spring会使用CGLIB库生成一个目标类的子类作为代理。原理是使用CGLIB库,通过继承目标类并重写其方法来实现代理。
原因
在同一个类中,一个无事务方法直接调用有事务的方法时,是通过this.方法名的方式调用。
this方式:它直接访问的是当前对象的实现,如果当前类被Spring AOP代理,那么使用this
调用的方法将不会触发AOP切面。也就是说,切面(如事务管理、日志记录等)不会生效,因为你直接调用了目标对象的方法,而不是代理对象的方法。
通过Autowired注解注入的bean进行调用的方式:是通过spring容器管理的代理对象进行调用,这种情况下AOP特性可以正常工作,例如事务、日志等会生效。
Spring容器管理的代理对象的生成条件和时机
在Spring中,代理对象的生成通常与AOP(面向切面编程)相关,当类上使用@Component
、@Service
、@Repository
或@Controller
等注解,并且方法上使用AOP相关的注解时,Spring会创建代理对象,以便能够在调用这些方法时执行切面逻辑。
常见的AOP注解包括:
@Transactional
@Cacheable
@Async
@Scheduled
事务失效代码
package com.ruoyi.system.service.impl; import com.ruoyi.common.core.domain.entity.SysDept; import com.ruoyi.system.domain.SysConfig; import com.ruoyi.system.mapper.SysConfigMapper; import com.ruoyi.system.mapper.SysDeptMapper; import com.ruoyi.system.service.TestTransactionalService; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; /** * @Author linaibo * @Date 2024/8/3 15:36 * @Version 1.0 */ @Service public class TestTransactionalServiceImpl implements TestTransactionalService { @Autowired private SysDeptMapper deptMapper; @Autowired private SysConfigMapper configMapper; @Override public int insertDept(SysDept dept) { dept.setAncestors("123123"); deptMapper.insertDept(dept); insertConfig(); return 1; } @Override @Transactional(rollbackFor = Exception.class) public int insertConfig() { SysConfig config; for (int i = 0; i < 5; i++) { config = new SysConfig(); config.setConfigName("配置" + i); configMapper.insertConfig(config); if (i == 2) { throw new RuntimeException(); } } return 1; } }
解决方法
①自己autowire自己(也可以将方法放到另外一个service中,然后注入该service进行调用)
package com.ruoyi.system.service.impl; import com.ruoyi.common.core.domain.entity.SysDept; import com.ruoyi.system.domain.SysConfig; import com.ruoyi.system.mapper.SysConfigMapper; import com.ruoyi.system.mapper.SysDeptMapper; import com.ruoyi.system.service.TestTransactionalService; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; /** * @Author linaibo * @Date 2024/8/3 15:36 * @Version 1.0 */ @Service public class TestTransactionalServiceImpl implements TestTransactionalService { @Autowired private SysDeptMapper deptMapper; @Autowired private SysConfigMapper configMapper; @Autowired private TestTransactionalService testTransactionalService; @Override public int insertDept(SysDept dept) { dept.setAncestors("123123"); deptMapper.insertDept(dept); testTransactionalService.insertConfig(); return 1; } @Override @Transactional(rollbackFor = Exception.class) public int insertConfig() { SysConfig config; for (int i = 0; i < 5; i++) { config = new SysConfig(); config.setConfigName("配置" + i); configMapper.insertConfig(config); if (i == 2) { throw new RuntimeException(); } } return 1; } }
②通过spring上下文获取到当前代理类
package com.ruoyi.system.service.impl; import com.ruoyi.common.core.domain.entity.SysDept; import com.ruoyi.system.domain.SysConfig; import com.ruoyi.system.mapper.SysConfigMapper; import com.ruoyi.system.mapper.SysDeptMapper; import com.ruoyi.system.service.TestTransactionalService; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.ApplicationContext; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; /** * @Author linaibo * @Date 2024/8/3 15:36 * @Version 1.0 */ @Service public class TestTransactionalServiceImpl implements TestTransactionalService { @Autowired private SysDeptMapper deptMapper; @Autowired private SysConfigMapper configMapper; @Autowired private TestTransactionalService testTransactionalService; @Autowired private ApplicationContext applicationContext; @Override public int insertDept(SysDept dept) { dept.setAncestors("123123"); deptMapper.insertDept(dept); TestTransactionalService service = applicationContext.getBean(TestTransactionalService.class); service.insertConfig(); return 1; } @Override @Transactional(rollbackFor = Exception.class) public int insertConfig() { SysConfig config; for (int i = 0; i < 5; i++) { config = new SysConfig(); config.setConfigName("配置" + i); configMapper.insertConfig(config); if (i == 2) { throw new RuntimeException(); } } return 1; } }
③使用AopContext获取到当前代理类,需要在启动类加上EnableAspectJAutoProxy(exposeProxy = true),exposeProxy = true用于控制AOP框架公开代理,公开后才可以通过AopContext获取到当前代理类。
package com.ruoyi.system.service.impl; import com.ruoyi.common.core.domain.entity.SysDept; import com.ruoyi.system.domain.SysConfig; import com.ruoyi.system.mapper.SysConfigMapper; import com.ruoyi.system.mapper.SysDeptMapper; import com.ruoyi.system.service.TestTransactionalService; import org.springframework.aop.framework.AopContext; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.ApplicationContext; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; import java.util.Objects; /** * @Author linaibo * @Date 2024/8/3 15:36 * @Version 1.0 */ @Service public class TestTransactionalServiceImpl implements TestTransactionalService { @Autowired private SysDeptMapper deptMapper; @Autowired private SysConfigMapper configMapper; @Override public int insertDept(SysDept dept) { dept.setAncestors("123123"); deptMapper.insertDept(dept); TestTransactionalService service = Objects.nonNull(AopContext.currentProxy()) ? (TestTransactionalService)AopContext.currentProxy() : this; service.insertConfig(); return 1; } @Override @Transactional(rollbackFor = Exception.class) public int insertConfig() { SysConfig config; for (int i = 0; i < 5; i++) { config = new SysConfig(); config.setConfigName("配置" + i); configMapper.insertConfig(config); if (i == 2) { throw new RuntimeException(); } } return 1; } }
总结
使用 AOP 注解:
- 如果
TestTransactionalService
类上使用了 AOP 相关的注解(如@Transactional
,@Aspect
,@Around
等) - 通过
applicationContext.getBean(TestTransactionalService.class)
获取的对象将是一个代理对象
代理对象的创建是为了在方法调用前后加入额外的行为,比如事务管理、日志记录等。
未使用 AOP 注解:
- 如果该类没有任何 AOP 相关的注解
- 获取的对象就是普通的 Bean
- 没有经过 AOP 的增强
以上为个人经验,希望能给大家一个参考,也希望大家多多支持脚本之家。