如何在需持续执行的特殊场景下处理EJBException?
嘿,这个场景我太熟悉了!当初在做EJB3.0项目时,也被这种“提交阶段才爆异常”的情况坑过好多次——明明DAO里没抛错,结果调用方在事务提交时拿到一个模糊的EJBException,根本没法直接判断是乐观锁冲突还是约束违规。下面是我总结的几个实用解决方案,亲测有效:
1. 主动flush,把异常提前揪出来
与其等容器提交事务时才暴露问题,不如在DAO方法结束前主动调用em.flush(),强制触发数据库操作,把预期内的异常提前在DAO层抛出来。这样你就能直接捕获到原始的数据库异常,而不是被容器包装成EJBException。
举个例子:
@Stateless public class OrderDAO { @PersistenceContext private EntityManager em; public void updateOrder(Order order) throws OptimisticLockException, ConstraintViolationException { em.merge(order); // 主动触发数据库同步,把异常提前抛出来 em.flush(); } }
这样调用方调用DAO方法时,就能直接拿到OptimisticLockException或者ConstraintViolationException,不用再猜EJBException里藏着什么。
2. 拆解EJBException的根异常
如果因为业务原因没法提前flush(比如跨多个DAO的大事务,flush会打乱操作顺序),那只能在调用方手动拆解EJBException的内部异常。记住,EJBException只是个“包装壳”,它的getCause()方法会返回真正的数据库异常——不过有时候可能会多层包装,所以最好写个小工具方法递归找根异常。
示例代码:
public class OrderService { @EJB private OrderDAO orderDAO; public void updateOrder(Order order) { try { orderDAO.updateOrder(order); // 事务在这里自动提交,异常会从这里抛出来 } catch (EJBException e) { Throwable rootCause = getRootCause(e); if (rootCause instanceof OptimisticLockException) { // 处理乐观锁冲突:比如提示用户刷新页面重试 throw new BusinessException("订单已被其他用户修改,请刷新后重试", rootCause); } else if (rootCause instanceof ConstraintViolationException) { // 处理约束违规:比如提示用户输入的字段不符合要求 throw new BusinessException("订单数据不合法,请检查后重试", rootCause); } else { // 未知异常,直接抛出不处理 throw e; } } } // 递归获取根异常的工具方法 private Throwable getRootCause(Throwable e) { while (e.getCause() != null) { e = e.getCause(); } return e; } }
3. 用自定义应用异常绕过容器包装
你可以自定义业务异常,给它加上@ApplicationException注解,这样EJB容器就不会把它包装成EJBException,而是直接抛给调用方。结合前面的flush方式,代码会更清晰。
比如:
// 自定义乐观锁业务异常 @ApplicationException(rollback = true) public class OptimisticLockConflictException extends RuntimeException { public OptimisticLockConflictException(String message, Throwable cause) { super(message, cause); } } // DAO层捕获原始异常并转换 @Stateless public class OrderDAO { @PersistenceContext private EntityManager em; public void updateOrder(Order order) { em.merge(order); try { em.flush(); } catch (OptimisticLockException e) { throw new OptimisticLockConflictException("订单已被他人修改,请重试", e); } catch (ConstraintViolationException e) { throw new DataConstraintViolationException("订单数据不符合规则,请检查", e); } } }
这样调用方就能直接捕获自定义的业务异常,不用再跟EJBException打交道,逻辑更直观。
4. 调整事务边界(谨慎使用)
如果业务允许的话,可以把事务边界下移到DAO方法,让DAO方法执行完就提交事务,这样异常会在调用DAO时直接抛出,而不是等到调用方的事务提交。比如给DAO方法加上@TransactionAttribute(TransactionAttributeType.REQUIRES_NEW),强制开启新事务:
@Stateless public class OrderDAO { @PersistenceContext private EntityManager em; @TransactionAttribute(TransactionAttributeType.REQUIRES_NEW) public void updateOrder(Order order) { em.merge(order); // 方法结束时事务自动提交,异常直接抛出 } }
不过这个方法要谨慎用,因为频繁开启新事务会影响性能,而且如果你的业务需要跨DAO的原子性操作,这种方式会破坏事务的一致性,所以得根据实际场景判断。
内容的提问来源于stack exchange,提问作者Thirler

