Hibernate-悲观锁和乐观锁

当多个事务同时访问数据库中的相同数据时,如果没有采取必要的隔离措施,将会导致各种并发问题,这时可以采用 悲观锁乐观锁 对其进行控制。

悲观锁

悲观锁 是指每次在操作数据时,总是悲观地认为会有其他事务也会来操作同一数据,因此,在整个数据处理过程中,将数据处于锁定状态。悲观锁由数据库来实现,在锁定的时间其他事务不能对数据进行存取,这样有可能造成长时间等待。在 Hibernate 中,用户可以显式地设定要锁定的表或字段及锁模式。

Hibernate 锁模式有如下几种

  • LockMode.NONE
    如果缓存中存在对象,直接返回该对象的引用,否则通过 SELECT 语句到数据库中加载该对象,这是锁模式的默认值。
  • LockMode.READ
    不管缓存中是否存在对象,总是通过 SELECT 语句到数据库中加载该对象,如果映射文件中设置了版本元素,就执行版本检查,比较缓存中的对象是否与数据库中的对象版本一致。
  • LockMode.UPGRADE
    不管缓存中是否存在对象,总是通过 SELECT 语句到数据库中加载该对象,如果映射文件中设置了版本元素,就执行版本检查,比较缓存中的对象是否与数据库中的对象版本一致,如果数据库系统支持悲观锁,就执行 SELECT … For UPDATE 语句,如果不支持,就执行普通 SELECT 语句。
  • LockMode.UPGRADE_NOWAIT
    LockMode.UPGRADE 具有同样功能,对于 Oracle 等支持 update nowait 的数据库,执行 select … for update nowait 语句,nowait 表明如果执行该 select 语句的事务不能立即获得悲观锁,那么不会等待其他事务来释放锁,而是立即抛出锁定异常。
  • LockMode.WRITE
    保存对象时会自动使用这种锁模式,仅供 Hibernate 内部使用,应用程序中不应该使用它。
  • LockMode.FORCE
    强制更新数据库中对象的版本属性,从而表明当前事务已经更新了这个对象。

设置锁模式的方法有:

  • 调用 Session.load() 时指定锁模式。
  • 调用 Session.lock()。
  • 调用 Query.setLockMode()。

乐观锁

乐观锁(Optimistic Locking) 通常认为多个事务同时操作同一个数据的情况很少发生,因此乐观锁不做数据库层次上的锁定,而是基于数据版本(Version)标识实现应用程序级别上的锁定机制,技能保证多个事务的并发操作,又能有效地防止第二类丢失更新的发生。

所谓的数据版本标识,就是通过为数据表增加一个 “version” 字段实现。读取数据时,将版本号一同读出,之后更新此数据时,将此版本号加一。在提交数据时,将现有的版本号与数据表对应记录的版本号进行对比,如果提交数据的版本号大于数据表中的版本号,则允许更新数据,否则禁止更新数据。

在 Hibernate 中,为乐观锁提供了两种基于版本控制的实现,分别是基于 version 的实现和基于 timestamp 的实现。

基于 version 的乐观锁

创建数据表的时候在表最后增加一个 Version 字段:

字段名 数据类型 说明
id int(4) 编号,主键,非空,自增
AccountNo varchar(20) 账号,非空
Balance decimal(10,0) 余额,非空
Version int(4) 版本号

在实体类中给 version 实例变量添加 @Version 注解:

1
2
3
4
5
6
@Version
@Column(name = "version")
public Integer getVersion()
{
return version;
}

基于 timestamp 的乐观锁

为了使用基于 timestamp 的乐观锁,需要在数据表中添加一个表示版本信息的字段 LastUpdateTime,取代原先的 version 字段。

字段名 数据类型 说明
id int(4) 编号,主键,非空,自增
AccountNo varchar(20) 账号,非空
Balance decimal(10,0) 余额,非空
LastUpdateTime datetime 最后修改时间

在实体类中给 latestTimeStamp 实例变量添加 @UpdateTimestamp 注解:

1
2
3
4
5
6
@UpdateTimestamp
@Column(name = "lastUpdateTime")
public Timestamp getLastUpdateTime()
{
return lastUpdateTime;
}