天天看點

hibernate3第六章之性能優化相關幾個問題_2事務隔離機制

一、事務隔離機制_悲觀鎖_樂觀鎖

1.事務特性:ACID

2.常見問題事務:

(1)第一類丢失更新(lost update)

時間 取款事務A 存款事務B
1 事務開始
2 事務開始
3 查詢餘額為1000
4 查詢餘額為1000
5 存入100變為1100
6 送出事務
7 取出100變為900
8 撤銷事務復原
9 餘額最後為1000(丢失了B事務)

(2)髒讀(dirty read)

時間 取款事務A 存款事務B
1 事務開始
2 事務開始
3 查詢餘額為1000
4 存入100變為1100
5 查詢餘額為1100(髒資料)
6 撤銷事務復原(餘額為1000)
7 取款1100
8 送出事務失敗

(3)不可重複讀(non-repeatable read)

時間 取款事務A 存款事務B
1 事務開始
2 事務開始
3 查詢餘額為1000
4  存入100變為1100
5 送出事務(餘額為1100)
6 查詢餘額為1100
7  送出事務
8

(4)不可重複讀特殊情況:第二類丢失更新(second lost update)

時間 取款事務A 存款事務B
1 事務開始
2 事務開始
3 查詢餘額為1000
4 查詢餘額為1000
5 取款100變為900
6 送出事務
7 存入100變為1100
8 送出事務
9 餘額為1100丢失了A事務

(5)幻讀(phantom read)幻讀

時間 查詢學生事務A 新增學生事務B
1 事務開始
2 事務開始
3 查詢學生為10人
4 添加一名學生
5 查詢學生為11人
6 送出事務
7  送出事務
8

3.資料庫的事務隔離級别有4種:

Field Summary

Modifier and Type Field and Description

static int

TRANSACTION_NONE

A constant indicating that transactions are not supported.

static int

TRANSACTION_READ_COMMITTED

A constant indicating that dirty reads are prevented; non-repeatable reads and phantom reads can occur.

static int

TRANSACTION_READ_UNCOMMITTED

A constant indicating that dirty reads, non-repeatable reads and phantom reads can occur.

static int

TRANSACTION_REPEATABLE_READ

A constant indicating that dirty reads and non-repeatable reads are prevented; phantom reads can occur.

static int

TRANSACTION_SERIALIZABLE

A constant indicating that dirty reads, non-repeatable reads and phantom reads are prevented.

TRANSACTION_NONE:沒有,不設定隔離機制

1TRANSACTION_READ_UNCOMMITTED:可以讀取沒有送出時更改的資料,會出現髒讀、不可重複讀、幻讀等問題

2TRANSACTION_READ_COMMITTED:隻能讀事務送出以後的資料,會出現不可重複讀、幻讀問題

4TRANSACTION_REPEATABLE_READ:讀資料的時候給這條語句加一把鎖,别人可以讀但是不能改,會出現幻讀問題

8TRANSACTION_SERIALIZABLE:序列化,串行,必須等我都操作完以後,别人才能操作,排着隊運作,解決所有問題

事務級别越高效率越低,但是越安全;級别是(1,2,4,8)為什麼不是1,2,3,4?因為換算成二進制後1,2,4,8為(0001,0010,0100,1000)這樣用移位運算效率要高,但是在JAVA中不要求這種級别的效率,在C和C++中會要求這麼寫。

4.hibernate事務隔離級别:

一般hibernate設定事務隔離級别為2:hibernate.connection.isolation=2      2級别的事務隔離級别會出現不可重複讀和幻讀等問題。

其中不可重複讀的問題可以通過悲觀鎖和樂觀鎖解決,幻讀問題暫時不考慮。

悲觀鎖:

import javax.persistence.*;

@Entity 
public class Account {
	private int id;
	private int balance;
	@Id
	@GeneratedValue
	public int getId() {
		return id;
	}
	public void setId(int id) {
		this.id = id;
	}
	public int getBalance() {
		return balance;
	}
	public void setBalance(int balance) {
		this.balance = balance;
	}  
}
           
import org.hibernate.*;
import org.hibernate.cfg.AnnotationConfiguration; 
import org.hibernate.tool.hbm2ddl.SchemaExport;
import org.junit.AfterClass;
import org.junit.BeforeClass;
import org.junit.Test;

public class Modeltest {

	private static SessionFactory sf=null;
	@BeforeClass
	public static void beforeClass(){
		sf=new AnnotationConfiguration().configure().buildSessionFactory();
	}
	@Test
	public void testSave(){
		Session session=sf.getCurrentSession();
		session.beginTransaction(); 
		Account a=new Account();  
		a.setBalance(100);
		session.save(a); 
		
		session.getTransaction().commit();
	}

	@Test
	public void testPessimistilock(){
		Session session=sf.getCurrentSession();
		session.beginTransaction();    
		/*在取出資料的時候給這條資料加一把鎖,别人不能修改
		 * load中的lock有以下幾種
		 * 	NONE:無鎖機制,事務結束時,自動切換到none
		 * (如果緩存中存在對象,直接傳回該對象的引用,否則通過select語句到資料庫中加載該對象,預設值.)
		 * 	READ:查詢的時候使用,hibernate自動擷取鎖
		 * (不管緩存中是否存在對象,總是通過select語句到資料庫中加載該對象,如果映射檔案中設定了版本元素,就執行版本檢查,比較緩存中的對象是否和資料庫中對象版本一緻)
		 * 	WRITE:在insert、update的時候自動擷取鎖
		 * (儲存對象時會自動使用這種鎖定模式,僅供Hibernate内部使用,應用程式中不應該使用它)
		 * 以上三種是hibernate内部使用,不用管它
		 * 
		 * 
		 * 	FORCE(強制更新資料庫中對象的版本屬性,進而表明目前事務已經更新了這個對象)
		 * 	UPGRADE:利用資料庫的 for update 子句加鎖,一般都是寫這種
		 * (不管緩存中是否存在對象,總是通過select語句到資料庫中加載該對象,如果映射檔案中設定了版本元素,就執行版本檢查,比較緩存中的對象是否和資料庫中對象的版本一緻,如果資料庫系統支援悲觀鎖(如Oracle/MySQL),就執行select...for update語句,如果不支援(如Sybase),執行普通select語句)
		 * 	UPGRADE_NOWAIT: Oracle 的特定實作,利用 Oracle 的 for update nowait 子句實作加鎖
		 * (和LockMode.UPGRADE具有同樣功能,此外,對于Oracle等支援update nowait的資料庫,執行select...for update nowait語句,nowait表明如果執行該select語句的事務不能立即獲得悲觀鎖,那麼不會等待其它事務釋放鎖,而是立刻抛出鎖定異常)
		*/
		Account a=(Account)session.load(Account.class, 1, LockMode.UPGRADE);
		a.setBalance(a.getBalance()-10);
		session.getTransaction().commit();
	} 
	
	@Test
	public void testSchemaExport(){
		new SchemaExport(new AnnotationConfiguration().configure()).create(false, true);
	}
	@AfterClass
	public static void afterClass(){
		sf.close();
	}
}
           

樂觀鎖:在實體裡加一個version版本字段,誰更改一次就加1,如果你在操作的時候對比一下version,一樣的話證明沒人更改。

要在version字段上添加@Version

import javax.persistence.*;

@Entity 
public class Account {
	private int id;
	private int balance;
	private int version;
	@Version
	public int getVersion() {
		return version;
	}
	public void setVersion(int version) {
		this.version = version;
	}
	@Id
	@GeneratedValue
	public int getId() {
		return id;
	}
	public void setId(int id) {
		this.id = id;
	}
	public int getBalance() {
		return balance;
	}
	public void setBalance(int balance) {
		this.balance = balance;
	}  
}
           
import org.hibernate.*;
import org.hibernate.cfg.AnnotationConfiguration; 
import org.hibernate.tool.hbm2ddl.SchemaExport;
import org.junit.AfterClass;
import org.junit.BeforeClass;
import org.junit.Test;

public class Modeltest {

	private static SessionFactory sf=null;
	@BeforeClass
	public static void beforeClass(){
		sf=new AnnotationConfiguration().configure().buildSessionFactory();
	}
	@Test
	public void testSave(){
		//先插入c再插入t保證每個t都關聯不同的c
		Session session=sf.getCurrentSession();
		session.beginTransaction(); 
		Account a=new Account();  
		a.setBalance(100);
		session.save(a); 
		
		session.getTransaction().commit();
	}

	@Test
	public void testOptimisticlock(){
		// 
		Session session=sf.openSession();
		Session session1=sf.openSession();
		
		session.beginTransaction();     
		Account a=(Account)session.load(Account.class, 1);
		
		session1.beginTransaction();     
		Account a1=(Account)session1.load(Account.class, 1);

		a.setBalance(500);
		a1.setBalance(1500);

		session.getTransaction().commit();//事務送出,會對比一下version都是0,沒人更改過,可以送出,version增加為1
		System.out.println(a.getVersion());
		
		session1.getTransaction().commit();//事務送出,對比version發現目前資料庫不是0,則報錯,不做操作。
		System.out.println(a1.getVersion());
		
		session.close(); 
		session1.close();
	} 
	
	@Test
	public void testSchemaExport(){
		new SchemaExport(new AnnotationConfiguration().configure()).create(false, true);
	}
	@AfterClass
	public static void afterClass(){
		sf.close();
	}
}
           
時間 取款事務A 存款事務B
1 事務開始
2 事務開始
3 查詢餘額為1000
4 存入100變為1100
5 查詢餘額為1100(髒資料)
6 撤銷事務復原(餘額為1000)
7 取款1100
8 送出事務失敗
時間 取款事務A 存款事務B
1 事務開始
2 事務開始
3 查詢餘額為1000
4 查詢餘額為1000
5 存入100變為1100
6 送出事務
7 取出100變為900
8 撤銷事務復原
9 餘額最後為1000(丢失了B事務)