Spring Data JPA + Hibernate:永远不会到达事务方法中的catch块



在我们的Spring Data JPA + Hibernate应用程序中,有不同的方法在同一个事务中有多个JPA操作-下面是其中一个Spring服务方法的代码/配置。

@Transactional(rollbackFor=MyAppException.class)
public void updateCustomerprofile(String customerKey) throws MyAppException{
    try{
    CustomerId customerIdObj = customerIdRepository.findOne(customerKey);
    customerIdObj.setCreatedUser("<loggedin-user>");
     // more logic here
    customerIdObj = customerIdRepository.save(customerIdObj);
    }catch(DataAccessException dae){
        //this catch block is never reached even if there is an exception while saving because of JPA flusheing at the end of transaction
        throw new MyAppException(dae);
    }
}

据观察,即使在保存记录时抛出异常,执行也永远不会到达catch块-这是因为JPA在事务结束时刷新。

这个catch块应该放在哪里?

我们应该在web层(bean)上捕获dataaccesssexception吗?

如果是这样,我们不是把数据层依赖到web层吗?

如果我必须包装DataAccessException到我的应用程序特定的异常,我怎么做?

请建议。

这里的问题是异常不是在代码中抛出的,而是在容器提交事务时抛出的(参见此答案)。要避免使用bean管理的事务,您可以:

  • 通过EntityManager.flush()
  • 同步持久性上下文
  • 使用带有@AroundInvoke注释的EJB拦截器来开始、提交和回滚事务(类似于BMT的方法),或者
  • @TransactionAttribute(REQUIRES_NEW)将应该抛出异常的代码包装在方法中。

请参阅此线程,其中进一步解释了有关REQUIRES_NEW方法的一些细节。

在catch块中添加entityManager.flush(),或者将catch块移动到@Transactional注释方法的周围。

原因是实体管理器通常可以自由决定何时将数据写入数据库(这是异常发生的时间点)。通常对数据库的写入操作是

  • 在"选择"查询之前,
  • 当显式调用entityManager.flush()或时
  • 提交事务时。

所以解决方案是:你要确保实体管理器在你的try-block内写入(非法的)数据到数据库。