entitymanager
JPA规范定义了几种类型的EntityManagers / Persistence Context。 我们可以有:
- 扩展和事务范围的EntityManager,
- 容器管理或应用程序管理的EntityManager。
- JTA或本地资源EntityManager,
除了上述区别之外,我们还可以在其中存在EntityManager / Persistence Context的两个主要上下文– Java EE和Java SE 。 并非每个选项都适用于Java EE,并且并非每个选项都适用于Java SE。 在本文的其余部分中, 我将介绍Java EE环境 。
好的,因此在继续本文之前的主题之前(这是使用EntityManagerFactory手动创建的Java EE中EntityManager的行为),我们仅简要介绍一下上述EM类型。
扩展与事务范围
此功能告诉我们EntityManager的操作是否可能跨越多个事务。 默认情况下,使用事务性持久性上下文,这意味着当提交当前事务时,将清除所有更改,并分离所有受管实体。 扩展范围仅适用于有状态EJB。 这非常有意义,因为SFSB可以保存状态,因此不必终止一种业务方法就意味着结束事务。 使用SLSB的情况就不同了–我们有业务方法必须在业务方法完成时结束,因为在下一次调用中,我们不知道我们将以哪个EJB实例结束。 SLSB仅允许使用事务范围的EntityManager。 您可以控制在EntityManager注入期间EntityManager是扩展的还是事务的:
@PersistenceContext(type=javax.persistence.PersistenceContextType.EXTENDED)
EntityManager em;
默认情况下为javax.persistence.PersistenceContextType.TRANSACTION
。 附带说明–使用扩展的EntityManager可能使您可以创建一些有趣的解决方案; 看看具有事务性保存方法技巧的 Adam Bien的无事务bean 。 他使用这种方法使事务开始和结束时自动清除所有更改(并且他实际上通过调用特殊的人工方法来做到这一点。)仅在容器管理的EntityManagers的情况下才允许扩展和事务范围的PersistenceContext。
容器管理与应用程序管理
在大多数Java EE应用程序中,您只是使用@PersistenceContext注入EntityManager,如下所示:
@PersistenceContext
EntityManager em;
实际上,这意味着您要让容器为您的EntityManager注入 (容器是在后台从EntityManagerFactory创建的。)这意味着EntityManager是由容器管理的。 另外,您可以从EntityManagerFactory自己创建EntityManager。 您可以通过注入获得它:
@PersistenceUnit
EntityManagerFactory emf;
然后,要获取EntityManager,您需要调用emf.createEntityManager()
。 这就是–您现在正在使用应用程序管理的EntityManager 。 应用程序(您)负责创建和删除EntityManager。 每个应用程序管理的持久性上下文都有扩展的范围。
如果要控制已创建的EM,则可以使用-例如,如果要为基础JPA实现程序设置一些属性,或者只是在业务方法将其投入使用之前插入自己。 但是,您将需要跨事务涉及的多个bean移动创建的EntityManager –容器不会为您这样做,并且每次调用emf.createEntityManager()
您都在创建一个与新PersistenceContext连接的EntityManager。 。 您可以使用CDI进行EntityManager的共享,但这是最后一节之一的主题。
JTA与本地资源
此属性定义您是要JTA管理EntityManager的事务,还是要使用其直接API开始和提交。 如果您使用的是容器管理的EntityManager,则自动意味着您必须使用JTA EntityManager 。 如果您使用的是应用程序管理的EntityManager,则可以使用JTA或本地资源。 在现实生活中,这意味着,如果您使用的是JTA EntityManager,则您只需照顾更高级别的事务管理:
- 声明性地 使用注释或XML JTA交易属性,或
- 以编程方式 使用
javax.transaction.UserTransaction
。
如果您使用的是本地资源EntityManager,则需要更深入一点,并使用EntityManager.getTransaction()
返回javax.persistence.EntityTransaction
并调用commit(-)
, begin(-)
, rollback()
等。使用transaction-type
属性在persistence.xml
定义此功能:
<?xml version='1.0' encoding='UTF-8'?>
<persistence ...>
<persistence-unit transaction-type='RESOURCE_LOCAL' ... >
</persistence>
另一个可能的值(当未定义transaction-type时为默认值)是JTA 。
Java EE中由应用程序管理的JTA EntityManager
尽管此字幕听起来很复杂,但了解EntityManager和PersistenceContexts的所有先前类型后,您应该确切了解if指的是什么。
- “应用程序托管”意味着我们将注入@PersistenceUnit EntityManagerFactory而不是EntityManager,
- 之所以称为 “ Java EE”,是因为这些示例(在github上发布)仅在Java EE Application Server中使用 ,
- “ JTA”,因为我们将使用JTA事务级别,因此不会使用
javax.persistence.EntityTransaction
。
现在,您需要了解的第一件事是如何使用JTA事务。 JTA事务管理有两种类型-容器(CMT)和Bean管理(BMT)。 容器管理的JTA事务 ( CMT )意味着您使用javax.ejb.TransactionAttribute
定义tx是否应处于活动状态以及事务边界在何处。 这是默认的JTA管理类型。 或者,您可以选择自己划定JTA事务的边界。 这称为Bean管理的JTA事务 ( BMT )。应用程序(您)负责启动,回滚或提交事务。 您如何控制JTA交易? 您可以使用javax.transaction.UserTransaction
做到这一点。
您如何获得一个? 好吧,至少有3种方法可以做到这一点:
- 它绑定到组件私有名称空间(
java:comp/UserTransaction
)中的JNDI,因此您可以使用InitialContext
或SessionContext
对其进行查找, - 您可以使用
SessionContext
进行访问–SessionContext#getUserTransaction()
, - 因为它绑定在众所周知的JNDI名称中,所以可以让容器使用以下方式注入它:
@Resource UserTransaction utx;
。
如果您有UserTransaction
,则可以开始划分事务中要执行的内容。 请注意,您仍在控制JTA事务 –您甚至都没有接触EntityManager的资源本地事务。
EntityManager何时在JTA事务中?
如果没有前面的介绍,您可能会认为由应用程序管理的EntityManager意味着您将独自承担一切–创建,共享EntityManager,开始tx,提交,关闭。 但是,了解了以上所有差异后,您知道可以在应用程序管理的EntityManager中使用JTA事务 。 但是问题是–如何使它知道活动的JTA事务? 如果我们有一个容器管理的EntityManager,我们知道容器将全部管理它,但是如果我们是一个人,我们该怎么做? 实际上, 这取决于我们在哪里创建EntityManager 。 在下面找到一些示例(完整的代码可以在我的github帐户上找到:
情况1:我们在没有活动交易的情况下调用以下代码(因此,对于CMT,我们具有TransactionAttribute.NEVER
或TransactionAttribute.NOT_SUPPORTED
;对于BMT,我们不调用UserTransaction.begin()
:
EntityManager em = emf.createEntityManager();
em.persist(new Customer(firstName, lastName));
结果: EntityManager操作在持久化时不会引发任何异常,但是所有更改都不会提交 。 没有活动的事务,因此不会进行任何更改。
情况2:我们使用BMT调用以下代码:
utx.begin();
EntityManager em = emf.createEntityManager();
em.persist(new Customer(firstName, lastName));
utx.commit();
结果:在JTA提交期间,新数据已正确保留(在最后一行。)
情况3:我们使用BMT调用以下代码:
EntityManager em = emf.createEntityManager();
utx.begin();
em.persist(new Customer(firstName, lastName));
utx.commit();
结果: EntityManager在事务之外,因为它是在启动JTA事务之前创建的。 尽管已提交JTA事务,但更改不会持久保存。 没有异常被抛出。
在第二个示例的情况下,您可能会问自己-是否可以首先创建EntityManager,然后开始事务,最后以某种方式使EntityManager知道周围的TX? 如果事实是这样,则可以执行此操作,而这正是EntityManager#joinTransaction()
方法的用途。 以下两种情况将向您展示如何使用它:
情况4:我们使用BMT调用以下代码:
EntityManager em = emf.createEntityManager();
utx.begin();
em.joinTransaction();
em.persist(new Customer(firstName, lastName));
utx.commit();
结果:在这里,我们明确地告诉EntityManager加入活动的JTA事务。 结果, EntityManager将在JTA commit期间刷新其所有更改 。
情况5:我们使用BMT调用以下代码:
EntityManager em = emf.createEntityManager();
utx.begin();
em.joinTransaction();
em.persist(new Customer(firstName, lastName));
utx.commit();
utx.begin();
em.joinTransaction();
em.persist(new Customer(firstName, lastName));
utx.commit();
结果: 两种EntityManager操作均正确存在。 在这里,我们证明了应用程序管理的持久性上下文可以跨越多个JTA事务 (请注意,我们没有创建另一个EntityManager,而只是重用了先前事务中使用的那个)。在这里,您可以看到JPA规范(JPA规范) 2.0最终版本)告诉您有关应用程序管理的持久性上下文的信息:
7.7应用程序管理的持久性上下文
使用JTA应用程序管理的实体管理器时,如果在当前JTA事务的范围之外创建实体管理器,则应用程序有责任通过调用EntityManager.joinTransaction将实体管理器与事务关联(如果需要)。 。 如果实体管理器是在JTA事务范围之外创建的,则除非调用EntityManager.joinTransaction,否则它不与事务关联。
使用CDI共享EntityManager
如前所述,如果要在组成一个事务的组件之间共享EntityManager,则应手动传递它(毕竟,它是“应用程序管理的”。)CDI可能是此处的解决方案。 您可以生成请求范围内的EntityManager并将其注入所需的任何组件中。 看起来可能像这样(在现实生活中,您还需要注意处理EM):
public class Resources {
@PersistenceUnit
EntityManagerFactory emf;
@Produces @RequestScoped
public EntityManager createEntityManager() {
return emf.createEntityManager();
}
}
现在,在每个bean中我们都可以拥有:
@Stateless
public class MyBean {
@Inject
EntityManager em;
}
在构成事务的不同组件之间共享应用程序管理的持久性上下文似乎是一种非常干净的方法。 但是,我担心的是:知道由应用程序管理的EntityManager事务性行为取决于创建它的位置 ,因此这种方法有时可能会给您带来讨厌的结果。 以下面的代码为例( 我的Github项目中也提供了该类,该类正是在这里 ):
@Stateless
@TransactionAttribute(TransactionAttributeType.NEVER)
public class BeanABoundary {
@Inject
private EntityManager em;
@EJB
BeanB beanB;
public void invoke() {
em.getProperties();
beanB.invoke();
}
请注意,BeanA是非事务性资源。 还要注意,我们在EntityManager上注入并调用了一些操作 (这使得注入实际上得以执行。)现在,如果BeanB是事务性的,并且还注入并使用EntityManager –我们将以非事务性EntityManager结束,该操作不会抛出任何异常,并且不会将任何更改保存到数据库 。
在旧的@PersistenceContext
的情况下,我们将处于事务中,因为EntityManager将由容器管理,并且容器将知道当前处于活动状态的事务。 容器负责在事务边界之间共享EntityManager。 在显示CDI生产者方法的情况下,CDI不知道运行事务,而只是共享EntityManager。
当然,可以使用CDI并创建一个@Produces @PersistenceContext EntityManager em
,然后使用@Inject EntityManager
。 这将与@PersistenceContext EntityManager
完全一样,但是允许我们在产生EntityManager的单个位置定义(例如)持久性单元的名称。 但是,如果我们要拥有一个应用程序管理的EntityManager,则这不是一个选择。
参考: 实体管理器的类型:由我们的JCG合作伙伴 Piotr Nowicki在Piotr Nowicki主页博客上进行的应用程序管理的EntityManager 。
entitymanager