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

Ste*_*ins 6 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!
}
Run Code Online (Sandbox Code Playgroud)

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

TransactionAttribute未使用的全部SecondEjbTypeThirdEjbType...既没有类,也没有方法的水平.我明白这意味着secondEjb.doSomething()thirdEjb.doSomething()会为双方提供的事务中进行操作firstEjb.doSomething().

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

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


更新 - 约翰斯托克要求的其他信息如下:

  • 我在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>

Dav*_*ins 15

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

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

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

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

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


Pas*_*ent 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团队)更明确地描述了持久化上下文传播中的传播规则:

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

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

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

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

参考

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


Ste*_*ins 0

我从这里的所有答案中学到了很多东西,对人们的感谢无以言表。然而,我相信我的问题已经把水搅浑了,以至于最好从一个不同的问题开始。

从一个 EJB 跳转到下一个 EJB 并返回似乎与任何事情都没有任何关系。为了简化问题,我尝试使用与一个 EJB 完全隔离的测试用例。我采用了该secondEjb.doSomething()方法,该方法将实体持久保存到数据库中。在该方法的末尾,我添加了一个em.flush(),并尝试使用 JPA 查询再次检索该实体。

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

如果我对链接的 CodeRanch 讨论的总结是准确的,那么 JPA 就会“恶心”!:) 不管怎样,我重构了代码来完全避免这个问题。

  • 我不相信你对 Coderanch 线程的总结是准确的。未提交的数据当然在事务中的后续查询中“可见”。如果不这样做,一切都会出错,一切都行不通。 (2认同)