Java EJB拦截器和事务生命周期或如何拦截提交/失败事件?

Java EJB拦截器和事务生命周期或如何拦截提交/失败事件?,java,jakarta-ee,ejb,cdi,Java,Jakarta Ee,Ejb,Cdi,我有一个EJB拦截器,我遵循Adam Bien建议的BCE模式,也就是说,边界上的所有EJB调用都启动并完成一个事务,这意味着没有嵌套的EJB调用(虽然可能有嵌套的CDI注入Bean调用,但这些调用应该在EJB边界上启动的同一个事务中) 所以在这些ejb边界中,我有一个拦截器,我想截取或者知道在ejb的方法调用之后,是否已经提交了事务?(也就是说,如果涉及EntityManager,则提交sql调用被发送到DB并成功返回) 我会从拦截器里得到信息吗 如果没有,如何获得成功提交或失败的事务的通知

我有一个EJB拦截器,我遵循Adam Bien建议的BCE模式,也就是说,边界上的所有EJB调用都启动并完成一个事务,这意味着没有嵌套的EJB调用(虽然可能有嵌套的CDI注入Bean调用,但这些调用应该在EJB边界上启动的同一个事务中)

所以在这些ejb边界中,我有一个拦截器,我想截取或者知道在ejb的方法调用之后,是否已经提交了事务?(也就是说,如果涉及EntityManager,则提交sql调用被发送到DB并成功返回)

  • 我会从拦截器里得到信息吗
  • 如果没有,如何获得成功提交或失败的事务的通知 注意:当然,如果我是EJB的客户机,并且我正在调用该方法,那么在方法调用之后,我知道事务发生了什么,,但是我感兴趣的是在客户机接收到EJB的响应之前截取它

    @AroundInvoke
    public Object logMethodEntry(InvocationContext ctx) throws Exception {
        Object proceed = null;
        try {
            proceed = ctx.proceed();
            // is the transacction finished/commited already?
            // is it still open ?
            return proceed;
        } catch (Exception e) {
            throw e;
        }
    }
    

    [更新]:我接受了一个很好的答案,但问题是在Java EE中无法接收已提交事务的事件。因此,不管答案如何,遗憾的是,在服务器内部,Java EE中无法通知已完成的事务,当然,如果您是客户端调用方,那么您肯定知道提交或回滚的事务…

    除非在抛出的异常上另有说明,否则如果ejb方法调用抛出异常,应将其回滚。此外,如果对DB的所有调用都在同一交易中,则应在交易周期结束时视为已提交

    回想起来,所有拦截器都是在同一个事务中调用的,在该事务中它拦截的ejb方法被调用(这就是拦截器在发生异常时决定回滚或仍然提交事务的原因)

    因此,您可以肯定地知道,如果在您的侦听器调用中,在调用并返回继续之后,没有抛出异常,并且有可能回滚事务,那么事务将成功完成

    因此,在您的场景中:

    @AroundInvoke
    public Object logMethodEntry(InvocationContext ctx) throws Exception {
        Object proceed = null;
        try {
            proceed = ctx.proceed();
            // is the transacction finished/commited already?
            // The transaction is successful, but afaik, it is not yet committed, until this method returns successfully
            // is it still open ? More or less. You can still grab the Ejbtransaction and commit it manually or rollback if some other conditions have not been met yet
            return proceed;
        } catch (Exception e) {
            //If this happens, and you propagate it, then for sure the transaction will be rolledback, and never get committed. Since all db calls were being done within this transaction, then no DB commit will be done.
            throw e;
        }
    }
    
    编辑: 要在拦截器中实际提交事务,您需要运行应用程序管理的事务,否则,EJB规范禁止在容器管理的事务上调用commit,您当然可以调用EJBContext的setOnrollback方法。 编辑 如果您真的想做一些DB更改,我建议您:

  • 用户应用程序管理的事务,从中手动启动 并在拦截器内提交事务
  • 使用观察者的概念,并监听@Observes(成功后)事件,该事件将在 事务已成功提交并完成,因此您 可以保证执行db调用,并且新的更新将 可用
  • 如果您可以忽略BCE模式,并派生一个新事务来执行更新,那么在它成功返回后,您将保证提交,然后正常继续
  • ```

    @无状态
    公共类事务服务{
    @TransactionAttribute(需要新的)
    公共对象executeTransaction(最终可调用任务){
    返回task.call();
    }
    }
    @拦截器
    公共类MyInterceptor{
    @EJB
    私人交易服务;
    @阿隆登沃克
    公共对象logMethodEntry(InvocationContext ctx)引发异常{
    对象继续=空;
    试一试{
    继续=service.executeTransaction(()->ctx.procedure());
    //如果你到达这里,你将保证提交,然后你可以做弹性搜索更新
    返回并继续;
    }捕获(例外e){
    //如果发生这种情况,并且您传播了它,那么可以确保事务将回滚,并且永远不会被提交。因为所有的db调用都是在此事务中完成的,所以不会执行db提交。
    投掷e;
    }
    }
    }
    
    好的-这个问题已经问了4年了,但我认为给出答案还是有意义的。 您当然可以注册回调以获得有关事务结果的信息。您只需使用javax.transaction.transaction的registerSynchronization()API

    Transaction tx = ((TransactionManager) (new InitialContext()).lookup("java:/TransactionManager")).getTransaction();
    tx.registerSynchronization(new Synchronization() {
                public void beforeCompletion() {
                   // do stuff before completion
                }
    
                public void afterCompletion(int status) {
                    if (status == Status.STATUS_COMMITTED) {
                        // do something after successful commit                    }
                }
            });
    

    谢谢你的回答,但我需要知道我是否能抓到
    提交的
    事件,而不是
    肯定会在几毫秒内提交的
    事件。可能吗?因为基于提交的事件,我需要在DB上进行读取以更新ElasticSearch文档,我不能依赖于
    将提交的
    ,因为如果读取数据库的代码发生在
    实际提交
    事件之前,那么ElasticSearch将无法获得更新的文档。也许我把事情搞砸了,这样做会容易些……我想你可能误解了亚当·宾。您写道“边界上的所有EJB调用都启动并完成一个事务,这意味着没有嵌套的EJB调用”,但事实并非如此。您可以嵌套EJB调用,除非您特别重写它,否则调用将在同一事务中进行。无论如何,我不认为BCE意味着不能在一个边界调用中使用多个事务。最后一个提示:
    AroundInvoke
    调用发生在与方法相同的事务中,因此您问题中的代码注释无法实现。我还意识到您可以对CDI事件执行相同的操作。如果可能,我宁愿让容器执行查找:
    @Resource(lookup=“java:/TransactionManager”)TransactionManager TransactionManager
    
    Transaction tx = ((TransactionManager) (new InitialContext()).lookup("java:/TransactionManager")).getTransaction();
    tx.registerSynchronization(new Synchronization() {
                public void beforeCompletion() {
                   // do stuff before completion
                }
    
                public void afterCompletion(int status) {
                    if (status == Status.STATUS_COMMITTED) {
                        // do something after successful commit                    }
                }
            });