代码之家  ›  专栏  ›  技术社区  ›  ip696

内部事务(需要新建)引发异常时的外部事务回滚

  •  3
  • ip696  · 技术社区  · 6 年前

      @Transactional
      public void importChargesRequest() {
      ...
       for (Charge charge : charges) {
    
          try {
            Charge savedCharge = saveCharge(charge);
          } catch (Exception e) {
            log.error(e.getMessage());
          }
        }
    }
    

    为了坚持每一个 Charge 我调用内部方法:

    @Transactional(propagation = Propagation.REQUIRES_NEW)
      public Charge saveCharge(Charge charge) {
        return chargesRepository.saveAndFlush(charge);
      }
    

    如果 saveCharge 方法抛出异常(在我的例子中是约束异常)我想要写日志,并继续保存另一个实体。但当我捕捉到异常时-我的外部事务返回错误:

    Transaction was marked for rollback only; cannot commit; nested exception is org.hibernate.TransactionException: Transaction was marked for rollback only; cannot commit
    

    我需要打开事务并开始保存每个实体。如果某个实体不能保存异常-我需要记录此异常并继续保存其他实体。当所有实体都将被保存(或记录)时,我需要提交外部事务。但现在它又卷土重来了。我怎样才能修好它?

    编辑:

    我接受了这些评论,并将需要的新事务转移到另一个bean:

    @Service
    public class TestService {
    
      private final TestDao testDao;
    
      public TestService(TestDao testDao) {
        this.testDao = testDao;
      }
    
      @Transactional
      public void saveTest() {
        for (int i = 0; i < 100; i++) {
          Test test = new Test();
          if (i == 10 || i == 20) {
            test.setName("123");
          } else {
            test.setName(UUID.randomUUID().toString());
          }
          testDao.save(test);
        }
      }
    } 
    

    以及每个内部事务的另一个bean:

    @Slf4j
    @Component
    @Repository
    public class TestDao {
    
      @PersistenceContext
      private EntityManager entityManager;
    
      @Transactional(propagation = Propagation.REQUIRES_NEW)
      public void save(Test test) {
        entityManager.persist(test);
      }
    }
    

    当我第一次尝试保存时,我在数据库中有20行。每保存一次,我就得到+10行。名称有约束。当我得到错误-事务是提交而不是继续。每次保存后我都要等待98行。

    3 回复  |  直到 6 年前
        1
  •  1
  •   Buurman    6 年前

    如果 saveCharge 方法与 importChargesRequest ,和 @Transactional 注释被忽略,saveAndFlush在同一个(外部)事务中工作。 (我确信当使用代理/拦截器管理事务时,情况就是这样。我很确定使用基于aspectj的事务拦截时也是如此)。

    通常,只有当异常一直冒泡到外部方法(标记为@transaction的方法)时,事务才会被标记为回滚,但我怀疑存储库或事务管理器本身(hibernate session)由于约束冲突而直接将事务标记为回滚。

    解决方案是将saveCharge移动到另一个bean,并使用importChargesRequest方法将该bean注入bean。

    @Service
    public class ChargesDataService{
    
      @Autowire
      private ChargesRepository chargesRepository;
    
      @Transactional(propagation = Propagation.REQUIRES_NEW)
      public Charge saveCharge(Charge charge) {
        return chargesRepository.saveAndFlush(charge);
      }
    }
    
    @RestController
    public class ChargesController{
    
      @Autowire
      private ChargesDataService chargesDataService;
    
      @Transactional
      public void importChargesRequest() {
    
       for (Charge charge : charges) {
    
          try {
            Charge savedCharge = chargesDataService.saveCharge(charge);
          } catch (Exception e) {
            log.error(e.getMessage());
          }
        }
      }
    }
    

    附录: 您可以通过在 储蓄

    ^您还可以创建一个新的异常,调用fillInStacktrace,然后记录/打印异常,包括其stacktrace。

        2
  •  2
  •   Piotr Rogowski    6 年前

    你应该移动方法 saveCharge 到其他班级

    创建新的服务类并注入主类

    例如:

    @Service
    public class A{
    
    @Autowire
    private B b;
    
    @Transactional
      public void importChargesRequest() {
      ...
       for (Charge charge : charges) {
    
          try {
            Charge savedCharge = b.saveCharge(charge);
          } catch (Exception e) {
            log.error(e.getMessage());
          }
        }
    }
    }
    
    @Service
    public class B{
    
    @Transactional(propagation = Propagation.REQUIRES_NEW)
      public Charge saveCharge(Charge charge) {
        return chargesRepository.saveAndFlush(charge);
      }
    }
    
        3
  •  1
  •   Eugene    6 年前

    你在做什么 saveCharge(charge) 那就是 this.saveCharge(charge) (您正以这种方式绕过spring代理)-spring无法创建 新的 Bean