如果需要保證資料通路的排它性,則需對目标資料加“鎖”,使其無法被其它程式修改
一,悲觀鎖
對資料被外界(包括本系統目前的其它事務和來自外部系統的事務處理)修改持保守态度,通過資料庫提供的鎖機制實作
最常用的,是對查詢進行加鎖(LockMode.UPGRADE和LockMode.UPGRADE_NOWAIT):
public class Test {
public static void main(String[] args) {
Configuration conf = new Configuration();
SessionFactory sessionFactory = conf.configure().buildSessionFactory();
Session sess = sessionFactory.openSession();
Transaction tran = sess.beginTransaction();
String hql = "from User where id = 1";
Query query = sess.createQuery(hql);
query.setLockOptions(LockOptions.UPGRADE);
List<User> list = query.list();
for(User user : list){
System.out.print(user.getName()+" ");
}
System.out.println();
tran.commit();
sess.close();
}
}
Hibernate會在生成的SQL後面加上for update子句:
Hibernate: select user0_.id as id0_, user0_.name as name0_, user0_.age as age0_
from TEST_USER user0_ where user0_.id=1 for update
longlong
通過for update子句,這條SQL鎖定了TEST_USER表中符合檢索條件的記錄,本次事務送出前,外界無法修改這些記錄,事務送出時會釋放事務過程中的鎖
Hibernate提供了2個鎖對象,LockMode和LockOptions:
通過LockOptions的源代碼,可以發現LockOptions隻是LockMode的簡單封裝(在LockMode的基礎上提供了timeout和scope):
......
/**
* NONE represents LockMode.NONE (timeout + scope do not apply)
*/
public static final LockOptions NONE = new LockOptions(LockMode.NONE);
/**
* READ represents LockMode.READ (timeout + scope do not apply)
*/
public static final LockOptions READ = new LockOptions(LockMode.READ);
/**
* UPGRADE represents LockMode.UPGRADE (will wait forever for lock and
* scope of false meaning only entity is locked)
*/
public static final LockOptions UPGRADE = new LockOptions(LockMode.UPGRADE);
public LockOptions(){}
public LockOptions( LockMode lockMode) {
this.lockMode = lockMode;
}
.....
public static final int NO_WAIT = 0;
/**
* Indicates that there is no timeout for the acquisition.
* @see #getTimeOut
*/
public static final int WAIT_FOREVER = -1;
private int timeout = WAIT_FOREVER;
private boolean scope=false;
......
LockOptions提供的加鎖機制要比LockMode少很多,但是LockMode多出的加鎖機制一般隻是供Hibernate内部實作使用的
保證了操作的獨占性,但嚴重影響資料庫性能
二,樂觀鎖
樂觀鎖大多基于資料版本記錄機制實作,既為資料增加一個版本辨別
在資料庫中增加version列,用來記錄每行資料的版本
Hibernate配置檔案中,version節點需要在id節點之後并緊跟id節點
<?xml version="1.0"?>
<!DOCTYPE hibernate-mapping PUBLIC
"-//Hibernate/Hibernate Mapping DTD 3.0//EN"
"http://hibernate.sourceforge.net/hibernate-mapping-3.0.dtd" >
<hibernate-mapping>
<class name="com.po.User"
table="TEST_USER">
<id name="id" column="id" type="java.lang.Integer">
<generator class="assigned"/>
</id>
<version name="version"
column="version"
type="java.lang.Integer"/>
<property name="name"
column="name"
type="java.lang.String"
not-null="true"
unique="true"
length="20"/>
<property name="age"
column="age"
type="java.lang.Integer"
not-null="true"
unique="false"
length="0"/>
</class>
</hibernate-mapping>
每次更新User對象時時,對應行的version字段都在增加
public class Test {
public static void main(String[] args) {
Configuration conf = new Configuration();
SessionFactory sessionFactory = conf.configure().buildSessionFactory();
Session sess1=sessionFactory.openSession();
Session sess2=sessionFactory.openSession();
try{
User user1 = (User)sess1.get(User.class, 1);
User user2 = (User)sess2.get(User.class, 1);
System.out.println("v1="+user1.getVersion()+"--v2="+user2.getVersion());
Transaction tx1 = sess1.beginTransaction();
Transaction tx2 = sess2.beginTransaction();
user1.setName("ll");
tx1.commit();
System.out.println("v1="+user1.getVersion()+"--v2="+user2.getVersion());
user2.setName("LL");
tx2.commit();
}catch(Exception e){
e.printStackTrace();
}finally{
sess1.close();
sess2.close();
}
}
}
運作結果如下,可以看到由于tx1送出時,version字段已經被修改,tx2送出時會抛出異常:
Hibernate: select user0_.id as id0_0_, user0_.version as version0_0_, user0_.name as name0_0_, user0_.age as age0_0_
from TEST_USER user0_ where user0_.id=?
Hibernate: select user0_.id as id0_0_, user0_.version as version0_0_, user0_.name as name0_0_, user0_.age as age0_0_
from TEST_USER user0_ where user0_.id=?
v1=0--v2=0
Hibernate: update TEST_USER set version=?, name=?, age=? where id=? and version=?
v1=1--v2=0
Hibernate: update TEST_USER set version=?, name=?, age=? where id=? and version=?
Exception in thread "main" org.hibernate.StaleObjectStateException: Row was updated or deleted by another transaction
(or unsaved-value mapping was incorrect): [com.po.User#1]
at org.hibernate.persister.entity.AbstractEntityPersister.check(AbstractEntityPersister.java:1932)
at org.hibernate.persister.entity.AbstractEntityPersister.update(AbstractEntityPersister.java:2576)
at org.hibernate.persister.entity.AbstractEntityPersister.updateOrInsert(AbstractEntityPersister.java:2476)
at org.hibernate.persister.entity.AbstractEntityPersister.update(AbstractEntityPersister.java:2803)
at org.hibernate.action.EntityUpdateAction.execute(EntityUpdateAction.java:113)
at org.hibernate.engine.ActionQueue.execute(ActionQueue.java:273)
at org.hibernate.engine.ActionQueue.executeActions(ActionQueue.java:265)
at org.hibernate.engine.ActionQueue.executeActions(ActionQueue.java:185)
at org.hibernate.event.def.AbstractFlushingEventListener.performExecutions(AbstractFlushingEventListener.java:321)
at org.hibernate.event.def.DefaultFlushEventListener.onFlush(DefaultFlushEventListener.java:51)
at org.hibernate.impl.SessionImpl.flush(SessionImpl.java:1216)
at org.hibernate.impl.SessionImpl.managedFlush(SessionImpl.java:383)
at org.hibernate.transaction.JDBCTransaction.commit(JDBCTransaction.java:133)
at com.test.Test.main(Test.java:43)
除了使用version作為版本辨別,還可以使用timestamp作為版本辨別
timestamp節點沒有type屬性:
<?xml version="1.0"?>
<!DOCTYPE hibernate-mapping PUBLIC
"-//Hibernate/Hibernate Mapping DTD 3.0//EN"
"http://hibernate.sourceforge.net/hibernate-mapping-3.0.dtd" >
<hibernate-mapping>
<class name="com.po.User"
table="TEST_USER">
<id name="id" column="id" type="java.lang.Integer">
<generator class="assigned"/>
</id>
<timestamp name="updatetime"
column="updatetime"/>
<property name="name"
column="name"
type="java.lang.String"
not-null="true"
unique="true"
length="20"/>
<property name="age"
column="age"
type="java.lang.Integer"
not-null="true"
unique="false"
length="0"/>
</class>
</hibernate-mapping>
在某些情況下,不允許修改資料庫的表結構,此時Hibernate也有相應的處理手段:
<?xml version="1.0"?>
<!DOCTYPE hibernate-mapping PUBLIC
"-//Hibernate/Hibernate Mapping DTD 3.0//EN"
"http://hibernate.sourceforge.net/hibernate-mapping-3.0.dtd" >
<hibernate-mapping>
<class name="com.po.User"
table="TEST_USER"
optimistic-lock="all"
dynamic-update="true"
dynamic-insert="true"
>
<id name="id" column="id" type="java.lang.Integer">
<generator class="assigned"/>
</id>
<property name="name"
column="name"
type="java.lang.String"
not-null="true"
unique="true"
length="20"/>
<property name="age"
column="age"
type="java.lang.Integer"
not-null="true"
unique="false"
length="0"/>
</class>
</hibernate-mapping>
此時Hibernate将使用User類的所有字段作為版本控制資訊
樂觀鎖相較悲觀鎖提高了不少性能,但是有一定的局限性,由于是在應用層加鎖,如果此時在資料中直接修改資料(或其它應用程式修改資料庫中的資料),應用層是無法感覺到這種變化的,需要配合其它技術手段一起使用