【问题标题】:EJB Interceptors and transaction lifecycle OR how to intercept a commit/failure event?EJB 拦截器和事务生命周期或如何拦截提交/失败事件?
【发布时间】:2015-03-02 07:26:33
【问题描述】:

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

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

  1. 我会从拦截器中获取该信息吗?
  2. 如果没有,我如何才能收到有关成功提交或失败的事务的通知?

注意:当然,如果我是 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 中无法在服务器内部通知已完成事务,当然,如果您是客户端调用者,那么您肯定知道事务已提交或回滚......

【问题讨论】:

  • 我想你可能误会了 Adam Bien。您写道“边界上的所有 EJB 调用都开始并完成一个事务,这意味着没有嵌套的 EJB 调用”,但这不是真的。您可以嵌套 EJB 调用,除非您专门覆盖它,否则调用将发生在同一个事务中。无论如何,我不认为 BCE 意味着您不能在单个边界调用中使用多个事务。最后一个提示:AroundInvoke 调用发生在与方法相同的事务中,因此无法实现您问题中的代码注释。

标签: java jakarta-ee ejb cdi


【解决方案1】:

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

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

因此,您可以肯定地知道,如果在您的拦截器调用中,在调用并返回proceed之后,没有抛出异常,并且有可能发生事务回滚,则事务成功完成。

所以在你的场景中:

@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 规范禁止在容器管理的事务上调用提交,您当然可以调用 EJBContext 的 setOnrollback 方法. 编辑 如果您真的想做一些数据库更改,我建议:

  1. 用户 ApplicationManaged 事务,您可以从中手动启动 并在拦截器中提交事务
  2. 使用观察者的概念,监听@Observes(AFTER_SUCCESS) 事件,当 事务已成功提交并完成,因此您 可以保证做一个db调用,新的更新将是 可用。
  3. 如果你可以忽略BCE模式,分拆一个新的事务来做更新,这样在它返回成功后,你就可以保证提交,然后正常继续

```

@Stateless
public class TransactionService {

   @TransactionAttribute(REQUIRES_NEW)
   public Object executeTransaction(final Callable<Object> task) {
       return task.call();
   }
}

@Interceptor
public class MyInterceptor {

  @EJB
  private TransactionService service;

   @AroundInvoke
    public Object logMethodEntry(InvocationContext ctx) throws Exception {
        Object proceed = null;
        try {
            proceed = service.executeTransactional(()->ctx.proceed());
            //If you reach here, you will be guaranteed of commit and then you can do the elastic search update
            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;
        }
    }
}

【讨论】:

  • 感谢您的回答,但我需要知道是否可以获取commited 事件,而不是will be commited for sure in a couple of milliseconds 事件。那可能吗 ?因为基于提交的事件,我需要读取数据库以更新 ElasticSearch 文档,而我不能依赖 will be commited,因为如果读取数据库的代码发生在 actual commit 事件之前,那么 ElasticSearch 不会获取更新的文档。也许我搞砸了一些事情,这可以更容易地完成......
【解决方案2】:

好的 - 这个问题现在已经 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                    }
            }
        });

【讨论】:

  • 我也意识到你可以对 CDI 事件做同样的事情
  • 如果可能的话,我宁愿让容器做查找:@Resource(lookup = "java:/TransactionManager") TransactionManager transactionManager;
猜你喜欢
  • 2019-06-04
  • 1970-01-01
  • 2014-03-10
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多