1 Hibernate架构
……
8 事务和并发控制-Transactions and concurrency control
重点是理解“事务”这个术语在持久性和对象/关系映射方面不同的但相关的含义。多数情况定义一致,少数情况不是。
- 可能会参考与数据库的物理事务。
- 可能将事务的逻辑概念称为与持久性上下文相关。
- 可能会参考原型模式定义的工作单元的应用概念。
本文主要将事务的物理和逻辑概念视为同一个事物。
8.1 物理事务-physical transactions
Hibernate使用JDBC API进行持久化。
在Java世界中,有两种定义明确的机制来处理JDBC中的事务:
- JDBC本身
- JTA
Hibernate支持两种与事务集成并允许应用程序管理物理事务的机制。
每个事务Session处理由org.hibernate.resource.transaction.spi.TransactionCoordinator合同处理,该合同由org.hibernate.resource.transaction.spi.TransactionCoordinatorBuilder服务构建。 TransactionCoordinatorBuilder表示处理事务的策略,而TransactionCoordinator表示与Session相关的策略的一个实例。其TransactionCoordinatorBuilder方案中使用由定义hibernate.transaction.coordinator_class设置。
-
JDBC(非JPA应用程序的默认值)
通过调用java.sql.Connection管理事务 。 -
JTA
通过调用JTA管理事务。
如果JPA应用程序没有提供设置hibernate.transaction.coordinator_class,Hibernate将根据持久性单元的事务类型自动构建正确的事务协调器。
如果非JPA应用程序没有提供设置hibernate.transaction.coordinator_class,Hibernate将使用jdbc默认设置。如果应用程序实际使用基于JTA的事务,则此缺省值将导致问题。使用基于JTA的事务的非JPA应用程序应显式设置hibernate.transaction.coordinator_class=jta 或提供自定义org.hibernate.resource.transaction.TransactionCoordinatorBuilder,org.hibernate.resource.transaction.TransactionCoordinator以便与基于JTA的事务建立 正确的坐标。
Hibernate直接使用JDBC和JTA连接资源,而不添加任何其他锁定行为。Hibernate不会锁定内存中的对象。使用Hibernate时,数据库事务的隔离级别定义的行为不会更改。Hibernate Session充当事务范围的缓存,提供可重复读取,以便按标识符和查询进行查找,从而导致加载实体。
要减少数据库中的锁争用,物理数据库事务需要尽可能短。
长时间运行的数据库事务会阻止您的应用程序扩展到高度并发的负载。不要在最终用户级工作期间打开数据库事务,而是在最终用户级工作完成后打开它。这个概念被称为transactional write-behind。
8.2 JTA配置-JTA configuration
与JTA系统的交互在一个名为合同的合同背后进行合并,该合同org.hibernate.engine.transaction.jta.platform.spi.JtaPlatform公开了对该系统javax.transaction.TransactionManager 和javax.transaction.UserTransaction该系统的访问,并暴露了注册javax.transaction.Synchronization实例,检查事务状态等的能力。
一般情况下,JtaPlatform将需要访问JNDI来解决JTA TransactionManager,UserTransaction等见JNDI章对配置到JNDI访问的细节。
Hibernate试图JtaPlatform通过使用另一个名为的服务来发现它应该使用它org.hibernate.engine.transaction.jta.platform.spi.JtaPlatformResolver。如果该分辨率不起作用,或者您希望提供自定义实现,则需要指定该hibernate.transaction.jta.platform设置。Hibernate提供了许多JtaPlatform契约实现,都有短名称,如下:
-
Atomikos
JtaPlatform 为Atomikos。 -
Borland
JtaPlatform 对于Borland企业级服务器。 -
Bitronix
JtaPlatform 对于Bitronix。 -
JBossAS
JtaPlatform在JBoss/WildFl应用服务器中使用时,用于Arjuna/ JBossTransactions/Narayana。 -
JBossTS
JtaPlatform 对于Arjuna / JBossTransactions / Narayana,当单独使用时。 -
JOnAS
JtaPlatform 在JOnAS中使用JOTM时。 -
JOTM
JtaPlatform JOTM独立使用时。 -
JRun4
JtaPlatform 对于JRun 4应用服务器。 -
OC4J
JtaPlatform 对于Oracle的OC4J容器。 -
Orion
JtaPlatform 对于Orion应用服务器。 -
Resin
JtaPlatform 用于Resin应用服务器。 -
SapNetWeaver
JtaPlatform 用于SAP NetWeaver应用服务器。 -
SunOne
JtaPlatform 对于SunOne应用服务器。 -
Weblogic
JtaPlatform 用于Weblogic应用服务器。 -
WebSphere
JtaPlatform 对于旧版本的WebSphere应用服务器。 -
WebSphereExtended
JtaPlatform 对于较新版本的WebSphere应用服务器。
8.3 Hibernate事务API
Hibernate提供了一个API,用于帮助将应用程序与正在使用的底层物理事务系统中的差异隔离开来。基于TransactionCoordinatorBuilder配置,Hibernate将在应用程序使用此事务API时执行正确的操作。这使您的应用程序和组件可以更加轻松地移动到不同的环境中。
要使用此API,需要从org.hibernate.Transaction会话中获取。Transaction允许所有你所期望的正常操作:begin、commit和rollback,它还有一些棒的方法,如:
-
markRollbackOnly
适用于JTA和JDBC -
getTimeout 和 setTimeout
适用于JTA和JDBC -
registerSynchronization
这使您甚至可以在非JTA环境中注册JTA同步。事实上,在JTA和JDBC环境中,这些Synchronizations都是由Hibernate本地保存的。在JTA环境中,Hibernate只会注册一个Synchronization,TransactionManager以避免排序问题。
公开了一个返回org.hibernate.resource.transaction.spi.TransactionStatus枚举的getStatus方法。如果需要,此方法将检查基础事务系统,因此应注意尽量减少其使用; 它可以在某些JTA设置中产生巨大的性能影响。
在各种环境中使用Transaction API:
示例1.在JDBC中使用transaction API
StandardServiceRegistry serviceRegistry = new StandardServiceRegistryBuilder()
// "jdbc" is the default, but for explicitness
.applySetting( AvailableSettings.TRANSACTION_COORDINATOR_STRATEGY, "jdbc" )
.build();
Metadata metadata = new MetadataSources( serviceRegistry )
.addAnnotatedClass( Customer.class )
.getMetadataBuilder()
.build();
SessionFactory sessionFactory = metadata.getSessionFactoryBuilder()
.build();
Session session = sessionFactory.openSession();
try {
// calls Connection#setAutoCommit( false ) to
// signal start of transaction
session.getTransaction().begin();
session.createQuery( "UPDATE customer set NAME = 'Sir. '||NAME" )
.executeUpdate();
// calls Connection#commit(), if an error
// happens we attempt a rollback
session.getTransaction().commit();
}
catch ( Exception e ) {
// we may need to rollback depending on
// where the exception happened
if ( session.getTransaction().getStatus() == TransactionStatus.ACTIVE
|| session.getTransaction().getStatus() == TransactionStatus.MARKED_ROLLBACK ) {
session.getTransaction().rollback();
}
// handle the underlying error
}
finally {
session.close();
sessionFactory.close();
}
示例2.在JTA(CMT)中使用transaction API
tandardServiceRegistry serviceRegistry = new StandardServiceRegistryBuilder()
// "jdbc" is the default, but for explicitness
.applySetting( AvailableSettings.TRANSACTION_COORDINATOR_STRATEGY, "jta" )
.build();
Metadata metadata = new MetadataSources( serviceRegistry )
.addAnnotatedClass( Customer.class )
.getMetadataBuilder()
.build();
SessionFactory sessionFactory = metadata.getSessionFactoryBuilder()
.build();
// Note: depending on the JtaPlatform used and some optional settings,
// the underlying transactions here will be controlled through either
// the JTA TransactionManager or UserTransaction
Session session = sessionFactory.openSession();
try {
// Since we are in CMT, a JTA transaction would
// already have been started. This call essentially
// no-ops
session.getTransaction().begin();
Number customerCount = (Number) session.createQuery( "select count(c) from Customer c" ).uniqueResult();
// Since we did not start the transaction ( CMT ),
// we also will not end it. This call essentially
// no-ops in terms of transaction handling.
session.getTransaction().commit();
}
catch ( Exception e ) {
// again, the rollback call here would no-op (aside from
// marking the underlying CMT transaction for rollback only).
if ( session.getTransaction().getStatus() == TransactionStatus.ACTIVE
|| session.getTransaction().getStatus() == TransactionStatus.MARKED_ROLLBACK ) {
session.getTransaction().rollback();
}
// handle the underlying error
}
finally {
session.close();
sessionFactory
示例3.在JTA(BMT)中使用transaction API
StandardServiceRegistry serviceRegistry = new StandardServiceRegistryBuilder()
// "jdbc" is the default, but for explicitness
.applySetting( AvailableSettings.TRANSACTION_COORDINATOR_STRATEGY, "jta" )
.build();
Metadata metadata = new MetadataSources( serviceRegistry )
.addAnnotatedClass( Customer.class )
.getMetadataBuilder()
.build();
SessionFactory sessionFactory = metadata.getSessionFactoryBuilder()
.build();
// Note: depending on the JtaPlatform used and some optional settings,
// the underlying transactions here will be controlled through either
// the JTA TransactionManager or UserTransaction
Session session = sessionFactory.openSession();
try {
// Assuming a JTA transaction is not already active,
// this call the TM/UT begin method. If a JTA
// transaction is already active, we remember that
// the Transaction associated with the Session did
// not "initiate" the JTA transaction and will later
// nop-op the commit and rollback calls...
session.getTransaction().begin();
session.persist( new Customer( ) );
Customer customer = (Customer) session.createQuery( "select c from Customer c" ).uniqueResult();
// calls TM/UT commit method, assuming we are initiator.
session.getTransaction().commit();
}
catch ( Exception e ) {
// we may need to rollback depending on
// where the exception happened
if ( session.getTransaction().getStatus() == TransactionStatus.ACTIVE
|| session.getTransaction().getStatus() == TransactionStatus.MARKED_ROLLBACK ) {
// calls TM/UT commit method, assuming we are initiator;
// otherwise marks the JTA transaction for rollback only
session.getTransaction().rollback();
}
// handle the underlying error
}
finally {
session.close();
sessionFactory.close();
}
在CMT的情况下,我们真的可以省略所有的Transaction调用。但这些示例的重点是表明Transaction API确实将您的代码与底层事务机制隔离开来。实际上,如果删除了bootstrap中提供的注释和单个配置设置,则所有3个示例中的代码完全相同。换句话说,我们可以在3个事务环境中的任何一个中开发该代码并将其删除。
Transaction API努力使所有环境中的体验保持一致。为此,当存在差异时(例如,在失败的提交时自动尝试回滚),它通常会遵循JTA规范。
8.4 上下文会话-contextual session
大多数使用Hibernate的应用程序都需要某种形式的上下文会话,其中给定的会话在给定上下文的整个范围内都有效。但是,在不同的应用程序中,构成上下文的定义通常是不同的; 不同的上下文定义了当前概念的不同范围。在版本3.0之前使用Hibernate的应用程序倾向于使用ThreadLocal基于本地的上下文会话,辅助类等HibernateUtil,或利用第三方框架,例如Spring或Pico,它们提供基于代理/拦截的上下文会话。
从版本3.0.1开始,Hibernate添加了该SessionFactory.getCurrentSession()方法。最初,这假设使用JTA事务,其中JTA事务定义了当前会话的范围和上下文。鉴于众多独立JTA TransactionManager实现的成熟度,大多数(如果不是全部)应用程序应该使用JTA事务管理,无论它们是否部署到J2EE容器中。基于此,JTA您需要使用基于上下文的会话。
但是,从版本3.1开始,后面的处理SessionFactory.getCurrentSession()现在是可插拔的。为此,添加了新的扩展接口org.hibernate.context.spi.CurrentSessionContext和新的配置参数,hibernate.current_session_context_class以允许定义当前会话的范围和上下文的可插入性。
有关其合同的详细讨论,请参阅Javadocs以获取org.hibernate.context.spi.CurrentSessionContext接口。它定义了一个方法,currentSession()实现负责跟踪当前的上下文会话。开箱即用,Hibernate附带了这个界面的三个实现:
org.hibernate.context.internal.JTASessionContext
当前会话由JTA事务跟踪和确定范围。此处的处理与旧的仅JTA方法完全相同。
org.hibernate.context.internal.ThreadLocalSessionContext
当前会话由执行线程跟踪。有关更多详细信息,请参阅Javadocs。
org.hibernate.context.internal.ManagedSessionContext
当前会话由执行线程跟踪。但是,您有责任Session在此类上使用静态方法绑定和取消绑定实例; 它不会打开,冲洗或关闭Session。
通常,此参数的值只是命名要使用的实现类。但是,对于三个开箱即用的实现,有三个相应的短名称:jta,thread和managed。
前两个实现提供一个会话 - 一个数据库事务编程模型。这也是已知的,并用作每个请求的会话。Hibernate会话的开始和结束由数据库事务的持续时间定义。如果在没有JTA的普通Java SE中使用程序化事务划分,建议您使用Hibernate TransactionAPI从代码中隐藏基础事务系统。如果使用JTA,则可以使用JTA接口划分事务。如果在支持CMT的EJB容器中执行,则以声明方式定义事务边界,并且在代码中不需要任何事务或会话分界操作。请参阅事务和并发控制 有关更多信息和代码示例。
所述hibernate.current_session_context_class配置参数定义org.hibernate.context.spi.CurrentSessionContext应使用的实现。为了向后兼容,如果未配置此配置参数但org.hibernate.engine.transaction.jta.platform.spi.JtaPlatform已配置a,则Hibernate将使用org.hibernate.context.internal.JTASessionContext。
8.5 事务模式(和and anti-patterns)-transactional patterns(and anti-patterns)
8.5.1 每个操作的会话反模式
这是Session在单个线程中为每个数据库调用打开和关闭a的反模式。就数据库事务而言,它也是一种反模式。将数据库调用分组为计划的序列。同样,在应用程序中的每个SQL语句之后都不要自动提交。Hibernate禁用或期望应用程序服务器立即禁用自动提交模式。数据库事务绝不是可选的。与数据库的所有通信必须由事务封装。避免自动提交行为以读取数据,因为许多小事务不可能比一个明确定义的工作单元执行得更好,并且更难以维护和扩展。
使用自动提交不会绕过数据库事务。
相反,在自动提交模式下,JDBC驱动程序只是在隐式事务调用中执行每个调用。就好像您的应用程序在每次JDBC调用之后都调用了commit。
8.5.2 每个请求的会话模式
这是最常见的交易模式。这里的术语请求涉及对来自客户端/用户的一系列请求作出反应的系统的概念。Web应用程序是此类系统的主要示例,但肯定不是唯一的。在处理此类请求之初,应用程序将打开Hibernate会话,启动事务,执行所有与数据相关的工作,结束事务并关闭会话。模式的关键是事务和会话之间的一对一关系。
在这种模式中,有一种定义当前会话的常用技术,以简化将其传递Session给可能需要访问它的所有应用程序组件的需要。Hibernate通过该getCurrentSession方法为这种技术提供支持SessionFactory。当前会话的概念必须具有一个范围,该范围定义了当前概念有效的界限。这是org.hibernate.context.spi.CurrentSessionContext合同的目的。
有两个可靠的定义范围:
-
首先是JTA事务,因为它允许回调挂钩知道它何时结束,这使Hibernate有机会关闭Session并清理它。这体现org.hibernate.context.internal.JTASessionContext在org.hibernate.context.spi.CurrentSessionContext合同的实施上。使用此实现,Session将getCurrentSession在该事务中第一次调用a。
-
其次是这个应用程序请求循环本身。这最能体现org.hibernate.context.internal.ManagedSessionContext在org.hibernate.context.spi.CurrentSessionContext合同的实施上。这里,外部组件负责管理当前会话的生命周期和范围。在这样的范围的开始,ManagedSessionContext#bind()方法被称为传入Session。最后,unbind()调用它的方法。这些外部组件的一些常见示例包括:
- javax.servlet.Filter 履行
- AOP拦截器与服务方法上的切入点
- 代理/拦截容器
该getCurrentSession()方法在JTA环境中有一个缺点。如果使用它,after_statement默认情况下也会使用连接释放模式。由于JTA规范的限制,Hibernate无法自动清除由or 返回的任何未关闭ScrollableResults或Iterator实例。通过调用或显式地从finally块释放底层数据库游标。scroll()iterate()ScrollableResults#close()Hibernate.close(Iterator)
8.5.3 会话(应用程序级事务)
每个请求的会话模式不是设计工作单元的唯一有效方式。许多业务流程需要与用户进行一系列与数据库访问交错的交互。在Web和企业应用程序中,数据库事务跨越用户交互是不可接受的。请考虑以下示例:
将打开一个对话框的第一个屏幕。用户看到的数据被加载到特定的Session数据库事务中。用户可以自由修改对象。
用户使用UI元素在编辑五分钟后保存其工作。这些修改是持久的。用户还希望在编辑会话期间具有对数据的独占访问权。
即使我们在这里有多个数据库访问,但从用户的角度来看,这一系列步骤代表了一个单独的工作单元。在您的应用程序中有很多方法可以实现它。
第一个天真实现可能会Session在用户编辑时保持和数据库事务处于打开状态,使用数据库级锁来防止其他用户修改相同的数据并保证隔离和原子性。这是一种反模式,因为锁争用是一个瓶颈,将来会阻止可扩展性。
几个数据库事务用于实现对话。在这种情况下,维护业务流程的隔离成为应用程序层的部分责任。单个对话通常跨越多个数据库事务。如果只有这些数据库事务中的一个(通常是最后一个)存储更新的数据,则这些多个数据库访问只能是原子的。所有其他人只读数据。接收此数据的常用方法是通过跨越多个请求/响应周期的向导式对话框。Hibernate包含一些使其易于实现的功能。
自动版本控制 | Hibernate可以为您执行自动乐观并发控制。它可以自动检测(在会话结束时)用户思考时间内是否发生了并发修改。 |
分离的对象 | 如果您决定使用每个请求的会话模式,则在用户思考期间,所有已加载的实例都将处于分离状态。Hibernate允许您重新附加对象并保留修改。该模式称为session-per-request-with-detaached-objects。自动版本控制用于隔离并发修改。 |
扩展 Session | Session提交数据库事务后,Hibernate 可以与基础JDBC连接断开连接,并在发生新客户端请求时重新连接。这种模式称为每次会话会话,甚至不需要重新附加。自动版本控制用于隔离并发修改,并且Session不允许仅显式地自动刷新。 |
每个请求的会话分离对象和每个会话的会话都有优点和缺点。
8.5.4 每个应用程序的会话反模式
每个应用程序的会话也被视为反模式。Session与JPA一样EntityManager,Hibernate 不是一个线程安全的对象,它一次只能局限于一个线程。如果Session在多个线程之间共享,则会出现竞争条件以及可见性问题,因此请注意这一点。
Hibernate抛出的异常意味着您必须回滚数据库事务并Session立即关闭。如果您Session的应用程序已绑定,则必须停止该应用程序。回滚数据库事务不会将业务对象恢复到事务开始时的状态。这意味着数据库状态和业务对象将不同步。通常,这不是问题,因为异常是不可恢复的,无论如何您都必须在回滚后重新开始。
有关更多详细信息,请查看异常处理章节。
所述Session高速缓存的每个对象是在持久性状态(Hibernate会监视和检查脏状态)。如果你长时间打开它或者只是加载过多的数据,它会无休止地增长直到你得到一个OutOfMemoryException。一种解决方案是调用clear()和evict()管理Session缓存,但如果需要海量数据操作,则应考虑存储过程。批处理章节中显示了一些解决方案。Session在用户会话期间保持开放也意味着陈旧数据的概率更高。