java 了解 EJB3/JPA 容器级事务和隔离级别
声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow
原文地址: http://stackoverflow.com/questions/4136852/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me):
StackOverFlow
Understanding EJB3/JPA container-level transactions and isolation level
提问by Steve Perkins
Consider this simplified view of some code with which I'm working:
考虑一下我正在使用的一些代码的简化视图:
@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!
}
I've set the TransactionAttribute
annotation to MANDATORY
at the class-level. I understand this to mean that all methods such as doSomething()
must be invoked within a supplied transaction. We are using container-managed transactions in this case.
我已将TransactionAttribute
注释设置为MANDATORY
类级别。我理解这意味着doSomething()
必须在提供的事务中调用所有方法。在这种情况下,我们使用容器管理的事务。
The TransactionAttribute
is not used at all in SecondEjbType
or ThirdEjbType
... neither at the class nor method level. I understand this to mean that secondEjb.doSomething()
and thirdEjb.doSomething()
will both operate within the transaction supplied for firstEjb.doSomething()
.
在TransactionAttribute
根本不被用于SecondEjbType
或ThirdEjbType
...既没有类,也没有方法的水平。我理解这意味着,secondEjb.doSomething()
并且thirdEjb.doSomething()
都将在为firstEjb.doSomething()
.
However, I'm seriously missing out on something! As indicated by the code comments... secondEjb
writes data to a database, and thirdEjb
reads that data as part of its operation. Since all of this is running within the same transaction, I would not expect there to be any issues with isolation levels. However, for whatever reason the secondEjb
database write isn't visible to thirdEjb
.
但是,我严重错过了一些东西!如代码注释所示...secondEjb
将数据写入数据库,并thirdEjb
在其操作过程中读取该数据。由于所有这些都在同一个事务中运行,因此我不希望隔离级别存在任何问题。 但是,无论出于何种原因,secondEjb
数据库写入对thirdEjb
.
I've turned tracing all the way up to the max, and there's apparently not an exception or error or rollback at issue... the initial write simply isn't visible to the subsequent read. I don't claim to be the world's greatest guru in transaction management... have I missed something obvious, or is my conceptual understanding basically correct and the issue may lie elsewhere?
我已经将跟踪一直到最大值,并且显然没有异常或错误或回滚问题......初始写入对于后续读取根本不可见。我并没有声称自己是世界上最伟大的事务管理大师……我是否遗漏了一些明显的东西,或者我的概念理解是否基本正确,问题可能出在其他地方?
UPDATE - Additional information requested by johnstok below:
更新 - 以下 johnstok 要求的其他信息:
- I am running in GlassFish
- I'm not sure what you mean by "non-standard flush mode", so I assume the answer is no.
- My persistence.xml file looks like this:
- 我在 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>
<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>
采纳答案by Steve Perkins
I've learned a ton from all the answers here, and can't thank people enough. However, I believe that my question has muddied the waters to a point where it might be better to start over with a different question.
我从这里的所有答案中学到了很多东西,对人们的感激不尽。但是,我相信我的问题已经把水搅浑到了最好从一个不同的问题重新开始的地步。
It doesn't appear that jumping from one EJB to the next and back has anything to do with anything. To simplify matters, I tried working with a test case completely isolated to one EJB. I went into that secondEjb.doSomething()
method, which persists an entity to the database. At the end of the method, I added an em.flush()
, and tried retrieving the entity again with a JPA query.
从一个 EJB 跳转到下一个并返回似乎与任何事情无关。为了简化问题,我尝试使用与一个 EJB 完全隔离的测试用例。我进入了那个secondEjb.doSomething()
方法,它将一个实体持久化到数据库中。在该方法的末尾,我添加了一个em.flush()
,并尝试使用 JPA 查询再次检索实体。
Even though I was still in the exact same method where the entity had just been persisted, it was not visible to that subsequent query. I've done some additional research elsewhere, and it looks like this may simply be the normal mode of isolation for JPA in a transactional context. One a transaction is started, additional queries within that transaction don't yet have visibility to uncommitted data.
即使我仍然使用刚刚持久化实体的完全相同的方法,它对随后的查询是不可见的。我在别处做了一些额外的研究,看起来这可能只是事务上下文中 JPA 的正常隔离模式。一个事务开始后,该事务中的其他查询尚无法查看未提交的数据。
If my summary of the linked CodeRanch discussion is accurate, then "yuck" on JPA! :) Either way, I've refactored the code to avoid this issue altogether.
如果我对链接的 CodeRanch 讨论的总结是准确的,那么 JPA 上的“糟糕”!:) 无论哪种方式,我都重构了代码以完全避免这个问题。
回答by David Blevins
First thing to check is that bean two and three use @PersistenceContext EntityManager
to get the EntityManager and not@PersistenceUnit EntityManagerFactory
followed by a createEntityManager()
call.
要检查的第一件事是,豆二和三和使用@PersistenceContext EntityManager
得到了EntityManager并没有@PersistenceUnit EntityManagerFactory
后跟一个createEntityManager()
电话。
Second, check that the DataSource
is actually setup to participate in JTA transactions (autoCommit or related properties should be off).
其次,检查DataSource
是否实际设置为参与 JTA 事务(应关闭 autoCommit 或相关属性)。
Lastly, a quick and dirty way to check your propagation is to call the EntityManager.getDelegate()
method and check the resulting object is the samethroughout the expected transaction scope.
最后,检查传播的一种快速而肮脏的方法是调用该EntityManager.getDelegate()
方法并检查结果对象在整个预期事务范围内是否相同。
Here's how things work under the covers.... The EntityManager
injected into your bean when it is created is a fake, a simple facade. When you attempt to use the EntityManager reference in a transaction, the container will actually go digging in the current transaction, find the realEntityManager that is stashed in the transaction scope and delegate your call to thatEntityManager (if there is no EntityManager already in the transaction, the container will create one and add it). This realobject will be the value of getDelegate()
. If the value of getDelegate()
is not the same (==) in secondEjb.doSomething()
and in thirdEjb.doSomething()
then you are not getting the expected propagation and each is talking to a different persistence context.
下面是事情在幕后的工作方式....EntityManager
创建时注入到您的 bean 中的是一个假的、一个简单的外观。当您尝试在事务中使用 EntityManager 引用时,容器实际上会在当前事务中进行挖掘,找到隐藏在事务范围中的真实EntityManager 并将您的调用委托给该EntityManager(如果没有 EntityManager 已经存在于事务范围内)事务,容器将创建一个并添加它)。这个真实的对象将是 的值getDelegate()
。如果getDelegate()
insecondEjb.doSomething()
和 in的值不相同 (==),thirdEjb.doSomething()
那么您将无法获得预期的传播,并且每个都在与不同的持久性上下文交谈。
On a side note, beware that applying MANDATORY on the class actually only affects methods defined in that exact class, not in super classes. If you do not specify an @TransactionAttribute on a super class, those methods use the default regardless of how subclasses maybe annotated. I only mention that as it may have an impact on your understanding of your code.
附带说明一下,请注意,在类上应用 MANDATORY 实际上只会影响该类中定义的方法,而不是超类中定义的方法。如果未在超类上指定 @TransactionAttribute,则无论子类如何注释,这些方法都使用默认值。我只提到这一点,因为它可能会影响您对代码的理解。
回答by Pascal Thivent
I've set the TransactionAttribute annotation to MANDATORY at the class-level. I understand this to mean that all methods such as doSomething() must be invoked within a supplied transaction. We are using container-managed transactions in this case.
我已在类级别将 TransactionAttribute 注释设置为 MANDATORY。我理解这意味着必须在提供的事务中调用所有方法,例如 doSomething()。在这种情况下,我们使用容器管理的事务。
Using MANDATORY
at the class level means that the container should throw an exception to the caller if there is no transaction in progress when any method of FirstEjbType
is called.
使用MANDATORY
在类级别意味着容器应抛出异常给调用者如果没有交易正在进行中,当任何方法FirstEjbType
被调用。
Out of curiosity, who is initiating the transaction then? Is there a particular reason to not use the default REQUIRED
?
出于好奇,那么谁在发起交易?是否有特殊原因不使用默认值REQUIRED
?
The TransactionAttribute is not used at all in SecondEjbType or ThirdEjbType... neither at the class nor method level. I understand this to mean that secondEjb.doSomething() and thirdEjb.doSomething() will both operate within the transaction supplied for firstEjb.doSomething()
TransactionAttribute 在 SecondEjbType 或 ThirdEjbType 中根本没有使用......无论是在类还是方法级别。我理解这意味着 secondEjb.doSomething() 和thirdEjb.doSomething() 都将在为 firstEjb.doSomething() 提供的事务中运行
This means that the default transaction attribute is used, i.e. REQUIRED
, and a REQUIRED
method is guaranteed to be executed within a transaction (the container would start one ifrequired).
这意味着使用默认事务属性,即REQUIRED
,REQUIRED
保证在事务中执行一个方法(如果需要,容器将启动一个)。
So in your case, secondEjb.doSomething()
and thirdEjb.doSomething()
should be indeed executed within the transaction of firstEjb.doSomething()
.
所以你的情况,secondEjb.doSomething()
并thirdEjb.doSomething()
应确实的事务内执行firstEjb.doSomething()
。
have I missed something obvious, or is my conceptual understanding basically correct and the issue may lie elsewhere?
我是否遗漏了一些明显的东西,或者我的概念理解是否基本正确并且问题可能出在其他地方?
The rule of thumb for (transaction-scoped) persistence context propagation is that the persistence context propagates as the JTA transaction propagates. But there are some restrictions. The JPA specification puts it like this:
(事务范围内的)持久性上下文传播的经验法则是,持久性上下文随着 JTA 事务的传播而传播。但是有一些限制。JPA 规范是这样写的:
5.6.3 Persistence Context Propagation
As described in section 5.1, a single persistence context may correspond to one or more JTA entity manager instances (all associated with the same entity manager factory).
The persistence context is propagated across the entity manager instances as the JTA transaction is propagated.
Propagation of persistence contexts only applies within a localenvironment. Persistence contexts are not propagated to remote tiers.
5.6.3 持久化上下文传播
如第 5.1 节所述,单个持久性上下文可能对应一个或多个 JTA 实体管理器实例(都与同一个实体管理器工厂相关联)。
随着 JTA 事务的传播,持久性上下文在实体管理器实例中传播。
持久性上下文的传播仅适用于本地环境。持久性上下文不会传播到远程层。
And Sahoo (from the GlassFish team) writes more explicitly about the propagation rules in Persistence Context propagation:
Sahoo(来自 GlassFish 团队)在持久上下文传播中更明确地描述了传播规则:
3. (rule #3) Don't expect PC to be propagated when you call a remote EJB even when the remote EJB happens to be running in the same JVM or part of the same application.
3.(规则#3)即使远程EJB 恰好在同一JVM 或同一应用程序的一部分中运行,也不要期望在调用远程EJB 时PC 会被传播。
So, assuming you're using JPA everywhere, I would expect business methods called within the same transaction to inherit the same persistence context only if you aren't using Remote
interfaces(I don't know if this is a GlassFish specific interpretation of the spec but Sahoo is pretty clear about this restriction).
因此,假设您在任何地方都使用 JPA,我希望在同一事务中调用的业务方法只有在您不使用Remote
接口时才能继承相同的持久性上下文(我不知道这是否是 GlassFish 对规范的特定解释)但 Sahoo 对这个限制非常清楚)。
PS: JPA assumes a READ_COMMITTED
isolation level (so optimistic locking can work) and standard JPA does not allow custom settings of isolation levels (well, some providers do allow to change it either globally or per request but that's non portable).
PS:JPA 假定一个READ_COMMITTED
隔离级别(因此乐观锁定可以工作)并且标准 JPA 不允许隔离级别的自定义设置(好吧,一些提供者确实允许全局或每个请求更改它,但这是不可移植的)。
PPS: I'm not convinced isolation levels are the key to your problem here.
PPS:我不相信隔离级别是这里问题的关键。
References
参考
- JPA 1.0 Specification
- Section 5.6.3 "Persistence Context Propagation"
- JPA 1.0 规范
- 第 5.6.3 节“持久化上下文传播”
回答by axtavt
Since jpais mentioned in the tags, I guess that persistence context is not flushed before calling thirdEjb
methods, so changes are not written to the database.
由于标签中提到了jpa,我猜在调用thirdEjb
方法之前没有刷新持久化上下文,因此不会将更改写入数据库。
By default, JPA persistence context is flushed before commit, before execution of a JPA query or manually with em.flush()
. So, visibility of changes depends on the data access method used in thirdEjb
- if data are read, for example, using JDBC, changes shouldn't be visible without em.flush()
.
默认情况下,JPA 持久化上下文在提交之前、执行 JPA 查询之前或手动使用em.flush()
. 因此,更改的可见性取决于 中使用的数据访问方法thirdEjb
- 如果读取数据,例如,使用 JDBC,更改不应该在没有em.flush()
.
回答by johnstok
You'll need to provide more information to get this question answered.
您需要提供更多信息才能回答此问题。
- Are you running in a Java EE container?
- Have you set a non-standard flush mode?
- Can you post your persistence.xml file?
- 您是否在 Java EE 容器中运行?
- 您是否设置了非标准冲洗模式?
- 你能发布你的persistence.xml文件吗?
Be aware that 'persistence context' and transaction lifecycle may not be the same.
请注意,“持久性上下文”和事务生命周期可能不同。
Transactions
交易
Per EJB 3.0 the default transaction attribute for all EJB 3.0 applications is REQUIRED
. The doc's for transaction type are here: http://download.oracle.com/javaee/6/api/javax/ejb/TransactionAttributeType.html
对于 EJB 3.0,所有 EJB 3.0 应用程序的默认事务属性都是REQUIRED
. 交易类型的文档在这里:http: //download.oracle.com/javaee/6/api/javax/ejb/TransactionAttributeType.html
Are you perhaps using the REQUIRES_NEW
transaction type which will operate in a separate transaction?
您是否正在使用REQUIRES_NEW
将在单独事务中运行的事务类型?
In particular, try using a local rather than remote interface for the 2nd and 3rd EJBs.
特别是,尝试为第二个和第三个 EJB 使用本地而不是远程接口。
Flushing
冲洗
With default settings a flush is forced before a query (if needed) to ensure results are correct: http://download.oracle.com/javaee/5/api/javax/persistence/FlushModeType.html
使用默认设置,在查询(如果需要)之前强制刷新以确保结果正确:http: //download.oracle.com/javaee/5/api/javax/persistence/FlushModeType.html
Try calling entityManager.setFlushMode(FlushModeType.AUTO);
to make sure a flush happens before your query. Enable SQL logging in your JPA provider to be sure that the updates / inserts are really being sent to the DB prior to the select.
尝试调用entityManager.setFlushMode(FlushModeType.AUTO);
以确保在您的查询之前发生刷新。在您的 JPA 提供程序中启用 SQL 日志记录以确保更新/插入确实在选择之前发送到数据库。