本文研究的主要是hibernate的session_flush與隔離級別,具體介紹和實例如下。
概念介紹
我們先來看一些概念:
1.臟讀:臟讀又稱為無效數(shù)據(jù)的讀出,是指在數(shù)據(jù)庫訪問中,事物t1將某一值修改,然后事物t2讀取該值,此后t1因為某種原因撤銷對該值的修改,這就導(dǎo)致了t2所讀取的數(shù)據(jù)是無效的。臟讀就是指當(dāng)一個事物正在訪問數(shù)據(jù),并且對數(shù)據(jù)進(jìn)行了修改,而這種修改還沒有提交到數(shù)據(jù)庫中,這時,另外一個事物也訪問這個數(shù)據(jù),然后使用了這個數(shù)據(jù)。因為這個數(shù)據(jù)還是沒有提交的數(shù)據(jù),那么另外一個事物讀到的這個數(shù)據(jù)就是臟數(shù)據(jù),依據(jù)臟數(shù)據(jù)所做的操作是不正確的。
2.不可重復(fù)讀:比如我在讀一個帖子,我查出來的數(shù)據(jù)是張三、李四,然后我一刷新發(fā)現(xiàn)最開始的張三變成了張八,這就是所謂的不可重復(fù)讀,因為我讀出的數(shù)據(jù)沒重復(fù)了嘛。
3.幻讀:我在查數(shù)據(jù)的時候,開始查出來的記錄為3條,我一刷新,發(fā)現(xiàn)記錄變?yōu)榱?條,這就是幻讀。
4.提交讀:提交了之后才可以讀取,oracle默認(rèn)就是這個,這種方式是不存在臟讀的。
5.可重復(fù)度:很顯然是和不可重復(fù)讀相反的,它可以避免不可重復(fù)讀,但是這個不能避免幻讀。
6.序列化:這種方式非常嚴(yán)格,通俗的說就是,當(dāng)我在做一件事情的時候,其他任何人都不能做,非常安全,但是效率極低。
隔離級別
下面我們通過實際的例子來體會hibernate清除緩存的應(yīng)用。
hibernate映射數(shù)據(jù)庫和主鍵的生成策略有關(guān)。
案例一
uuid的方式生成主鍵的例子:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
|
public class user { private string uid; private string uname; private date birthday; public string getuid() { return uid; } public void setuid(string uid) { this .uid = uid; } public string getuname() { return uname; } public void setuname(string uname) { this .uname = uname; } public date getbirthday() { return birthday; } public void setbirthday(date birthday) { this .birthday = birthday; } } |
user.hbm.xml:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
|
<?xml version= "1.0" ?> <!doctype hibernate-mapping public "-//hibernate/hibernate mapping dtd 3.0//en" "http://hibernate.sourceforge.net/hibernate-mapping-3.0.dtd" > <!-- package 表示實體類的包名 --> <hibernate-mapping package = "com.lixue.bean" > <!-- class 結(jié)點(diǎn)的name表示實體的類名,table表示實體映射到數(shù)據(jù)庫中table的名稱 --> < class name= "user" table= "t_user" > <id name= "uid" > <!-- 通過uuid的方式生成 --> <generator class = "uuid" /> </id> <property name= "uname" /> <property name= "birthday" /> </ class > </hibernate-mapping> |
測試方法:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
|
/** * 測試uuid主鍵生成策略 */ public void testsave1(){ /*定義的session和事物*/ session session = null; transaction transaction = null; try { /*獲取session和事物*/ session = hibernateutils.getsession(); transaction = session.begintransaction(); /*創(chuàng)建用戶*/ user user = new user(); user.setuname("習(xí)近平"); user.setbirthday(new date()); /** * 因為user的主鍵生成策略為uuid,所以調(diào)用完save之后,只是將user納入到session管理 * 不會發(fā)出insert語句,但是id已經(jīng)生成,persistencecontext中的existsindatebase狀態(tài)為false */ session.save(user); /** * 調(diào)用flush,hibernate會清理緩存(將session->insertions中臨時集合中的對象插入數(shù)據(jù)庫,在清空臨時集合) * 此時并不能在數(shù)據(jù)庫中看到數(shù)據(jù),但是如果數(shù)據(jù)庫的隔離級別設(shè)置為未提交讀, * 那么我們可以看到flush過的數(shù)據(jù),并且persistencecontext中existsindatabase狀態(tài)為true */ session.flush(); /** * 提交事物 * 默認(rèn)情況下,commit操作會執(zhí)行flush清理緩存, * 所以不用顯示的調(diào)用flush * commit后數(shù)據(jù)是無法回滾的 */ transaction.commit(); } catch (exception e) { e.printstacktrace(); transaction.rollback(); } finally { hibernateutils.closesession(session); } } |
我們可以通過斷點(diǎn)調(diào)試程序:
1.由于user的主鍵生成側(cè)率為uuid,調(diào)用save()方法之后,只能將user對象納入session管理,不會發(fā)出insert語句,但是id已經(jīng)生成了(注:save之后又兩個地方很重要,首先是session->actionqueue->insertions->elementdata數(shù)組中有某個元素存儲了我們的對象,這是一個臨時集合對象,另外還有一個就是persistencecontext->entityentries->map->table->某個數(shù)組元素->value存儲了該對象,value下面還有一個屬性那就是existsindatabase代表數(shù)據(jù)庫中是否有對應(yīng)的數(shù)據(jù))。如圖:
2.調(diào)用完flush()方法之后,會清空session中的actionqueue的臨時存儲的值,然后將persistencecontext中的existsindatabase的值設(shè)為true,表示此時,數(shù)據(jù)庫中有對應(yīng)的數(shù)據(jù),但是此時打開數(shù)據(jù)庫打開表是看不到數(shù)據(jù)的,因為我們mysql數(shù)據(jù)庫默認(rèn)的隔離級別為提交讀,即,必須提交才能讀取數(shù)據(jù),調(diào)用commit()方法之后,數(shù)據(jù)庫中有數(shù)據(jù)。
案例二
native方式生成主鍵的例子:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
|
public class user1 { private integer uid; private string uname; private date birthday; public integer getuid() { return uid; } public void setuid(integer uid) { this .uid = uid; } public string getuname() { return uname; } public void setuname(string uname) { this .uname = uname; } public date getbirthday() { return birthday; } public void setbirthday(date birthday) { this .birthday = birthday; } } |
user1.hbm.xml:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
|
<?xml version= "1.0" ?> <!doctype hibernate-mapping public "-//hibernate/hibernate mapping dtd 3.0//en" "http://hibernate.sourceforge.net/hibernate-mapping-3.0.dtd" > <!-- package 表示實體類的包名 --> <hibernate-mapping package = "com.lixue.bean" > <!-- class 結(jié)點(diǎn)的name表示實體的類名(賦值映射文件的時候要記得修改類名,否則會出現(xiàn)bug),table表示實體映射到數(shù)據(jù)庫中table的名稱 --> < class name= "user1" table= "t_user1" > <id name= "uid" > <!-- 自增長 --> <generator class = "native" /> </id> <property name= "uname" /> <property name= "birthday" /> </ class > </hibernate-mapping> |
測試方法:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
|
/** * 測試native主鍵生成策略 */ public void testsave2(){ /*定義的session和事物*/ session session = null; transaction transaction = null; try { /*獲取session和事物*/ session = hibernateutils.getsession(); transaction = session.begintransaction(); /*創(chuàng)建用戶*/ user1 user = new user1(); user.setuname("李克強(qiáng)"); user.setbirthday(new date()); /** * 因為user1的主鍵生成策略是native,所以調(diào)用session.save()后,將執(zhí)行insert語句,并且會清空臨時集合對象 * 返回由數(shù)據(jù)庫生成的id,納入session的管理,修改了session中existsindatabase狀態(tài)為true, * 如果數(shù)據(jù)庫的隔離級別設(shè)置為未提交讀,那么我們可以看到save過的數(shù)據(jù) */ session.save(user); transaction.commit(); } catch (exception e) { e.printstacktrace(); transaction.rollback(); } finally { hibernateutils.closesession(session); } } |
通過斷點(diǎn)調(diào)試程序:
1.由于主鍵的生成策略為native,所以調(diào)用save()方法之后,將執(zhí)行insert語句,并且會清空臨時集合對象中的數(shù)據(jù),返回由數(shù)據(jù)庫生成的id。
2.將對象納入session管理,修改了persistencecontext中的existsindatabase屬性為true(表示數(shù)據(jù)庫中有對應(yīng)的數(shù)據(jù),但是看不到,因為隔離界別的原因)
案例三
我們再來測試一下hibernate的另一個方法,那就是evict(),表示將對象從session逐出。
針對uuid主鍵生成策略的程序,在來一個測試方法:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
|
/** * 測試uuid主鍵生成策略 */ public void testsave3(){ /*定義的session和事物*/ session session = null; transaction transaction = null; try { /*獲取session和事物*/ session = hibernateutils.getsession(); transaction = session.begintransaction(); /*創(chuàng)建用戶*/ user user = new user(); user.setuname("胡錦濤"); user.setbirthday(new date()); /** * 因為user的主鍵生成策略為uuid,所以調(diào)用完save之后,只是將user納入到session管理 * 不會發(fā)出insert語句,但是id已經(jīng)生成。session中的existsindatebase狀態(tài)為false */ session.save(user); /*將user對象從session中逐出,即從persistencecontext的entityentries屬性中逐出*/ session.evict(user); /** * 無法成功提交,因為hibernate在清理緩存時,在session的insertions臨時集合中取出user對象進(jìn)行insert * 操作后,需要更新entityentries屬性中的existsindatabase為true,而我們調(diào)用了evict方法 * 將user從session的entityentries中逐出了,所以找不到existsindatabase屬性,無法更新,拋出異常 */ transaction.commit(); } catch (exception e) { e.printstacktrace(); transaction.rollback(); } finally { hibernateutils.closesession(session); } } |
通過斷點(diǎn)調(diào)試:
1.由于使用的是uuid的主鍵生成策略,所以調(diào)用save()方法之后,不會發(fā)送insert語句,只是將對象納入了session管理,id已經(jīng)生成,數(shù)據(jù)庫中沒有與之對應(yīng)的數(shù)據(jù)(即existsindatabase屬性值為false)。
2.調(diào)用evict()之后,將user對象從session中逐出,即從persistencecontext的entityentries屬性中逐出。
3.當(dāng)我再調(diào)用commit()方法時,我們會發(fā)現(xiàn),我們的數(shù)據(jù)保存不了,因為一開始我們的existsindatabase屬性為false,即數(shù)據(jù)庫中不存在對應(yīng)數(shù)據(jù),緊接著我們又調(diào)用了evict()將persistencecontext中的對象屬性(existsindatabase屬性也包括在內(nèi))全刪除了,但是actionqueue中的臨時存儲數(shù)據(jù)還沒被刪除。我們只調(diào)用commit()方法時會先隱式的調(diào)用flush()方法,這個方法的作用之前也講過,它會將actionqueue中的臨時對象進(jìn)行insert操作,然后將persistencecontext中的existsindatabase屬性值設(shè)為true,但很遺憾,persistencecontext中并沒有existsindatabase屬性,所以會出現(xiàn)錯誤,導(dǎo)致無法保存。
為此,我們改進(jìn)上述程序:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
|
/** * 測試uuid主鍵生成策略 */ public void testsave4(){ /*定義的session和事物*/ session session = null; transaction transaction = null; try { /*獲取session和事物*/ session = hibernateutils.getsession(); transaction = session.begintransaction(); /*創(chuàng)建用戶*/ user user = new user(); user.setuname("胡錦濤"); user.setbirthday(new date()); /** * 因為user的主鍵生成策略為uuid,所以調(diào)用完save之后,只是將user納入到session管理 * 不會發(fā)出insert語句,但是id已經(jīng)生成。persistencecontext中的existsindatebase狀態(tài)為false */ session.save(user); /** * flush后hibernate會清理緩存,會將user對象保存到數(shù)據(jù)庫中,將session中的insertions中的user對象 * 清除,并且設(shè)置persistencecontext中existsindatabase的狀態(tài)為true */ session.flush(); /*將user對象從session中逐出,即從persistencecontext的entityentries屬性中逐出*/ session.evict(user); /** * 可以成功提交,因為hibernate在清理緩存時,在session的insertions集合中無法 * 找到user對象(調(diào)用flush時清空了),所以就不會發(fā)出insert語句,也不會更新session中的existsindatabase的狀態(tài) */ transaction.commit(); } catch (exception e) { e.printstacktrace(); transaction.rollback(); } finally { hibernateutils.closesession(session); } } |
注:修改后的程序我們在save之后顯示的調(diào)用了flush()方法,再調(diào)用evict()方法。
通過斷點(diǎn)調(diào)試:
1.因為還是uuid的生成策略,所以在調(diào)用save之后,不會發(fā)出insert語句,只是將對象納入session管理,persistencecontext中的existsindatabase屬性為false。
2.調(diào)用完save()之后,我們又調(diào)用了flush()方法,這個方法的作用是清理緩存,即發(fā)出insert語句,將session中的insertions中的臨時對象插入到數(shù)據(jù)庫,然后清空該臨時集合,并且將persistencecontext中的existsindatabase屬性設(shè)置為true。
3.調(diào)用完flush()之后又調(diào)用evict()方法,它的作用是將user對象從session中清除,即清除persistencecontext的entityentries屬性。
4.調(diào)用完evict()方法之后又調(diào)用commit()方法,它的會隱式的先調(diào)用flush()方法,而flush的作用是清除緩存,即將session->insertions臨時集合中的對象insert到數(shù)據(jù)庫中,但是我們之前就調(diào)用了flush()方法(注:調(diào)用完這個方法之后會清空臨時集合),所以臨時集合根本就沒有對象,所以不會發(fā)出insert語句。也不會去更新persistencecontext中的existsindatabase狀態(tài)。可以成功提交。
案例四
我們再來考慮下native方式的主鍵生成策略中使用evict()方法:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
|
/** * 測試native主鍵生成策略 */ public void testsave5(){ /*定義的session和事物*/ session session = null; transaction transaction = null; try { /*獲取session和事物*/ session = hibernateutils.getsession(); transaction = session.begintransaction(); /*創(chuàng)建用戶*/ user1 user = new user1(); user.setuname("馬英九"); user.setbirthday(new date()); /** * 因為user1的主鍵生成策略是native,所以調(diào)用session.save()后,將執(zhí)行insert語句, * 返回由數(shù)據(jù)庫生成的id,納入session的管理,修改了session中existsindatabase狀態(tài)為true,并且清空了臨時集合 * 如果數(shù)據(jù)庫的隔離級別設(shè)置為未提交讀,那么我們可以看到save過的數(shù)據(jù) */ session.save(user); /*將user對象從session中逐出,即從persistencecontext的entityentries屬性中逐出*/ session.evict(user); /** * 可以成功提交,因為hibernate在清理緩存的時候在session的insertions集合中 * 無法找到user對象,所以就不會發(fā)出insert語句,也不會更新session中的existtsindatabase的狀態(tài) */ transaction.commit(); } catch (exception e) { e.printstacktrace(); transaction.rollback(); } finally { hibernateutils.closesession(session); } } |
通過調(diào)試:
1.由于主鍵生成策略為native,所以調(diào)用完save方法之后,馬上就會發(fā)出insert語句,返回由數(shù)據(jù)庫生成的id,將對象納入session管理,修改persistencecontext中的existsindatabase屬性為true即數(shù)據(jù)庫中有與之對應(yīng)的數(shù)據(jù),并且會清空臨時集合中的對象。但是由于mysql隔離級別的原因我們在沒有提交之前是看不到數(shù)據(jù)的。
2.調(diào)用完save之后又調(diào)用evict()方法,將對象從session中逐出,即從persistencecontext中的entityentries中逐出。
3.調(diào)用完evict()方法之后又調(diào)用commit()方法,此時是可以成功保存提交的,因為調(diào)用commit()之前會隱式調(diào)用flush()方法,即清理緩存,去臨時集合中找對象insert到數(shù)據(jù)庫,但是會發(fā)現(xiàn)臨時集合中已經(jīng)沒有數(shù)據(jù)了,所以不會發(fā)出insert語句,也就不會去更新persistencecontext中的existsindatabase屬性。
通過上述幾個案例,我們可以看出,有時候我們需要顯示的調(diào)用flush()方法,去清理緩存。另外,從上面我們也發(fā)現(xiàn)了一個問題,那就是當(dāng)我們save()了數(shù)據(jù),沒提交之前是看不到數(shù)據(jù)的,即數(shù)據(jù)庫的隔離界別限制了,現(xiàn)在我們來說說mysql的隔離級別:
1.查看mysql數(shù)據(jù)庫當(dāng)前的隔離級別:
select @@tx_isolation;
注:從圖中,我們可以看出,mysql數(shù)據(jù)庫默認(rèn)的隔離級別為可重復(fù)讀,也就是說不會出現(xiàn)不可重復(fù)讀,即必須提交之后才能讀。
2.修改mysql當(dāng)前的隔離級別(假設(shè)修改為未提交讀,即沒有提交就可以讀):
set transaction isolation level read uncommited;
總結(jié)
以上就是本文關(guān)于hibernate的session_flush與隔離級別代碼詳解的全部內(nèi)容,希望對大家有所幫助。感興趣的朋友可以繼續(xù)參閱本站其他相關(guān)專題,如有不足之處,歡迎留言指出。感謝朋友們對本站的支持!
原文鏈接:http://blog.csdn.net/lzm1340458776/article/details/32729127