您的位置:首页 > 数据库

数据库乐观锁、悲观锁的初步认识,以及hibernate对它们的支持实现

2020-02-07 17:59 295 查看

从“锁”说起

  学过数据库理论的都知道,关系型数据库有ACID特征。其中I是Isolation的简写,表示数据库并发进行事务时,只有顺序,串联执行的事务才会最终反映到数据库中,即是每个成功执行的事务都是孤立分离的,它们之间不会相互影响。这个Isolation就是由数据库的锁(lock)机制来实现的。所谓的锁,也叫读锁或者写锁(分别对应于读事务及写事务的情景),锁的作用就是防止数据库在并发进行读、写事务时出现数据过时或者无效的情况。对于这些并发的用户而言,同一时间内某个用户只能更新(读事务不会影响数据状态)被加上锁的数据记录(records),加上锁后能够确保数据不能被访问,只有释放了锁后才能访问。

锁机制:悲观锁和乐观锁

  锁的类型有两种:悲观锁和乐观锁。悲观锁是当事务一开始就进行locking,被locked的table或者records都对外不可见,直到事务结束;乐观锁则是只有数据的更改变动向数据库提交时才进行的locking。悲观锁能够确保所有更改变动都正确提交到数据库,而乐观锁则有可能出现事务无法提交的情况(例如前一个用户已提交更改,后一个用户会提交失败,因为这时数据状态已经不是后一个用户原来读出来的那个了,已经被前一个用户更改了)。悲观锁能最大程度保证事务的隔离,但是占用数据库的开销太大(事务时间越长,开销就越大),在大并发是场景下是不可接受的。乐观锁机制在一定程度上解决了这个问题,但代价时更改不一定能够成功提交。

Hibernate的实现

  注意,这里的Hibernate是指4.3.5.Final版本。无论是乐观锁还是悲观锁,Hibernate都是使用数据库的锁机制实现,不会对内存的对象进行锁;

  • 悲观锁:需要定义隔离级别(isolation level),然后依赖数据库的锁机制。
    LockMode定义了Hibernate请求的锁机制,如下:
LockMode.WRITE

当Hibernate进行updates or inserts a row时自动请求锁;

LockMode.UPGRADE

在数据库层面使用 

SELECT ... FOR UPDATE
 语句(语法视数据库不同而不同)

LockMode.UPGRADE_NOWAIT

类似UPGRAGE级别,不同的是此模式在事务不能提交时,立刻返回异常,而不是等待当前事务结束再进行等待的事务,适用于Oracle数据库

LockMode.UPGRADE_SKIPLOCKED

acquired upon explicit user request using a

SELECT ... FOR UPDATE SKIP LOCKED
 in Oracle, or
SELECT ... with (rowlock,updlock,readpast) in SQL Server
.

LockMode.READ

acquired automatically when Hibernate reads data under Repeatable Read or Serializable isolation level. It can be re-acquired by explicit user request.

LockMode.NONE

The absence of a lock. All objects switch to this lock mode at the end of a Transaction. Objects associated with the session via a call to 

update()
or 
saveOrUpdate()
 also start out in this lock mode.

  具体显式编码可通过以下三种方式请求锁级别:

  1.Session.load();//eg session.load(User.class, new Long(1), LockMode.UPGRADE);

  2.Session.lock();//eg session.lock(user, LockMode.UPGRADE);

  3.Query.setLockMode();//eg query.setLockMode("user", LockMode.UPGRADE);

  • 乐观锁

  在那种读操作多、写操作少的情况下,Hibernate的乐观锁能够提供一定程度的事务隔离。在遇到同样的数据先后被更改时,后更改提交的事务将会被告知冲突。

  Hibernate实现乐观锁的方法有两种:1.检测版本号;2.时间戳。

  检测版本号

  • 通过在要作为版本号列的字段或者对应的getter上面加上@Version注释。通过实现
    UserVersionType可以实现任意类型的版本号。默认情况下用户不能认为修改version字段的值,但是可以通过更改
    LockModeType.OPTIMISTIC_FORCE_INCREMENT或者LockModeType.PESSIMISTIC_FORCE_INCREMENT的值来实现手动增加版本号;

   

//数据表:
表名:
person_table

personId name age version
1 Peter 20 4

//Entity:
@Entity
@Table(name="person_table")
public class Person {

private Integer personId;

private String name;

private Integer age;

private Integer version;

@Id
@GeneratedValue()
public Integer getPersonId() {
return personId;
}

public void setPersonId(Integer personId) {
this.personId = personId;
}

@Column
public String getName() {
return name;
}

public void setName(String name) {
this.name = name;
}

@Column
public Integer getAge() {
return age;
}

public void setAge(Integer age) {
this.age = age;
}

@Version //这里是有version字段作为version列,并且让Hibernate自动增长值
public Integer getVersion() {
return version;
}

public void setVersion(Integer version) {
this.version = version;
}
}


//测试代码
public class OptimisticLockTest { public static void main(String[] args) { SessionFactory sessionFactory = HibernateUtils.getSessionFactory(); Session session1 = sessionFactory.openSession(); Session session2 = sessionFactory.openSession(); Person person1 = (Person)session1.get(Person.class, new Integer(3)); Person person2 = (Person)session2.get(Person.class, new Integer(3)); System.out.println("before transation person1.getVersion = "+person1.getVersion()); System.out.println("before transation person2.getVersion = "+person2.getVersion()); session1.beginTransaction(); person1.setAge(25); session1.getTransaction().commit(); session2.beginTransaction(); person2.setAge(28); session2.getTransaction().commit(); } }
输出:

before transation person1.getVersion = 4
before transation person2.getVersion = 4

session2提交事务时,出现异常:

Exception in thread "main" org.hibernate.StaleObjectStateException: Row was updated or deleted by another transaction (or unsaved-value mapping was incorrect) : [com.entity.Person#3]
at org.hibernate.persister.entity.AbstractEntityPersister.check(AbstractEntityPersister.java:2541)
at org.hibernate.persister.entity.AbstractEntityPersister.update(AbstractEntityPersister.java:3285)
at org.hibernate.persister.entity.AbstractEntityPersister.updateOrInsert(AbstractEntityPersister.java:3183)
at org.hibernate.persister.entity.AbstractEntityPersister.update(AbstractEntityPersister.java:3525)
at org.hibernate.action.internal.EntityUpdateAction.execute(EntityUpdateAction.java:159)
at org.hibernate.engine.spi.ActionQueue.executeActions(ActionQueue.java:463)
at org.hibernate.engine.spi.ActionQueue.executeActions(ActionQueue.java:349)
at org.hibernate.event.internal.AbstractFlushingEventListener.performExecutions(AbstractFlushingEventListener.java:350)
at org.hibernate.event.internal.DefaultFlushEventListener.onFlush(DefaultFlushEventListener.java:56)
at org.hibernate.internal.SessionImpl.flush(SessionImpl.java:1222)
at org.hibernate.internal.SessionImpl.managedFlush(SessionImpl.java:425)
at org.hibernate.engine.transaction.internal.jdbc.JdbcTransaction.beforeTransactionCommit(JdbcTransaction.java:101)
at org.hibernate.engine.transaction.spi.AbstractTransactionImpl.commit(AbstractTransactionImpl.java:177)
at com.test.OptimisticLockTest.main(OptimisticLockTest.java:28)

   查看session1更新的行,version由4变为5了,session2没有更新成功,因为它提交事务时原来的数据版本发生了变化,需要或者最新版本数据再更新。

  1. 配置文件的方式:使用<version />标签配置version字段,主要有以下几个相关属性:
column

The name of the column holding the version number. Optional, defaults to the property name.

name

The name of a property of the persistent class.

type

The type of the version number. Optional, defaults to 

integer
.

access

Hibernate's strategy for accessing the property value. Optional, defaults to 

property
.

unsaved-value

Indicates that an instance is newly instantiated and thus unsaved. This distinguishes it from detached instances that were saved or loaded in a previous session. The default value,

undefined
, indicates that the identifier property value should be used. Optional.

generated

Indicates that the version property value is generated by the database. Optional, defaults to

never
.

insert

Whether or not to include the 

version
 column in SQL 
insert
 statements. Defaults to 
true
, but you can set it to 
false
 if the database column is defined with a default value of 
0
.

  时间戳(Timestamp)

  当选择Date或者Calendar类型的字段作为version列时,Hibernate将会自动使用Timestamp作为version信息。例如

@Entity
public class Flight implements Serializable {
...
@Version
public Date getLastUpdate() { ... }
}

  默认情况下,Hibernate读取数据库的时间作为version的时间戳,不过可以通过

@org.hibernate.annotations.Source注释来控制读取的时间源:
o
rg.hibernate.annotations.SourceType.DB
 ;
org.hibernate.annotations.SourceType.VM(来源JVM)。

  此外,也可以通过配置文件方式进行配置。例如:

<timestamp
        column="timestamp_column"
        name="propertyName"
        access="field|property|ClassName"
        unsaved-value="null|undefined"
        source="vm|db"
        generated="never|always"
        node="element-name|@attribute-name|element/@attribute|."
/>
相关的属性说明:
column

The name of the column which holds the timestamp. Optional, defaults to the property namel

name

The name of a JavaBeans style property of Java type Date or Timestamp of the persistent class.

access

The strategy Hibernate uses to access the property value. Optional, defaults to 

property
.

unsaved-value

A version property which indicates than instance is newly instantiated, and unsaved. This distinguishes it from detached instances that were saved or loaded in a previous session. The default value of 

undefined
 indicates that Hibernate uses the identifier property value.

source

Whether Hibernate retrieves the timestamp from the database or the current JVM. Database-based timestamps incur an overhead because Hibernate needs to query the database each time to determine the incremental next value. However, database-derived timestamps are safer to use in a clustered environment. Not all database dialects are known to support the retrieval of the database's current timestamp. Others may also be unsafe for locking, because of lack of precision.

generated

Whether the timestamp property value is generated by the database. Optional, defaults to

never
.

转载于:https://www.cnblogs.com/lauyu/p/5042154.html

  • 点赞
  • 收藏
  • 分享
  • 文章举报
dianmi9782 发布了0 篇原创文章 · 获赞 0 · 访问量 80 私信 关注
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: