Hibernate4教程五:事务和并发
Hibernate本身没有事务的实现
Hibernate 直接使用 JDBC 连接和 JTA 资源,不添加任何附加锁定行为。也就是说你在Hibernate里面使用的事务要么是JDBC的事务,要么是JTA的事务。
Hibernate不锁定内存中的对象
你的应用程序会按照你的数据库事务的隔离级别规定的那样运作,真正对事务的实现和支持也依赖于数据库。
对于并发处理,Hibernate提供了乐观锁和悲观锁来进行并发处理
Hibernate对自动乐观并发控制提供版本管理,针对行级悲观锁定,Hibernate 也提供了辅助的(较小的)API,它使用了 SELECT FOR UPDATE 的 SQL 语法
Hibernate的Session是和事务联系在一起的
可以通过Session去获取事务的接口,从而进行事务的控制。
数据库事务应该尽可能的短
这样能降低数据库中的锁争用。数据库长事务会阻止你的应用程序扩展到高的并发负载。因此,假若在用户思考期间让数据库事务开着,直到整个工作单元完成才关闭这个事务,这绝不是一个好的设计。
这就引出一个问题:一个操作单元,也就是一个事务单元的范围应该是多大?
一个操作一个?一个请求一个?一个应用一个?
反模式:session-per-operation
在单个线程中, 不要因为一次简单的数据库调用,就打开和关闭一次 Session!数据库事务也是如此。也就是说应该禁止自动事务提交(auto-commit)。
session-per-request
最常用的模式是 每个请求一个会话。 在这种模式下,来自客户端的请求被发送到服务器端,即 Hibernate 持久化层运行的地方,一个新的 Hibernate Session 被打开,并且执行这个操作单元中所有的数据库操作。一旦操作完成(同时对客户端的响应也准备就绪),session 被同步,然后关闭。会话和请求之间的关系是一对一的关系。
Hibernate 内置了对“当前 session(current session)” 的管理,用于简化此模式。你要做的一切就是在服务器端要处理请求的时候,开启事务,在响应发送给客户之前结束事务,通常使用Servelt Filter来完成。
非托管环境下
所谓非托管,指的是:应用程序没有托管到J2EE环境中,通常由Hibernate自己来负责管理数据库连接池。应用程序开发人员必须手工设置事务声明,换句话说,就是手工启动,提交,或者回滚数据库事务。
使用JTA
又有两种方式,一种是在Hibernate配置里面修改transaction的factory类,从而在程序里面可以直接使用Hibernate的事务API,也就是程序不用变化。
另外一种方式就是直接通过JNDI去查找UserTransaction,然后直接在程序里面使用JTA的接口来控制事务。
应用程序级别的版本检查
简单点说,就是由应用程序自己实现版本检查来确保对话事务的隔离,从数据访问的角度来说是最低效的,不推荐使用。
扩展周期的session和自动版本化
Hibernate 使用扩展周期的 Session 的方式,或者脱管对象实例的方式来提供自动版本检查。单个 Session 实例和它所关联的所有持久化对象实例都被用于整个对话,这被称为 session-per-
conversation。
Hibernate 在同步的时候进行对象实例的版本检查,如果检测到并发修改则抛出异常。由开发人员来决定是否需要捕获和处理这个异常(通常的抉择是给用户 提供一个合并更改,或者在无脏数据情况下重新进行业务对话的机会)。
在等待用户交互的时候, Session 断开底层的 JDBC 连接。这种方式以数据库访问的角度来说是最高效的方式。应用程序不需要关心版本检查或脱管对象实例的重新关联,在每个数据库事务中,应用程序也不需要载入读取对象实例。
应用程序级别的版本检查
简单点说,就是由应用程序自己实现版本检查来确保对话事务的隔离,从数据访问的角度来说是最低效的,不推荐使用。
扩展周期的session和自动版本化
Hibernate 使用扩展周期的 Session 的方式,或者脱管对象实例的方式来提供自动版本检查。单个 Session 实例和它所关联的所有持久化对象实例都被用于整个对话,这被称为 sessionper-conversation。
Hibernate 在同步的时候进行对象实例的版本检查,如果检测到并发修改则抛出异常。由开发人员来决定是否需要捕获和处理这个异常(通常的抉择是给用户 提供一个合并更改,或者在无脏数据情况下重新进行业务对话的机会)。
在等待用户交互的时候, Session 断开底层的 JDBC 连接。这种方式以数据库访问的角度来说是最高效的方式。应用程序不需要关心版本检查或脱管对象实例的重新关联,在每个数据库事务中,应用程序也不需要载入读取对象实例。
通常不需要自己去管理锁定策略
Hibernate 总是使用数据库的锁定机制,从不在内存中锁定对象。因而用户并不需要花很多精力去担心锁定策略的问题。通常情况下,只要为 JDBC 连接指定一下隔离级别,然后让数据库去搞定一切就够了。
然而,高级用户有时候希望进行一个排它的悲观锁定,或者在一个新的事务启动的时候,重新进行锁定。
类 LockMode 定义了Hibernate 所需的不同的锁定级别
1:当更新或者插入一行记录的时候,锁定级别自动设置为LockMode.WRITE。
2:当用户显式的使用数据库支持的 SQL 格式 SELECT ... FOR UPDATE 发送 SQL 的时候,锁定级别设置为 LockMode.UPGRADE。
3:当用户显式的使用 Oracle 数据库的 SQL 语句 SELECT ... FOR UPDATE NOWAIT 的时候,锁定级别设置 LockMode.UPGRADE_NOWAIT。
4:当 Hibernate 在“可重复读”或者是“序列化”数据库隔离级别下读取数据的时候,锁定模式自动设置为 LockMode.READ。这种模式也可以通过用户显式指定进行设置。
5:LockMode.NONE 代表无需锁定。在 Transaction 结束时, 所有的对象都切换到该模式上来。与session 相关联的对象通过调用 update() 或者 saveOrUpdate() 脱离该模式。
显示的指定锁定模式
1:调用 Session.load() 的时候指定锁定模式(LockMode)。
2:调用 Session.lock()。
3:调用 Query.setLockMode()。
显示指定锁定模式的说明
1:如果在 UPGRADE 或者 UPGRADE_NOWAIT 锁定模式下调用 Session.load(),并且要读取的对象尚未被session 载入过,那么对象通过 SELECT ... FOR UPDATE 这样的 SQL 语句被载入。如果为一个对象调用 load() 方法时,该对象已经在另一个较少限制的锁定模式下被载入了,那么 Hibernate 就对该对象调用 lock() 方法。
2:如果指定的锁定模式是 READ,UPGRADE 或 UPGRADE_NOWAIT,那么 Session.lock() 就执行版本号检查。(在 UPGRADE 或者 UPGRADE_NOWAIT 锁定模式下,执行 SELECT ... FOR UPDATE这样的SQL语句。)
3:如果数据库不支持用户设置的锁定模式,Hibernate 将使用适当的替代模式(而不是扔出异常)。这一点可以确保应用程序的可移植性。