事務隔離
使用封鎖技術,事務對申請的資源加鎖,但是會影響數據庫性能。根據數據對象封鎖的程度,可以分成多種不同的事務隔離級別。
數據并發執行時,產生不一致的現象:
1,丟失更新(Lost Update)
兩個事務讀入同一數據并修改,然后提交修改,T2 提交的結果破壞了 T1 提交的結果,導致 T1 的修改丟失。
2, 不可重復讀
事務T1 讀取數據后,事務T2 執行了同一數據的跟新操作,使得事務 T1 無法再現前一次讀取的結果。
事務1 讀取某一數據后,事務2 對該數據作了修改,事務1 再次讀取時,得到數據和前一次不一致。
① 事務1 讀取某一些記錄后,事務2 刪除了同一數據源的部分數據,事務1 再次讀取時,發現某些記錄丟失。
① 事務1 讀取某一些記錄后,事務2 插入了同一數據源的新數據,事務1 再次讀取時,發現某些記錄增加。
3, 讀“臟”數據
事務T1 修改某一數據,并將其寫回物理數據庫。事務T2 讀取同一數據后,事務T1 由于某種原因被撤銷,數據庫將已經修改的數據恢復原值,導致事務T2 保持的數據和數據庫中的數據產生了不一致。
ANSI SQL-99 標準定義了下列隔離級別:
Hibernate 在配置文件中聲明事務的隔離級別,Hibenate 獲取數據庫連接后,將根據隔離級別自動設置數據庫連接為指定的事務隔離級別。
<property name="connection.isolation">8</property>
● 未提交讀(Read Uncommitted):隔離事務的最低級別,只能保證不會讀取到物理上損壞的數據。Hibernate配置:1;允許產生:1,2,3
● 已提交讀(Read Committed):常見數據庫引擎的默認級別,保證一個事務不會讀取到另一個事務已修改但未提交的數據。Hibernate配置:2;允許產生:1,2
● 可重復讀(Repeatable Read):保證一個事務不能更新已經由另一個事務讀取但是未提交的數據。相當于應用中的已提交讀和樂觀并發控制。Hibernate配置:4;允許產生:1
● 可串行化(Serializable):隔離事務的最高級別,事務之間完全隔離。系統開銷最大。Hibernate配置:8;這種情況很容易造成死鎖的問題,hibernate表現為:
Deadlock found when trying to get lock; try restarting transaction
2-3、并發控制類型
根據使用的鎖定策略和隔離等級,可以把事務的并發控制分為兩種:
① 悲觀并發控制
用戶使用時鎖定數據。主要應用于數據爭用激烈的環境中,以及發生并發沖突時用鎖保護數據的成本低于回滾事務成本的環境中。
Hibernate 的悲觀鎖定不在內存中鎖定數據,由底層數據庫負責完成。
② 樂觀并發控制
用戶讀取數據時不鎖定數據。當一個用戶更新數據時,系統將進行檢查該用戶讀取數據后其他用戶是否更改了該數據,是則產生一個錯誤,一般情況下,收到錯誤信息的用戶將回滾事務并重新開始。主要用戶數據爭用不大,且偶爾回滾事務的成本低于讀取數據時鎖定數據的成本的環境中。
Hibernate 中使用元素 version 和 timestamp 實現樂觀并發控制模式的版本控制,并提供多種編程方式。版本是數據庫表中的一個字段,可以是一個遞增的整數,也可以是一個時間戳,它們對應 Java 持久化類的一個屬性。事務提交成功后,Hibernate 自動修改版本號。如果另外一個事務同時訪問同一數據,若發現提交前的版本號和事前載入的版本號有出入,則認為發生了沖突,事務停止執行,撤銷操作,并拋出異常。應用程序必須捕捉該異常并做出一定的處理。
⒈應用程序級別的版本控制
⒉長生命周期會話的自動化版本控制
⒊托管對象的自動化版本控制
⒋定制自動化版本控制
二 實際應用中我們使用Read Committed作為我們的事務級別 也就是 已提交讀(Read Committed),因為如果用戶 有權限 修改一個 數據 ,那 那么 就可以提交這個事務,這是系統需要解決的權限問題,由于這種情況會造成第二類數據丟失的情況,因此要配備樂觀鎖的機制,這種事物方法使用比較多。下面 舉例實驗:
1,例如A持久化后會被事務關聯(事務針對修改,插入,不針對查詢,如果對于一般不需要修改的表,如字典表,可以不配置事務,而是配置二級緩存來提高性能)
class A代碼(先不加樂觀鎖,會造成第二類數據丟失):
@Entity
public class A {
private int id;
private int anum;
private String aname;
@Id
@GeneratedValue(strategy=GenerationType.IDENTITY)
public int getId() {
return id;
}
//get..set
}
hibernate配置:<property name="connection.isolation">2</property>
事先插入一條數據(a.setAnum(1);)
測試代碼:
public static void test(){
new Thread(new Runnable(){//線程2,啟動事務后不提交,然后讓線程1啟動
public void run() {
Session session = HibernateSessionFactory.getSession();
Transaction tt = session.beginTransaction();
System.out.println("t2 statr....");
A a = (A) session.get(A.class, 1);
System.out.println("未修改之前的num:"+a.getAnum());
a.setAnum(a.getAnum()+1);
session.update(a);
try {
Thread.sleep(5000);//讓出時間讓線程1執行
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
tt.commit();
session.clear();//清除一級緩存
System.out.println("t2 cummit end....");
A a1 = (A) session.get(A.class, 1);
System.out.println("修改之后的num:"+a1.getAnum());
session.close();
}
}).start();//正確結果為1+1=2
new Thread(new Runnable(){
public void run() {
Session session = HibernateSessionFactory.getSession();
Transaction tt = session.beginTransaction();
System.out.println("t1 statr....");
A a = (A) session.get(A.class, 1);
a.setAnum(a.getAnum()+2);
session.update(a);
tt.commit();
System.out.println("1 cummit end....");
session.clear();//清除一級緩存
session.close();
}
}).start();//正確結果為1+1=3
}
打印信息:
t2 statr....
Hibernate: select a0_.id as id0_0_, a0_.aname as aname0_0_, a0_.anum as anum0_0_ from A a0_ where a0_.id=?//讓出時間讓t1執行
t1 statr....
Hibernate: select a0_.id as id0_0_, a0_.aname as aname0_0_, a0_.anum as anum0_0_ from A a0_ where a0_.id=?
未修改之前的num:1
Hibernate: update A set aname=?, anum=? where id=?
1 cummit end....
Hibernate: update A set aname=?, anum=? where id=?
t2 cummit end....
Hibernate: select a0_.id as id0_0_, a0_.aname as aname0_0_, a0_.anum as anum0_0_ from A a0_ where a0_.id=?
修改之后的num:2
可以看出他t1本來應該是3但是被t2覆蓋
為此我們加上鎖:
修改A(鎖可以有兩種,version,和時間鎖,但是時間鎖有個精確度問題)使用version:
private int version;
...
@Version//使用version注釋來表明版本控制
public int getVersion() {
return version;
}
測試(同樣的測試代碼):
打印信息:
t2 statr....
Hibernate: select a0_.id as id0_0_, a0_.aname as aname0_0_, a0_.anum as anum0_0_, a0_.version as version0_0_ from A a0_ where a0_.id=?
t1 statr....
Hibernate: select a0_.id as id0_0_, a0_.aname as aname0_0_, a0_.anum as anum0_0_, a0_.version as version0_0_ from A a0_ where a0_.id=?
未修改之前的num:1
Hibernate: update A set aname=?, anum=?, version=? where id=? and version=?
1 cummit end....
Hibernate: update A set aname=?, anum=?, version=? where id=? and version=?
Exception in thread "Thread-0" org.hibernate.StaleObjectStateException: Row was updated or deleted by another transaction (or unsaved-value mapping was incorrect): [com.eric.po.A#1]
at org.hibernate.persister.entity.AbstractEntityPersister.check(AbstractEntityPersister.java:1792)
at org.hibernate.persister.entity.AbstractEntityPersister.update(AbstractEntityPersister.java:2435)
at org.hibernate.persister.entity.AbstractEntityPersister.updateOrInsert(AbstractEntityPersister.java:2335)
at org.hibernate.persister.entity.AbstractEntityPersister.update(AbstractEntityPersister.java:2635)
at org.hibernate.action.EntityUpdateAction.execute(EntityUpdateAction.java:115)
at org.hibernate.engine.ActionQueue.execute(ActionQueue.java:279)
at org.hibernate.engine.ActionQueue.executeActions(ActionQueue.java:263)
at org.hibernate.engine.ActionQueue.executeActions(ActionQueue.java:168)
at org.hibernate.event.def.AbstractFlushingEventListener.performExecutions(AbstractFlushingEventListener.java:321)
at org.hibernate.event.def.DefaultFlushEventListener.onFlush(DefaultFlushEventListener.java:50)
at org.hibernate.impl.SessionImpl.flush(SessionImpl.java:1027)
at org.hibernate.impl.SessionImpl.managedFlush(SessionImpl.java:365)
at org.hibernate.transaction.JDBCTransaction.commit(JDBCTransaction.java:137)
at com.eric.dao.Testcreate$1.run(Testcreate.java:139)
at java.lang.Thread.run(Thread.java:619)
此時樂觀所就起作用了,防止了數據的丟失。針對異常可自行捕獲。