在Spring中,经常使用@Transactional
注解来声明方法需要事务支持。然而,当一个类中的方法自调用(即一个方法调用同类中的另一个方法)时,@Transactional
注解可能会失效。这是因为Spring的事务管理是基于代理机制实现的,而自调用无法触发代理逻辑。
示例代码
以下是遇到的一个事务失效场景:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
| @Service public class XXServiceImpl implements XXService {
@Override public void acquireItem(Long uuid, Long iId, IdptEnum idptEnum, String affairId) { String idptKey = getIdptKey(iId, idptEnum, affairId); doAcquireItem(uuid, iId, idptKey); }
@Transactional public void doAcquireItem(Long uuid, Long iId, String idptKey) { }
private String getIdptKey(Long iId, IdptEnum idptEnum, String affairId) { return String.format("%d_%d_%s", iId, idptEnum.getType(), affairId); } }
|
在上述代码中,调用了同类中的doAcquireItem
方法,但由于是自调用,@Transactional
注解没有生效。
原理分析
通过分析,了解到Spring的事务管理是通过AOP(面向切面编程)实现的:
- Spring会为带有
@Transactional
注解的方法生成代理对象。
- 当外部调用代理对象的方法时,代理会拦截调用并添加事务逻辑。
- 自调用时,调用的是目标对象本身,而不是代理对象,因此事务逻辑不会被触发。
解决方案
方法一:通过代理对象调用
将自调用改为通过代理对象调用,确保事务逻辑生效。
修改后的代码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36
| @Service public class XXServiceImpl implements XXService { @Autowired private LockService lockService; @Autowired private XXDao XXDao; @Autowired @Lazy private XXServiceImpl XXServiceImp;
@Override public void acquireItem(Long uuid, Long iId, IdptEnum idptEnum, String affairId) { String idptKey = getIdptKey(iId, idptEnum, affairId); XXServiceImp.doAcquireItem(uuid, iId, idptKey); }
@Transactional @RedisLock(key = "#idptKey", waitTime = 3000) public void doAcquireItem(Long uuid, Long iId, String idptKey) { XX XX = XXDao.getByIdpt(idptKey); if (Objects.nonNull(XX)) { return; } XX insert = XX.builder() .uuid(uuid) .iId(iId) .status(YesOrNoEnum.NO.getStatus()) .idpt(idptKey) .build(); XXDao.save(insert); }
private String getIdptKey(Long iId, IdptEnum idptEnum, String affairId) { return String.format("%d_%d_%s", iId, idptEnum.getType(), affairId); } }
|
关键点
- 使用了
@Lazy
注解延迟注入代理对象,避免循环依赖。
- 通过代理对象调用
doAcquireItem
方法,确保事务逻辑生效。
方法二:提取方法到另一个类
还尝试将需要事务支持的方法提取到另一个类中,由外部类调用。
修改后的代码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41
| @Service public class XXServiceImpl implements XXService { @Autowired private LockService lockService; @Autowired private XXDao XXDao; @Autowired private TransactionalService transactionalService;
@Override public void acquireItem(Long uuid, Long iId, IdptEnum idptEnum, String affairId) { String idptKey = getIdptKey(iId, idptEnum, affairId); transactionalService.doAcquireItem(uuid, iId, idptKey); }
private String getIdptKey(Long iId, IdptEnum idptEnum, String affairId) { return String.format("%d_%d_%s", iId, idptEnum.getType(), affairId); } }
@Service public class TransactionalService { @Autowired private XXDao XXDao;
@Transactional @RedisLock(key = "#idptKey", waitTime = 3000) public void doAcquireItem(Long uuid, Long iId, String idptKey) { XX XX = XXDao.getByIdpt(idptKey); if (Objects.nonNull(XX)) { return; } XX insert = XX.builder() .uuid(uuid) .iId(iId) .status(YesOrNoEnum.NO.getStatus()) .idpt(idptKey) .build(); XXDao.save(insert); } }
|
关键点
- 将事务逻辑提取到
TransactionalService
类中,避免自调用。
- 原类通过
@Autowired
注入TransactionalService
,调用其方法。
方法三:使用AopContext.currentProxy
最后,尝试通过AopContext.currentProxy()
获取当前代理对象,并通过代理对象调用方法。
修改后的代码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33
| @Service public class XXServiceImpl implements XXService { @Autowired private LockService lockService; @Autowired private XXDao XXDao;
@Override public void acquireItem(Long uuid, Long iId, IdptEnum idptEnum, String affairId) { String idptKey = getIdptKey(iId, idptEnum, affairId); ((XXServiceImpl) AopContext.currentProxy()).doAcquireItem(uuid, iId, idptKey); }
@Transactional @RedisLock(key = "#idptKey", waitTime = 3000) public void doAcquireItem(Long uuid, Long iId, String idptKey) { XX XX = XXDao.getByIdpt(idptKey); if (Objects.nonNull(XX)) { return; } XX insert = XX.builder() .uuid(uuid) .iId(iId) .status(YesOrNoEnum.NO.getStatus()) .idpt(idptKey) .build(); XXDao.save(insert); }
private String getIdptKey(Long iId, IdptEnum idptEnum, String affairId) { return String.format("%d_%d_%s", iId, idptEnum.getType(), affairId); } }
|
关键点
- 通过
AopContext.currentProxy()
获取当前代理对象。
- 确保在Spring配置中启用了
exposeProxy
属性:
1 2 3
| <bean class="org.springframework.aop.framework.autoproxy.DefaultAdvisorAutoProxyCreator"> <property name="exposeProxy" value="true" /> </bean>
|
事务传播行为
在解决问题的过程中,还学习了Spring事务的多种传播行为(Propagation):
REQUIRED
(默认):如果当前存在事务,则加入;否则创建新事务。
REQUIRES_NEW
:总是创建新事务,暂停当前事务。
NESTED
:嵌套事务,支持回滚到子事务。
事务失效的其他场景
- 非
public
方法:Spring AOP仅支持public
方法。
- 异常未被捕获:事务仅在未捕获的
RuntimeException
或Error
时回滚。
- 多线程调用:事务上下文无法跨线程传播。
通过这些方法,成功解决了Spring事务注解自调用失效的问题,并加深了对Spring事务机制的理解。