JTA的REQUIRES_NEW在同一CDI注入类中不生效问题
为什么同一类中
@Transactional(REQUIRES_NEW)不生效?怎么解决? 这个问题其实是事务代理机制的典型坑——不管是CDI还是Java EE的事务实现,都是基于动态代理工作的。当你在同一个类内部调用方法时,是直接调用原始对象的方法,完全没经过事务代理类,所以@Transactional(value = Transactional.TxType.REQUIRES_NEW)这个注解根本不会被触发,自然和start()方法共享同一个事务。
下面给你几个可行的解决方案:
方案1:把forceSave()移到单独的类中(最推荐)
这是你已经发现的有效方法,也是最符合单一职责原则的做法。把独立的事务操作抽成单独的服务类,注入到原类中调用,此时调用会经过代理,触发新事务:
@Named @RequestScoped public class SaveService { @Transactional(value = Transactional.TxType.REQUIRES_NEW) public void forceSave() { // 你的保存逻辑 } }
然后修改你的MyClass:
@Named @RequestScoped public class MyClass{ @Inject private SaveService saveService; @Transactional public void start(){ for(int i = 0; i < 100;i++){ //执行一些逻辑 saveService.forceSave();//现在会开启新事务立即保存 } } }
方案2:自注入代理对象(不拆类的快速方案)
如果你不想拆分类,可以通过CDI自注入自己的代理实例,然后通过这个代理调用forceSave()方法,就能触发事务拦截器:
@Named @RequestScoped public class MyClass{ @Inject private MyClass self; // 注入的是代理对象,不是原始类实例 @Transactional public void start(){ for(int i = 0; i < 100;i++){ //执行一些逻辑 self.forceSave();//通过代理调用,触发REQUIRES_NEW } } @Transactional(value = Transactional.TxType.REQUIRES_NEW) public void forceSave(){ //保存逻辑 } }
这里要注意:CDI允许自注入,注入的是代理实例而非原始对象,所以调用self的方法会经过事务代理,从而开启新事务。
方案3:通过BeanManager获取代理实例(更底层的方式)
如果自注入遇到循环依赖问题(虽然CDI通常能处理),还可以通过BeanManager手动获取代理对象:
@Named @RequestScoped public class MyClass{ @Inject private BeanManager beanManager; @Transactional public void start(){ MyClass proxy = beanManager.getReference( beanManager.resolve(beanManager.getBeans(MyClass.class)), MyClass.class, beanManager.createCreationalContext(null) ); for(int i = 0; i < 100;i++){ //执行一些逻辑 proxy.forceSave();//通过代理调用 } } @Transactional(value = Transactional.TxType.REQUIRES_NEW) public void forceSave(){ //保存逻辑 } }
这个方法比较繁琐,一般优先推荐前两种方案,除非你有特殊场景需求。
总结一下:核心就是要让带有@Transactional(REQUIRES_NEW)的方法调用经过事务代理,而同一类内部直接调用会绕开代理,所以必须通过外部代理实例来触发新事务。
内容的提问来源于stack exchange,提问作者Ronaldo Lanhellas
相关产品推荐
相关产品推荐

