从“锁”说起
学过数据库理论的都知道,关系型数据库有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 |
在数据库层面使用 |
LockMode.UPGRADE_NOWAIT |
类似UPGRAGE级别,不同的是此模式在事务不能提交时,立刻返回异常,而不是等待当前事务结束再进行等待的事务,适用于Oracle数据库 |
LockMode.UPGRADE_SKIPLOCKED |
acquired upon explicit user request using a |
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 |
具体显式编码可通过以下三种方式请求锁级别:
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注释。通过实现
LockModeType.OPTIMISTIC_FORCE_INCREMENT或者LockModeType.PESSIMISTIC_FORCE_INCREMENT的值来实现手动增加版本号;UserVersionType可以实现任意类型的版本号。默认情况下用户不能认为修改version字段的值,但是可以通过更改
//数据表:
表名:
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没有更新成功,因为它提交事务时原来的数据版本发生了变化,需要或者最新版本数据再更新。
- 配置文件的方式:使用<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 |
access |
Hibernate's strategy for accessing the property value. Optional, defaults to |
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, |
generated |
Indicates that the version property value is generated by the database. Optional, defaults to |
insert |
Whether or not to include the |
时间戳(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 |
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 |
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 |