了解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()
。 在这种情况下,我们使用容器管理的事务。
在SecondEjbType
或ThirdEjbType
中SecondEjbType
不使用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”! :)无论哪种方式,我都重构了代码以完全避免这个问题。