ITEEDU

Chapter 17. 事务和并行(Transactions And Concurrency)

Hibernate本身并不是数据库,它只是一个轻量级的对象-关系数据库映射(object-relational)工具。它的事务交由底层的数据库连接管理,如果数据库连接有JTA的支持,那么在Session中进行的操作将是整个原子性JTA事务的一部分。Hibernate可以看作是添加了面向对象语义的JDBC瘦适配器(thin adapter)。

17.1. 配置,会话和工厂(Configurations, Sessions and Factories)

SessionFactory的创建需要耗费大量资源,它是线程安全(threadsafe)的对象,在应用中它被所有线程共享。而Session的创建耗费资源很少,它不是线程安全的对象,对于一个简单商业过程(business process),它应该只被使用一次,然后被丢弃。举例来说,当Hibernate在基于servlet的应用中,servlet能够以下面的方式得到SessionFactory。

SessionFactory sf = (SessionFactory)getServletContext().getAttribute("my.session.factory");

每次调用SessionFactory的service方法能够生成一个新的Session对象,然后调用Session的flush(),调用commit()提交它的连接,调用close()关闭它,最终丢弃它。

在无状态的session bean中,可以同样使用类似的方法。bean在setSessionContext()中得到SessionFactory的实例,每个商业方法会生成一个Session对象,调用它的flush()和close(),当然,应用不应该commit()connection. (把它留给JTA.)

这里需要理解flush()的含义。 flush()将持久化存储与内存中的变化进行同步,但不是将内存的变化与持久化存储进行同步。所以在调用flush()并接着调用commit()关闭连接时,会话将仍然含有过时的数据,在这种情况下,继续使用会话的唯一的方法是将会话中的数据进行版本化。

接下来的几小节将讨论利用版本化的方法来确保事务原子性,这些“高级”方法需要小心使用。

17.2. 线程和连接(Threads and connections)

You should observe the following practices when creating Hibernate Sessions:

在创建Hibernate会话(Session)时,你应该留意以下的实践(practices):

  • 对于一个数据库连接,不要创建一个以上的Session或Transaction

  • 在对于一个数据库连接、一个事务使用多个Session时,你尤其需要格外地小心。Session对象会记录下调入数据更新的情况,所以另一个Session对象可能会遇到过时的数据。

  • Session不是线程安全的。如果确实需要在两个同时运行的线程中共享会话,那么你应该确保线程在访问会话时,线程对Session具有同步锁。

17.3. 乐观锁定/版本化(Optimistic Locking / Versioning)

许多商业过程需要一系列与用户进行交互的过程,数据库访问穿插在这些过程中。对于web和企业应用来说,跨一个用户交互过程的数据事务是不可接受的,因而维护各商业事务间的隔离(isolocation)就成为应用层的部分责任。唯一满足高并发性以及高可扩展性的方法是使用带有版本化的乐观锁定。Hibernate为使用乐观锁定的代码提供了三种可能的方法。

17.3.1. 使用长生命周期带有自动版本化的会话

在整个商业过程中使用一个单独的Session实例以及它的持久化实例,这个Session使用带有版本化的乐观锁定机制,来确保多个数据库事务对于应用来说只是一个逻辑上的事务。在等待用户交互时,Session断开与数据库的连接。这个方法从数据库访问方面来看是最有效的,应用不需要关心对自己的版本检查或是重新与不需要序列化(transient)的实例进行关联。

// foo is an instance loaded earlier by the Session
session.reconnect();
foo.setProperty("bar");
session.flush();
session.connection().commit();
session.disconnect();

17.3.2. 使用带有自动版本化的多个会话

每个与持久化存储的交互出现在一个新的Session中,在每次与数据库的交互中,使用相同的持久化实例。应用操作那些从其它Session调入的不需要持久化实例的状态,通过使用Session.update()或者Session.saveOrUpdate()来重新建立与它们的关联。

// foo is an instance loaded by a previous Session
foo.setProperty("bar");
session = factory.openSession();
session.saveOrUpdate(foo);
session.flush();
session.connection().commit();
session.close();

17.3.3. 应用程序自己进行版本检查

每当一个新的Session中与持久化存储层出现交互的时候,这个session会在操作持久化实例前重新把它们从数据存储中装载进来。我们现在所说的方式就是你的应用程序自己使用版本检查来确保商业过程的隔绝性。(当然,Hibernate仍会为你更新版本号)。从数据库访问方面来看,这种方法是最没有效率的,与entity EJB方式类似。

// foo is an instance loaded by a previous Session
session = factory.openSession();
int oldVersion = foo.getVersion();
session.load( foo, foo.getKey() );
if ( oldVersion!=foo.getVersion ) throw new StaleObjectStateException();
foo.setProperty("bar");
session.flush();
session.connection().commit();
session.close();

当然,如果在低数据并行(low-data-concurrency)的环境中,并不需要版本检查,你仍可以使用这个方法,只需要忽略版本检查。