了解EJB3 / JPA容器级事务和隔离级别

考虑一下我正在使用的一些代码的简化视图:

@Stateless(...) @Remote(...) @TransactionAttribute(TransactionAttributeType.MANDATORY) public class FirstEjbType { @EJB(...) private SecondEjbType secondEjb; @EJB(...) private ThirdEjbType thirdEjb; public void doSomething() { secondEjb.doSomething(); // WRITES SOMETHING TO THE DATABASE thirdEjb.doSomething(); // CAN'T SEE THAT SOMETHING IN THE DATABASE! } 

我已经在类级别将TransactionAttribute注释设置为MANDATORY 。 我理解这意味着必须在提供的事务中调用所有方法,如doSomething() 。 在这种情况下,我们使用容器管理的事务。

SecondEjbTypeThirdEjbTypeSecondEjbType不使用TransactionAttribute ……在类和方法级别都没有。 我理解这意味着secondEjb.doSomething()thirdEjb.doSomething()都将在为firstEjb.doSomething()提供的事务中运行。

但是,我真的错过了什么 ! 如代码注释所示…… secondEjb将数据写入数据库, thirdEjb将该数据作为其操作的一部分进行读取。 由于所有这些都在同一个事务中运行,我不希望隔离级别存在任何问题。 但是,无论出于何种原因, secondEjb数据库写入对thirdEjb不可见。

我已经将跟踪一直转到最大值,并且显然没有exception或错误或回滚问题……初始写入对于后续读取是不可见的。 我并不是自称是世界上最伟大的交易管理大师……我是否错过了一些明显的东西,或者我的概念理解是否基本正确,问题可能在其他地方?


更新 – johnstok要求的其他信息如下:

  • 我在GlassFish中运行
  • 我不确定你的意思是“非标准冲洗模式”,所以我认为答案是否定的。
  • 我的persistence.xml文件如下所示:



jdbc/datasource
false




要检查的第一件事是bean 2和3使用@PersistenceContext EntityManager获取EntityManager而不是 @PersistenceUnit EntityManagerFactory后跟createEntityManager()调用。

其次,检查DataSource是否实际设置为参与JTA事务(autoCommit或相关属性应该关闭)。

最后,检查传播的快速而脏的方法是调用EntityManager.getDelegate()方法并检查结果对象整个预期的事务范围内是否相同

这里的内容是如何工作的……在创建bean时,注入到bean中的EntityManager是一个假的,简单的外观。 当您尝试在事务中使用EntityManager引用时,容器实际上将在当前事务中挖掘,找到在事务范围中存储的真实 EntityManager并将您的调用委托给 EntityManager(如果在该事务中已经没有EntityManager)事务,容器将创建一个并添加它)。 这个真实对象将是getDelegate()的值。 如果getDelegate()的值在secondEjb.doSomething()thirdEjb.doSomething()不相同(==),那么您没有获得预期的传播,并且每个都在与不同的持久化上下文进行通信。

另外,请注意在类上应用MANDATORY实际上只影响在该类中定义的方法,而不是在超类中。 如果未在超类上指定@TransactionAttribute,则无论子类如何注释,这些方法都使用默认值。 我只提到它,因为它可能会影响您对代码的理解。

我已经在类级别将TransactionAttribute注释设置为MANDATORY。 我理解这意味着必须在提供的事务中调用所有方法,如doSomething()。 在这种情况下,我们使用容器管理的事务。

在类级别使用MANDATORY意味着如果在调用FirstEjbType任何方法时没有正在进行的事务,容器应该向调用者抛出exception。

出于好奇,谁开始交易呢? 有没有特别的理由不使用默认的REQUIRED

在SecondEjbType或ThirdEjbType中根本不使用TransactionAttribute ……在类和方法级别都没有。 我理解这意味着secondEjb.doSomething()和thirdEjb.doSomething()都将在为firstEjb.doSomething()提供的事务中运行

这意味着使用了默认事务属性,即REQUIRED ,并且保证在事务中执行REQUIRED方法( 如果需要,容器将启动一个)。

所以在你的情况下, secondEjb.doSomething()thirdEjb.doSomething()应该确实在firstEjb.doSomething()的事务中执行。

我错过了一些明显的东西,或者我的概念理解基本正确,问题可能在其他地方?

(事务范围)持久性上下文传播的经验法则是持久化上下文在JTA事务传播时传播。 但是有一些限制。 JPA规范如下:

5.6.3持久化上下文传播

如5.1节所述,单个持久化上下文可以对应于一个或多个JTA实体管理器实例(全部与同一实体管理器工厂相关联)。

随着JTA事务的传播,持久化上下文在实体管理器实例中传播。

持久化上下文的传播仅适用于本地环境。 持久性上下文不会传播到远程层。

而Sahoo(来自GlassFish团队)更明确地写了关于持久化上下文传播中的传播规则:

3.(规则#3)即使远程EJB恰好在同一个JVM或同一个应用程序的一部分中运行,也不要指望在调用远程EJB时传播PC。

因此,假设您在任何地方都使用JPA,我希望在同一事务中调用的业务方法只有在您不使用Remote接口时才会inheritance相同的持久性上下文(我不知道这是否是对GlassFish的特定解释规范,但Sahoo非常清楚这种限制)。

PS:JPA假设一个READ_COMMITTED隔离级别(因此乐观锁定可以工作),标准JPA不允许自定义隔离级别设置(好吧,一些提供商允许全局或按请求更改它,但这是非可移植的)。

PPS: 不相信隔离级别是你问题的关键。

参考

  • JPA 1.0规范
    • 第5.6.3节“持久性上下文传播”

由于标签中提到了jpa ,我想在调用thirdEjb方法之前不会刷新持久化上下文,因此更改不会写入数据库。

默认情况下,JPA持久性上下文在提交之前,执行JPA查询之前或使用em.flush()手动刷新。 因此,更改的可见性取决于thirdEjb使用的数据访问方法 – 如果读取数据(例如,使用JDBC),则不应在没有em.flush()情况下显示更改。

您需要提供更多信息才能回答此问题。

  • 您是否在Java EE容器中运行?
  • 您是否设置了非标准冲洗模式?
  • 你可以发布你的persistence.xml文件吗?

请注意,“持久性上下文”和事务生命周期可能不同。

交易

根据EJB 3.0,所有EJB 3.0应用程序的缺省事务属性都是REQUIRED 。 交易类型的文档位于: http : //download.oracle.com/javaee/6/api/javax/ejb/TransactionAttributeType.html

您是否正在使用REQUIRES_NEW交易类型,该交易类型将在单独的交易中运作?

特别是,尝试为第二个和第三个EJB使用本地而不是远程接口

法拉盛

使用默认设置,在查询之前强制刷新(如果需要)以确保结果正确: http : //download.oracle.com/javaee/5/api/javax/persistence/FlushModeType.html

尝试调用entityManager.setFlushMode(FlushModeType.AUTO); 确保在查询之前发生刷新。 在JPA提供程序中启用SQL日志记录,以确保在选择之前确实将更新/插入发送到数据库。

我从这里的所有答案中学到了很多,并且不能感谢别人。 但是,我认为我的问题已经弄得一团糟,可能会更好地重新开始一个不同的问题。

从一个EJB跳到另一个EJB并没有看起来与任何事情有任何关系。 为了简化问题,我尝试使用与一个EJB完全隔离的测试用例。 我进入了secondEjb.doSomething()方法,该方法将实体持久化到数据库。 在方法结束时,我添加了一个em.flush() ,并尝试使用JPA查询再次检索实体。

即使我仍然使用刚刚保持实体的完全相同的方法 ,但后续查询不可见。 我在其他地方做了一些额外的研究 ,看起来这可能只是事务上下文中JPA的正常隔离模式。 启动一个事务,该事务中的其他查询尚未查看未提交的数据。

如果我对链接的CodeRanch讨论的总结是准确的,那么在JPA上“yuck”! :)无论哪种方式,我都重构了代码以完全避免这个问题。