在我们的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内写入(非法的)数据到数据库。