无状态Bean中JPA遇重复键异常,已持久化对象未入库原因?
问题原因分析
这是EJB容器管理事务(CMT)的默认行为导致的:
@Stateless无状态Bean默认采用容器管理事务,事务边界默认绑定到整个业务方法(也就是addProductToCustomer方法)。ConstraintViolationException属于RuntimeException范畴,在CMT规则下,只要方法内部抛出这类未被容器标记为“非回滚触发”的异常,哪怕你在代码里手动捕获了它,容器仍然会自动触发整个事务的回滚。- 你执行的
flush操作只是把持久化上下文的变更同步到数据库,但此时这些变更处于未提交的事务状态,一旦事务被回滚,数据库会撤销所有未提交的修改——包括之前持久化的customerEntity和prod1。
不提前查询的解决方案
方案1:拆分事务,隔离prod2的操作
把prod2的持久化逻辑拆到单独的方法中,通过@TransactionAttribute注解指定该方法使用新事务执行,这样prod2的事务与主事务相互隔离:
@Stateless public class WebUserServiceEjb { @PersistenceContext private EntityManager em; public void addProductToCustomer() { // 持久化customerEntity并执行flush、refresh em.persist(customerEntity); em.flush(); em.refresh(customerEntity); // 持久化prod1 em.persist(prod1); // 调用单独处理prod2的方法 try { persistProd2(prod2); } catch (ConstraintViolationException e) { // 自定义异常处理逻辑 } } @TransactionAttribute(TransactionAttributeType.REQUIRES_NEW) private void persistProd2(Product prod2) { em.persist(prod2); } }
REQUIRES_NEW会强制开启一个独立的新事务,prod2的操作在新事务中执行,即使它抛出异常触发回滚,也只会影响自身事务,主事务中的customerEntity和prod1不受影响,主事务正常提交后就能写入数据库。
方案2:切换为Bean管理事务(BMT)
手动控制事务边界,将customer和prod1的操作、prod2的操作分属不同事务:
@Stateless @TransactionManagement(TransactionManagementType.BEAN) public class WebUserServiceEjb { @PersistenceContext private EntityManager em; @Resource private UserTransaction utx; public void addProductToCustomer() { try { // 开启主事务,处理customer和prod1 utx.begin(); em.persist(customerEntity); em.flush(); em.refresh(customerEntity); em.persist(prod1); utx.commit(); // 提交主事务,写入数据库 // 开启新事务处理prod2 utx.begin(); em.persist(prod2); utx.commit(); } catch (ConstraintViolationException e) { // 处理prod2的异常,仅回滚当前(prod2的)事务 try { utx.rollback(); } catch (Exception ex) { // 处理回滚异常 } } catch (Exception e) { // 处理其他异常,回滚主事务 try { utx.rollback(); } catch (Exception ex) { // 处理回滚异常 } } } }
通过BMT手动控制事务的开启、提交与回滚,完全隔离不同操作的事务范围,避免prod2的异常影响之前的业务操作。
内容的提问来源于stack exchange,提问作者y.z
相关产品推荐
相关产品推荐

