关于 Hibernate 实体管理器和并发性控制最重要的一点是,它非常易于理解。Hibernate 实体管理器直接使用 JDBC 连接和 JTA 资源,而不会添加任何其他锁定行为。我们强烈建议您花时间了解 JDBC、ANSI 及数据库管理系统的交易隔离说明。Hibernate 实体管理器只会添加自动版本控制,但不会锁定内存中的对象或更改您数据库事务的隔离级别。基本上,使用 Hibernate 实体管理器,就像使用直接 JDBC(或 JTA/CMT)与数据库资源一样。
我们从EntityManagerFactory
和EntityManager
以及数据库事务和长工作单元的粒度开始讨论 Hibernate 中的并发性控制。
在本章中,除非明确表示,我们将混合匹配实体管理器和持久化上下文的概念。一个是 API 和编程对象,另一个是范围的定义。但是,请记住其本质区别。在 Java EE 中,持久化上下文通常绑定到 JTA 事务,并且持久化上下文从事务边界开始和结束(事务范围),除非您使用扩展实体管理器。有关更多信息,请参见第 1.2.3 节,“持久化上下文范围”。
EntityManagerFactory
是一个创建成本高的线程安全对象,旨在供所有应用程序线程共享。通常在应用程序启动时创建一次。
为了完整地理解这一概念,你还必须考虑数据库事务。一个数据库事务必须尽可能地短,以减少数据库中的锁竞争。长的数据库事务将阻止你的应用程序扩展到高度并发负载。
从用户的角度来看,我们将此作业单元称为长期运行 应用程序事务。有很多方法可以在应用中实现此动作。
每个请求一个实体管理器和分离对象和每个应用程序事务一个实体管理器都具有优点和缺点,我们将在本章后面以乐观并发控制的背景下对其进行讨论。
持久性上下文会缓存处于托管状态中的每个对象(由 Hibernate 监视和检查其脏状态)。这意味着它会不断增长,直到你收到 OutOfMemoryException
,如果你一直让它处于打开状态或只加载太多数据。针对这个问题的一个解决方案是某种批量处理,并定期刷新持久性上下文,但是如果你需要批量数据操作,你应当考虑使用数据库存储过程。针对这个问题的一些解决方案在第 7 章,批处理 中有所说明。在用户会话期间一直让持久性上下文处于打开状态也意味着很可能会出现过期数据,你必须了解这一点并恰当加以控制。
我们现在将仔细查看在托管和非托管环境中的事务划分和异常处理。
如果 JPA 持久性层在非托管环境中运行,则数据库连接通常在幕后由 Hibernate 的池机制处理。常见的实体管理器和事务处理习语如下所示
// Non-managed environment idiom
EntityManager em = emf.createEntityManager();
EntityTransaction tx = null;
try {
tx = em.getTransaction();
tx.begin();
// do some work
...
tx.commit();
}
catch (RuntimeException e) {
if ( tx != null && tx.isActive() ) tx.rollback();
throw e; // or display error message
}
finally {
em.close();
}
您不必明确地 flush()
EntityManager
- 调用 commit()
会自动触发同步。
调用 close()
标记 EntityManager
的结束。close()
的主要含义是释放资源 - 确保您始终在明确的 finally 块内进行关闭,而不要在外部。
如果您的持久层在应用程序服务器中运行(例如在 EJB3 会话 Bean 后面),则实体管理器内部获得的每个数据源连接都将自动成为全局 JTA 事务的一部分。Hibernate 为此集成提供了两种策略。
如果您使用 bean 管理的事务 (BMT),代码将如下所示
// BMT idiom
@Resource public UserTransaction utx;
@Resource public EntityManagerFactory factory;
public void doBusiness() {
EntityManager em = factory.createEntityManager();
try {
// do some work
...
utx.commit();
}
catch (RuntimeException e) {
if (utx != null) utx.rollback();
throw e; // or display error message
}
finally {
em.close();
}
如果您在 CMT 环境中工作,可能还需要在代码的不同部分中使用相同的实体管理器。通常,在非托管环境中,您将使用 ThreadLocal
变量来保存实体管理器,但是单个 EJB 请求可能在不同的线程中执行(例如,会话 bean 调用另一个会话 bean)。EJB3 容器会为您负责持久化上下文传播。无论使用注入还是查找,EJB3 容器都会返回一个具有与 JTA 上下文(如果存在)绑定的相同持久化上下文的实体管理器,或创建一个新上下文并进行绑定(请参阅 第 1.2.4 节,“持久性上下文传播”)。
我们在 CMT 和 EJB3 容器使用中的实体管理器/事务管理习语被缩减为
//CMT idiom through injection
@PersistenceContext(name="sample") EntityManager em;
或者,如果您使用 Java 上下文和依赖注入 (CDI),则为
@Inject EntityManager em;
换句话说,您在托管环境中要做的全部工作就是注入 EntityManager
,执行数据访问工作,然后将其他事情留给容器。事务边界在您的会话 bean 的注释或部署描述符中以声明的方式设置。实体管理器和持久化上下文的生命周期完全由容器管理。
由于 JTA 规范的一个愚蠢限制,Hibernate 无法自动清除由 scroll()
或 iterate()
返回的任何未关闭的 ScrollableResults
或 Iterator
实例。您必须通过从 finally
块显式调用 ScrollableResults.close()
或 Hibernate.close(Iterator)
来释放底层数据库游标。(当然,大多数应用程序可以很容易地避免在 CMT 代码中使用 scroll()
或 iterate()
。)
所有以这种方式定义的应用程序托管实体管理器和容器托管持久上下文都是 EXTENDED
。这意味着持久上下文类型超出了事务生命周期。因此,我们应该理解在事务的范围内之外执行的操作会发生什么。
// foo is an instance loaded by a previous entity manager
em = factory.createEntityManager();
EntityTransaction t = em.getTransaction();
t.begin();
int oldVersion = foo.getVersion();
Foo dbFoo = em.find( foo.getClass(), foo.getKey() ); // load the current state
if ( dbFoo.getVersion()!=foo.getVersion ) throw new StaleObjectStateException();
dbFoo.setProperty("bar");
t.commit();
em.close();
在 EXTENDED
持久化上下文中,所有在活动事务之外执行的操作都已排队。当在活动事务(最差情况下在提交时间)中执行时,EXTENDED
持久化上下文被刷新。
// foo is an instance loaded earlier by the extended entity manager
em.getTransaction.begin(); // new connection to data store is obtained and tx started
foo.setProperty("bar");
em.getTransaction().commit(); // End tx, flush and check version, disconnect
版权所有 © 2005 Red Hat Inc. 及其他几位作者