Hibernate的Session_flush與隔離級(jí)別代碼詳解
本文研究的主要是Hibernate的Session_flush與隔離級(jí)別,具體介紹和實(shí)例如下。
概念介紹
我們先來(lái)看一些概念:
1.臟讀:臟讀又稱為無(wú)效數(shù)據(jù)的讀出,是指在數(shù)據(jù)庫(kù)訪問(wèn)中,事物T1將某一值修改,然后事物T2讀取該值,此后T1因?yàn)槟撤N原因撤銷對(duì)該值的修改,這就導(dǎo)致了T2所讀取的數(shù)據(jù)是無(wú)效的。臟讀就是指當(dāng)一個(gè)事物正在訪問(wèn)數(shù)據(jù),并且對(duì)數(shù)據(jù)進(jìn)行了修改,而這種修改還沒(méi)有提交到數(shù)據(jù)庫(kù)中,這時(shí),另外一個(gè)事物也訪問(wèn)這個(gè)數(shù)據(jù),然后使用了這個(gè)數(shù)據(jù)。因?yàn)檫@個(gè)數(shù)據(jù)還是沒(méi)有提交的數(shù)據(jù),那么另外一個(gè)事物讀到的這個(gè)數(shù)據(jù)就是臟數(shù)據(jù),依據(jù)臟數(shù)據(jù)所做的操作是不正確的。
2.不可重復(fù)讀:比如我在讀一個(gè)帖子,我查出來(lái)的數(shù)據(jù)是張三、李四,然后我一刷新發(fā)現(xiàn)最開(kāi)始的張三變成了張八,這就是所謂的不可重復(fù)讀,因?yàn)槲易x出的數(shù)據(jù)沒(méi)重復(fù)了嘛。
3.幻讀:我在查數(shù)據(jù)的時(shí)候,開(kāi)始查出來(lái)的記錄為3條,我一刷新,發(fā)現(xiàn)記錄變?yōu)榱?條,這就是幻讀。
4.提交讀:提交了之后才可以讀取,Oracle默認(rèn)就是這個(gè),這種方式是不存在臟讀的。
5.可重復(fù)度:很顯然是和不可重復(fù)讀相反的,它可以避免不可重復(fù)讀,但是這個(gè)不能避免幻讀。
6.序列化:這種方式非常嚴(yán)格,通俗的說(shuō)就是,當(dāng)我在做一件事情的時(shí)候,其他任何人都不能做,非常安全,但是效率極低。
隔離級(jí)別

下面我們通過(guò)實(shí)際的例子來(lái)體會(huì)Hibernate清除緩存的應(yīng)用。
Hibernate映射數(shù)據(jù)庫(kù)和主鍵的生成策略有關(guān)。
案例一
UUID的方式生成主鍵的例子:
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:
<?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表示實(shí)體類的包名 --> <hibernate-mapping package="com.lixue.bean"> <!-- class結(jié)點(diǎn)的name表示實(shí)體的類名,table表示實(shí)體映射到數(shù)據(jù)庫(kù)中table的名稱 --> <class name="User" table="t_user"> <id name="uid"> <!-- 通過(guò)UUID的方式生成 --> <generator class="uuid"/> </id> <property name="uname"/> <property name="birthday"/> </class> </hibernate-mapping>
測(cè)試方法:
/**
* 測(cè)試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("喜羊羊");
user.setBirthday(new Date());
/**
* 因?yàn)閁ser的主鍵生成策略為uuid,所以調(diào)用完save之后,只是將User納入到Session管理
* 不會(huì)發(fā)出insert語(yǔ)句,但是ID已經(jīng)生成,PersistenceContext中的existsInDatebase狀態(tài)為false
*/
session.save(user);
/**
* 調(diào)用flush,Hibernate會(huì)清理緩存(將session->insertions中臨時(shí)集合中的對(duì)象插入數(shù)據(jù)庫(kù),在清空臨時(shí)集合)
* 此時(shí)并不能在數(shù)據(jù)庫(kù)中看到數(shù)據(jù),但是如果數(shù)據(jù)庫(kù)的隔離級(jí)別設(shè)置為未提交讀,
* 那么我們可以看到flush過(guò)的數(shù)據(jù),并且PersistenceContext中existsInDatabase狀態(tài)為true
*/
session.flush();
/**
* 提交事物
* 默認(rèn)情況下,commit操作會(huì)執(zhí)行flush清理緩存,
* 所以不用顯示的調(diào)用flush
* commit后數(shù)據(jù)是無(wú)法回滾的
*/
transaction.commit();
} catch (Exception e) {
e.printStackTrace();
transaction.rollback();
} finally{
HibernateUtils.closeSession(session);
}
} 我們可以通過(guò)斷點(diǎn)調(diào)試程序:
1.由于User的主鍵生成側(cè)率為UUID,調(diào)用save()方法之后,只能將User對(duì)象納入Session管理,不會(huì)發(fā)出insert語(yǔ)句,但是ID已經(jīng)生成了(注:save之后又兩個(gè)地方很重要,首先是session->actionQueue->insertions->elementData數(shù)組中有某個(gè)元素存儲(chǔ)了我們的對(duì)象,這是一個(gè)臨時(shí)集合對(duì)象,另外還有一個(gè)就是PersistenceContext->EntityEntries->map->table->某個(gè)數(shù)組元素->value存儲(chǔ)了該對(duì)象,value下面還有一個(gè)屬性那就是existsInDatabase代表數(shù)據(jù)庫(kù)中是否有對(duì)應(yīng)的數(shù)據(jù))。如圖:


2.調(diào)用完flush()方法之后,會(huì)清空session中的actionQueue的臨時(shí)存儲(chǔ)的值,然后將PersistenceContext中的existsInDatabase的值設(shè)為true,表示此時(shí),數(shù)據(jù)庫(kù)中有對(duì)應(yīng)的數(shù)據(jù),但是此時(shí)打開(kāi)數(shù)據(jù)庫(kù)打開(kāi)表是看不到數(shù)據(jù)的,因?yàn)槲覀僊ySQL數(shù)據(jù)庫(kù)默認(rèn)的隔離級(jí)別為提交讀,即,必須提交才能讀取數(shù)據(jù),調(diào)用commit()方法之后,數(shù)據(jù)庫(kù)中有數(shù)據(jù)。
案例二
native方式生成主鍵的例子:
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:
<?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表示實(shí)體類的包名 --> <hibernate-mapping package="com.lixue.bean"> <!-- class結(jié)點(diǎn)的name表示實(shí)體的類名(賦值映射文件的時(shí)候要記得修改類名,否則會(huì)出現(xiàn)bug),table表示實(shí)體映射到數(shù)據(jù)庫(kù)中table的名稱 --> <class name="User1" table="t_user1"> <id name="uid"> <!-- 自增長(zhǎng) --> <generator class="native"/> </id> <property name="uname"/> <property name="birthday"/> </class> </hibernate-mapping>
測(cè)試方法:
/**
* 測(cè)試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("小李");
user.setBirthday(new Date());
/**
* 因?yàn)閁ser1的主鍵生成策略是native,所以調(diào)用Session.save()后,將執(zhí)行insert語(yǔ)句,并且會(huì)清空臨時(shí)集合對(duì)象
* 返回由數(shù)據(jù)庫(kù)生成的ID,納入Session的管理,修改了Session中existsInDatabase狀態(tài)為true,
* 如果數(shù)據(jù)庫(kù)的隔離級(jí)別設(shè)置為未提交讀,那么我們可以看到save過(guò)的數(shù)據(jù)
*/
session.save(user);
transaction.commit();
} catch (Exception e) {
e.printStackTrace();
transaction.rollback();
} finally{
HibernateUtils.closeSession(session);
}
} 通過(guò)斷點(diǎn)調(diào)試程序:
1.由于主鍵的生成策略為native,所以調(diào)用save()方法之后,將執(zhí)行insert語(yǔ)句,并且會(huì)清空臨時(shí)集合對(duì)象中的數(shù)據(jù),返回由數(shù)據(jù)庫(kù)生成的ID。
2.將對(duì)象納入session管理,修改了PersistenceContext中的existsInDatabase屬性為true(表示數(shù)據(jù)庫(kù)中有對(duì)應(yīng)的數(shù)據(jù),但是看不到,因?yàn)楦綦x界別的原因)
案例三
我們?cè)賮?lái)測(cè)試一下Hibernate的另一個(gè)方法,那就是evict(),表示將對(duì)象從session逐出。
針對(duì)UUID主鍵生成策略的程序,在來(lái)一個(gè)測(cè)試方法:
/**
* 測(cè)試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());
/**
* 因?yàn)閁ser的主鍵生成策略為uuid,所以調(diào)用完save之后,只是將User納入到Session管理
* 不會(huì)發(fā)出insert語(yǔ)句,但是ID已經(jīng)生成。Session中的existsInDatebase狀態(tài)為false
*/
session.save(user);
/*將user對(duì)象從session中逐出,即從PersistenceContext的EntityEntries屬性中逐出*/
session.evict(user);
/**
* 無(wú)法成功提交,因?yàn)镠ibernate在清理緩存時(shí),在session的insertions臨時(shí)集合中取出user對(duì)象進(jìn)行insert
* 操作后,需要更新entityEntries屬性中的existsInDatabase為true,而我們調(diào)用了evict方法
* 將user從session的entityEntries中逐出了,所以找不到existsInDatabase屬性,無(wú)法更新,拋出異常
*/
transaction.commit();
} catch (Exception e) {
e.printStackTrace();
transaction.rollback();
} finally{
HibernateUtils.closeSession(session);
}
} 通過(guò)斷點(diǎn)調(diào)試:
1.由于使用的是UUID的主鍵生成策略,所以調(diào)用save()方法之后,不會(huì)發(fā)送insert語(yǔ)句,只是將對(duì)象納入了session管理,ID已經(jīng)生成,數(shù)據(jù)庫(kù)中沒(méi)有與之對(duì)應(yīng)的數(shù)據(jù)(即existsInDatabase屬性值為false)。
2.調(diào)用evict()之后,將User對(duì)象從session中逐出,即從PersistenceContext的EntityEntries屬性中逐出。
3.當(dāng)我再調(diào)用commit()方法時(shí),我們會(huì)發(fā)現(xiàn),我們的數(shù)據(jù)保存不了,因?yàn)橐婚_(kāi)始我們的existsInDatabase屬性為false,即數(shù)據(jù)庫(kù)中不存在對(duì)應(yīng)數(shù)據(jù),緊接著我們又調(diào)用了evict()將PersistenceContext中的對(duì)象屬性(existsInDatabase屬性也包括在內(nèi))全刪除了,但是actionQueue中的臨時(shí)存儲(chǔ)數(shù)據(jù)還沒(méi)被刪除。我們只調(diào)用commit()方法時(shí)會(huì)先隱式的調(diào)用flush()方法,這個(gè)方法的作用之前也講過(guò),它會(huì)將actionQueue中的臨時(shí)對(duì)象進(jìn)行insert操作,然后將PersistenceContext中的existsInDatabase屬性值設(shè)為true,但很遺憾,PersistenceContext中并沒(méi)有existsInDatabase屬性,所以會(huì)出現(xiàn)錯(cuò)誤,導(dǎo)致無(wú)法保存。
為此,我們改進(jìn)上述程序:
/**
* 測(cè)試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());
/**
* 因?yàn)閁ser的主鍵生成策略為uuid,所以調(diào)用完save之后,只是將User納入到Session管理
* 不會(huì)發(fā)出insert語(yǔ)句,但是ID已經(jīng)生成。PersistenceContext中的existsInDatebase狀態(tài)為false
*/
session.save(user);
/**
* flush后Hibernate會(huì)清理緩存,會(huì)將user對(duì)象保存到數(shù)據(jù)庫(kù)中,將session中的insertions中的user對(duì)象
* 清除,并且設(shè)置PersistenceContext中existsInDatabase的狀態(tài)為true
*/
session.flush();
/*將user對(duì)象從session中逐出,即從PersistenceContext的EntityEntries屬性中逐出*/
session.evict(user);
/**
* 可以成功提交,因?yàn)镠ibernate在清理緩存時(shí),在session的insertions集合中無(wú)法
* 找到user對(duì)象(調(diào)用flush時(shí)清空了),所以就不會(huì)發(fā)出insert語(yǔ)句,也不會(huì)更新session中的existsInDatabase的狀態(tài)
*/
transaction.commit();
} catch (Exception e) {
e.printStackTrace();
transaction.rollback();
} finally{
HibernateUtils.closeSession(session);
}
} 注:修改后的程序我們?cè)趕ave之后顯示的調(diào)用了flush()方法,再調(diào)用evict()方法。
通過(guò)斷點(diǎn)調(diào)試:
1.因?yàn)檫€是UUID的生成策略,所以在調(diào)用save之后,不會(huì)發(fā)出insert語(yǔ)句,只是將對(duì)象納入session管理,PersistenceContext中的existsInDatabase屬性為false。
2.調(diào)用完save()之后,我們又調(diào)用了flush()方法,這個(gè)方法的作用是清理緩存,即發(fā)出insert語(yǔ)句,將session中的insertions中的臨時(shí)對(duì)象插入到數(shù)據(jù)庫(kù),然后清空該臨時(shí)集合,并且將PersistenceContext中的existsInDatabase屬性設(shè)置為true。
3.調(diào)用完flush()之后又調(diào)用evict()方法,它的作用是將user對(duì)象從session中清除,即清除PersistenceContext的EntityEntries屬性。
4.調(diào)用完evict()方法之后又調(diào)用commit()方法,它的會(huì)隱式的先調(diào)用flush()方法,而flush的作用是清除緩存,即將session->insertions臨時(shí)集合中的對(duì)象insert到數(shù)據(jù)庫(kù)中,但是我們之前就調(diào)用了flush()方法(注:調(diào)用完這個(gè)方法之后會(huì)清空臨時(shí)集合),所以臨時(shí)集合根本就沒(méi)有對(duì)象,所以不會(huì)發(fā)出insert語(yǔ)句。也不會(huì)去更新PersistenceContext中的existsInDatabase狀態(tài)。可以成功提交。
案例四
我們?cè)賮?lái)考慮下native方式的主鍵生成策略中使用evict()方法:
/**
* 測(cè)試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());
/**
* 因?yàn)閁ser1的主鍵生成策略是native,所以調(diào)用Session.save()后,將執(zhí)行insert語(yǔ)句,
* 返回由數(shù)據(jù)庫(kù)生成的ID,納入Session的管理,修改了Session中existsInDatabase狀態(tài)為true,并且清空了臨時(shí)集合
* 如果數(shù)據(jù)庫(kù)的隔離級(jí)別設(shè)置為未提交讀,那么我們可以看到save過(guò)的數(shù)據(jù)
*/
session.save(user);
/*將user對(duì)象從session中逐出,即從PersistenceContext的EntityEntries屬性中逐出*/
session.evict(user);
/**
* 可以成功提交,因?yàn)镠ibernate在清理緩存的時(shí)候在session的insertions集合中
* 無(wú)法找到user對(duì)象,所以就不會(huì)發(fā)出insert語(yǔ)句,也不會(huì)更新session中的existtsInDatabase的狀態(tài)
*/
transaction.commit();
} catch (Exception e) {
e.printStackTrace();
transaction.rollback();
} finally{
HibernateUtils.closeSession(session);
}
} 通過(guò)調(diào)試:
1.由于主鍵生成策略為native,所以調(diào)用完save方法之后,馬上就會(huì)發(fā)出insert語(yǔ)句,返回由數(shù)據(jù)庫(kù)生成的ID,將對(duì)象納入session管理,修改PersistenceContext中的existsInDatabase屬性為true即數(shù)據(jù)庫(kù)中有與之對(duì)應(yīng)的數(shù)據(jù),并且會(huì)清空臨時(shí)集合中的對(duì)象。但是由于MySQL隔離級(jí)別的原因我們?cè)跊](méi)有提交之前是看不到數(shù)據(jù)的。
2.調(diào)用完save之后又調(diào)用evict()方法,將對(duì)象從session中逐出,即從PersistenceContext中的EntityEntries中逐出。
3.調(diào)用完evict()方法之后又調(diào)用commit()方法,此時(shí)是可以成功保存提交的,因?yàn)檎{(diào)用commit()之前會(huì)隱式調(diào)用flush()方法,即清理緩存,去臨時(shí)集合中找對(duì)象insert到數(shù)據(jù)庫(kù),但是會(huì)發(fā)現(xiàn)臨時(shí)集合中已經(jīng)沒(méi)有數(shù)據(jù)了,所以不會(huì)發(fā)出insert語(yǔ)句,也就不會(huì)去更新PersistenceContext中的existsInDatabase屬性。
通過(guò)上述幾個(gè)案例,我們可以看出,有時(shí)候我們需要顯示的調(diào)用flush()方法,去清理緩存。另外,從上面我們也發(fā)現(xiàn)了一個(gè)問(wèn)題,那就是當(dāng)我們save()了數(shù)據(jù),沒(méi)提交之前是看不到數(shù)據(jù)的,即數(shù)據(jù)庫(kù)的隔離界別限制了,現(xiàn)在我們來(lái)說(shuō)說(shuō)MySQL的隔離級(jí)別:
1.查看MySQL數(shù)據(jù)庫(kù)當(dāng)前的隔離級(jí)別:
select @@tx_isolation;

注:從圖中,我們可以看出,MySQL數(shù)據(jù)庫(kù)默認(rèn)的隔離級(jí)別為可重復(fù)讀,也就是說(shuō)不會(huì)出現(xiàn)不可重復(fù)讀,即必須提交之后才能讀。
2.修改MySQL當(dāng)前的隔離級(jí)別(假設(shè)修改為未提交讀,即沒(méi)有提交就可以讀):
set transaction isolation level read uncommited;

總結(jié)
以上就是本文關(guān)于Hibernate的Session_flush與隔離級(jí)別代碼詳解的全部?jī)?nèi)容,希望對(duì)大家有所幫助。感興趣的朋友可以繼續(xù)參閱本站其他相關(guān)專題,如有不足之處,歡迎留言指出。感謝朋友們對(duì)本站的支持!
相關(guān)文章
JavaFX Application應(yīng)用實(shí)例
下面小編就為大家?guī)?lái)一篇JavaFX Application應(yīng)用實(shí)例。小編覺(jué)得挺不錯(cuò)的,現(xiàn)在就分享給大家,也給大家做個(gè)參考。一起跟隨小編過(guò)來(lái)看看吧2016-10-10
Java語(yǔ)言實(shí)現(xiàn)Blowfish加密算法完整代碼分享
這篇文章主要介紹了Java語(yǔ)言實(shí)現(xiàn)Blowfish加密算法完整代碼分享,簡(jiǎn)單介紹了blowfish加密算法,具有一定借鑒價(jià)值,需要的朋友可以參考下。2017-11-11
Java完全二叉樹(shù)的創(chuàng)建與四種遍歷方法分析
這篇文章主要介紹了Java完全二叉樹(shù)的創(chuàng)建與四種遍歷方法,結(jié)合實(shí)例形式分析了完全二叉樹(shù)的概念、定義及遍歷操作相關(guān)實(shí)現(xiàn)技巧,并對(duì)比分析了滿二叉樹(shù)與完全二叉樹(shù)的區(qū)別,需要的朋友可以參考下2017-11-11
springBoo3.0集成knife4j4.1.0的詳細(xì)教程(swagger3)
這篇文章主要介紹了springBoo3.0集成knife4j4.1.0的詳細(xì)教程(swagger3),本文給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2023-07-07
Spring整合redis(jedis)實(shí)現(xiàn)Session共享的過(guò)程
這篇文章主要介紹了Spring整合redis(jedis)實(shí)現(xiàn)Session共享,需要的朋友可以參考下2018-06-06
解決shiro 定時(shí)監(jiān)聽(tīng)器不生效的問(wèn)題 onExpiration不調(diào)用問(wèn)題
這篇文章主要介紹了解決shiro 定時(shí)監(jiān)聽(tīng)器不生效的問(wèn)題 onExpiration不調(diào)用問(wèn)題。具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2021-07-07

