捕获 EJBTransactionRolledbackException

Catching EJBTransactionRolledbackException

我对 EJBTransactionRolledbackException 的理解有问题。

我有实体:

@Entity
public class MyEntity {

    @Id
    @GeneratedValue
    private Long id;
    @Size(max=5)
    private String name;
//...
}

由于 CMT 的易用性,存储库是 SLSB:

@Stateless
public class ExampleRepository {
    @PersistenceContext
    private EntityManager em;

    public void add(MyEntity me) {
        em.persist(me);
    }
}

现在我有测试 Servlet,当我模拟 ConstraintViolation(名称太长)时。

@WebServlet("/example")
public class ExampleServlet extends HttpServlet {
    @Inject
    private ExampleRepository repo;

    protected void doGet(HttpServletRequest request, HttpServletResponse response)
            throws ServletException, IOException {
        MyEntity me = new MyEntity();
        me.setName("TooLongName");
        try {
            repo.add(me);
        } catch(EJBTransactionRolledbackException e) {
            System.out.println("Exception caught");
        }
    }
}

我知道在这种情况下,EJB 容器将包装 ConstraintViolationException,所以我捕获了 EJBTransactionRolledbackException。问题是在控制台中我可以看到来自 catch 块的消息 ("Exception caught"),但在此之前会生成大量异常日志 (link)。 我不太明白发生了什么 - 这个异常是否被捕获?如何在这种简单的场景中防止控制台中出现所有这些错误消息?

我可以建议你两个解决方案:

  1. 使用 bean 管理事务:

    @Stateless
    @TransactionManagement(TransactionManagementType.BEAN)
    public class ExampleRepository {
        @PersistenceContext(synchronization = SynchronizationType.UNSYNCHRONIZED))
        private EntityManager em;
    
        @Resource
        private UserTransaction tx;
    
        public void add(MyEntity me) {
            try {
                tx.begin();
                em.joinTransaction();
                em.persist(me);
                tx.commit();
            } catch (ValidationException ex) {
                throw new AppValidationException(ex);
            }
        }
    }
    
  2. 委托/门面模式:

    您将 ExampleRepository 保持原样:

    @Stateless
    public class ExampleRepository {
        @PersistenceContext
        private EntityManager em;
    
        public void add(MyEntity me) {
            em.persist(me);
        }
    }
    

    创建没有事务的新 EJB(使用与初始相同的方法):

    @Stateless
    @TransactionAttribute(TransactionAttributeType.NOT_SUPPORTED)
    public class ExampleRepositoryDelegate {
        @EJB
        private ExampleRepository repository;
    
        public void add(MyEntity me) {
            try {
                repository.add(me);
            } catch (ValidationException e) {
                e.printStackTrace();
            }
        }
    }
    

    并且在 servlet 中使用新的委托 bean:

    @WebServlet("/example")
    public class ExampleServlet extends HttpServlet {
        @Inject
        private ExampleRepositoryDelegate repoDelegate;
    
        protected void doGet(HttpServletRequest request, HttpServletResponse response)
                throws ServletException, IOException {
            MyEntity me = new MyEntity();
            me.setName("TooLongName");
            try {
                repoDelegate.add(me);
            } catch(Exception e) {
                System.out.println("Exception caught");
            }
        }
    }
    

请看这个解释:A clear explanation of system exception vs application exception

你必须明白处理异常和处理事务是同时发生的两件不同的事情。 系统异常无条件触发事务回滚。当你看到一个 ConstraintViolationException,这是一个系统异常,因为它扩展了 RuntimeException,它不仅仅是包装和重新抛出。一路上发生了一件坏事 - 您的交易已中止。

所以,如果异常 (ConstraintViolationException) 被捕获,那么回答第一个问题——是的,它被容器捕获了。事务被中止并抛出一个新的异常以通知应用程序代码。

您可以禁止记录这些消息,但这样您就不会知道数据持久性失败。