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

时间:2010-11-09 17:54:44

标签: java jpa transactions java-ee ejb-3.0

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

@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()。在这种情况下,我们使用容器管理的事务。

TransactionAttributeSecondEjbType中根本没有使用ThirdEjbType ...在类和方法级别都没有。我理解这意味着secondEjb.doSomething()thirdEjb.doSomething()都将在为firstEjb.doSomething()提供的交易中运作。

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

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


更新 - johnstok在下面要求的其他信息:

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

<persistence xmlns="http://java.sun.com/xml/ns/persistence" version="1.0">
<persistence-unit name="pu" transaction-type="JTA">
<jta-data-source>jdbc/datasource</jta-data-source>
<exclude-unlisted-classes>false</exclude-unlisted-classes>
<properties>
<property name="toplink.cache.shared.default" value="false"/>
</properties>
</persistence-unit>
</persistence>

5 个答案:

答案 0 :(得分:15)

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

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

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

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

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

答案 1 :(得分:12)

  

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

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

出于好奇,谁开始交易呢?有没有特别的理由不使用默认的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团队)在Persistence Context propagation中更明确地写了关于传播规则的内容:

  

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

所以,假设你到处都在使用JPA,我希望在同一个事务中调用的业务方法只能继承相同的持久化上下文如果你没有使用Remote接口(我不知道这是否是GlassFish对规范的具体解释,但Sahoo对此限制非常清楚)。

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

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

参考

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

答案 2 :(得分:2)

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

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

答案 3 :(得分:1)

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

  • 您是否在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日志记录,以确保在选择之前确实将更新/插入发送到数据库。

答案 4 :(得分:0)

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

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

即使我 仍然使用刚刚保留实体的完全相同的方法 ,但后续查询无法看到它。我已经完成了some additional research elsewhere,看起来这可能只是事务上下文中JPA的正常隔离模式。启动一个事务,该事务中的其他查询尚未查看未提交的数据。

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