内部事务(REQUIRES_NEW)引发异常时,外部事务回滚

时间:2018-08-14 12:34:32

标签: java spring spring-data-jpa spring-transactions

我有方法:

  @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

我需要打开交易并开始保存每个实体。如果某个实体无法例外保存-我需要记录此例外并继续保存另一个实体。当所有实体都将保存(或记录)时,我需要提交外部事务。但是现在回滚了。我该如何解决?

编辑:

我接受了评论并将REQUIRES_NEW事务移至另一个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 个答案:

答案 0 :(得分:2)

您应将方法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);
  }
}

答案 1 :(得分:1)

如果saveCharge是与importChargesRequest相同的bean中的方法,则@Transactional注释将被忽略,并且saveAndFlush在同一(外部)事务中工作。 (我确定当您使用代理/拦截器来管理事务时就是这种情况。我相当确定当使用基于Aspectj的事务拦截时也是如此。)

通常,只有在异常一直上升到外部方法(标为@Transaction的气泡)的情况下,才将事务标记为回滚,但是我怀疑存储库或事务管理器本身(休眠会话)直接将事务标记为由于违反约束而回滚。

解决方案是将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());
      }
    }
  }
}

附录: 注释将被忽略,因为您不再通过bean实例的代理,这意味着不会调用任何拦截器,这意味着没有地方可以处理新事务。 您可以通过在saveCharge方法中设置断点并查看stacktrace ^来检查是否是这种情况。寻找类似事务拦截器的invokeIninTransaction之类的方法。

^您还可以创建一个新的异常,调用fillInStacktrace,然后记录/打印该异常及其堆栈跟踪。

答案 2 :(得分:1)

您正在做saveCharge(charge)的{​​{1}}(您以这种方式绕过spring代理)-spring为此无法创建 new 事务-您需要具有单独的this.saveCharge(charge)

中的方法