您好,登錄后才能下訂單哦!
本文研究的主要是Hibernate的Session_flush與隔離級別,具體介紹和實例如下。
我們先來看一些概念:
1.臟讀:臟讀又稱為無效數據的讀出,是指在數據庫訪問中,事物T1將某一值修改,然后事物T2讀取該值,此后T1因為某種原因撤銷對該值的修改,這就導致了T2所讀取的數據是無效的。臟讀就是指當一個事物正在訪問數據,并且對數據進行了修改,而這種修改還沒有提交到數據庫中,這時,另外一個事物也訪問這個數據,然后使用了這個數據。因為這個數據還是沒有提交的數據,那么另外一個事物讀到的這個數據就是臟數據,依據臟數據所做的操作是不正確的。
2.不可重復讀:比如我在讀一個帖子,我查出來的數據是張三、李四,然后我一刷新發現最開始的張三變成了張八,這就是所謂的不可重復讀,因為我讀出的數據沒重復了嘛。
3.幻讀:我在查數據的時候,開始查出來的記錄為3條,我一刷新,發現記錄變為了8條,這就是幻讀。
4.提交讀:提交了之后才可以讀取,Oracle默認就是這個,這種方式是不存在臟讀的。
5.可重復度:很顯然是和不可重復讀相反的,它可以避免不可重復讀,但是這個不能避免幻讀。
6.序列化:這種方式非常嚴格,通俗的說就是,當我在做一件事情的時候,其他任何人都不能做,非常安全,但是效率極低。
下面我們通過實際的例子來體會Hibernate清除緩存的應用。
Hibernate映射數據庫和主鍵的生成策略有關。
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表示實體類的包名 --> <hibernate-mapping package="com.lixue.bean"> <!-- class結點的name表示實體的類名,table表示實體映射到數據庫中table的名稱 --> <class name="User" table="t_user"> <id name="uid"> <!-- 通過UUID的方式生成 --> <generator class="uuid"/> </id> <property name="uname"/> <property name="birthday"/> </class> </hibernate-mapping>
測試方法:
/** * 測試uuid主鍵生成策略 */ public void testSave1(){ /*定義的Session和事物*/ Session session = null; Transaction transaction = null; try { /*獲取session和事物*/ session = HibernateUtils.getSession(); transaction = session.beginTransaction(); /*創建用戶*/ User user = new User(); user.setUname("習近平"); user.setBirthday(new Date()); /** * 因為User的主鍵生成策略為uuid,所以調用完save之后,只是將User納入到Session管理 * 不會發出insert語句,但是ID已經生成,PersistenceContext中的existsInDatebase狀態為false */ session.save(user); /** * 調用flush,Hibernate會清理緩存(將session->insertions中臨時集合中的對象插入數據庫,在清空臨時集合) * 此時并不能在數據庫中看到數據,但是如果數據庫的隔離級別設置為未提交讀, * 那么我們可以看到flush過的數據,并且PersistenceContext中existsInDatabase狀態為true */ session.flush(); /** * 提交事物 * 默認情況下,commit操作會執行flush清理緩存, * 所以不用顯示的調用flush * commit后數據是無法回滾的 */ transaction.commit(); } catch (Exception e) { e.printStackTrace(); transaction.rollback(); } finally{ HibernateUtils.closeSession(session); } }
我們可以通過斷點調試程序:
1.由于User的主鍵生成側率為UUID,調用save()方法之后,只能將User對象納入Session管理,不會發出insert語句,但是ID已經生成了(注:save之后又兩個地方很重要,首先是session->actionQueue->insertions->elementData數組中有某個元素存儲了我們的對象,這是一個臨時集合對象,另外還有一個就是PersistenceContext->EntityEntries->map->table->某個數組元素->value存儲了該對象,value下面還有一個屬性那就是existsInDatabase代表數據庫中是否有對應的數據)。如圖:
2.調用完flush()方法之后,會清空session中的actionQueue的臨時存儲的值,然后將PersistenceContext中的existsInDatabase的值設為true,表示此時,數據庫中有對應的數據,但是此時打開數據庫打開表是看不到數據的,因為我們MySQL數據庫默認的隔離級別為提交讀,即,必須提交才能讀取數據,調用commit()方法之后,數據庫中有數據。
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表示實體類的包名 --> <hibernate-mapping package="com.lixue.bean"> <!-- class結點的name表示實體的類名(賦值映射文件的時候要記得修改類名,否則會出現bug),table表示實體映射到數據庫中table的名稱 --> <class name="User1" table="t_user1"> <id name="uid"> <!-- 自增長 --> <generator class="native"/> </id> <property name="uname"/> <property name="birthday"/> </class> </hibernate-mapping>
測試方法:
/** * 測試native主鍵生成策略 */ public void testSave2(){ /*定義的Session和事物*/ Session session = null; Transaction transaction = null; try { /*獲取session和事物*/ session = HibernateUtils.getSession(); transaction = session.beginTransaction(); /*創建用戶*/ User1 user = new User1(); user.setUname("李克強"); user.setBirthday(new Date()); /** * 因為User1的主鍵生成策略是native,所以調用Session.save()后,將執行insert語句,并且會清空臨時集合對象 * 返回由數據庫生成的ID,納入Session的管理,修改了Session中existsInDatabase狀態為true, * 如果數據庫的隔離級別設置為未提交讀,那么我們可以看到save過的數據 */ session.save(user); transaction.commit(); } catch (Exception e) { e.printStackTrace(); transaction.rollback(); } finally{ HibernateUtils.closeSession(session); } }
通過斷點調試程序:
1.由于主鍵的生成策略為native,所以調用save()方法之后,將執行insert語句,并且會清空臨時集合對象中的數據,返回由數據庫生成的ID。
2.將對象納入session管理,修改了PersistenceContext中的existsInDatabase屬性為true(表示數據庫中有對應的數據,但是看不到,因為隔離界別的原因)
我們再來測試一下Hibernate的另一個方法,那就是evict(),表示將對象從session逐出。
針對UUID主鍵生成策略的程序,在來一個測試方法:
/** * 測試uuid主鍵生成策略 */ public void testSave3(){ /*定義的Session和事物*/ Session session = null; Transaction transaction = null; try { /*獲取session和事物*/ session = HibernateUtils.getSession(); transaction = session.beginTransaction(); /*創建用戶*/ User user = new User(); user.setUname("胡錦濤"); user.setBirthday(new Date()); /** * 因為User的主鍵生成策略為uuid,所以調用完save之后,只是將User納入到Session管理 * 不會發出insert語句,但是ID已經生成。Session中的existsInDatebase狀態為false */ session.save(user); /*將user對象從session中逐出,即從PersistenceContext的EntityEntries屬性中逐出*/ session.evict(user); /** * 無法成功提交,因為Hibernate在清理緩存時,在session的insertions臨時集合中取出user對象進行insert * 操作后,需要更新entityEntries屬性中的existsInDatabase為true,而我們調用了evict方法 * 將user從session的entityEntries中逐出了,所以找不到existsInDatabase屬性,無法更新,拋出異常 */ transaction.commit(); } catch (Exception e) { e.printStackTrace(); transaction.rollback(); } finally{ HibernateUtils.closeSession(session); } }
通過斷點調試:
1.由于使用的是UUID的主鍵生成策略,所以調用save()方法之后,不會發送insert語句,只是將對象納入了session管理,ID已經生成,數據庫中沒有與之對應的數據(即existsInDatabase屬性值為false)。
2.調用evict()之后,將User對象從session中逐出,即從PersistenceContext的EntityEntries屬性中逐出。
3.當我再調用commit()方法時,我們會發現,我們的數據保存不了,因為一開始我們的existsInDatabase屬性為false,即數據庫中不存在對應數據,緊接著我們又調用了evict()將PersistenceContext中的對象屬性(existsInDatabase屬性也包括在內)全刪除了,但是actionQueue中的臨時存儲數據還沒被刪除。我們只調用commit()方法時會先隱式的調用flush()方法,這個方法的作用之前也講過,它會將actionQueue中的臨時對象進行insert操作,然后將PersistenceContext中的existsInDatabase屬性值設為true,但很遺憾,PersistenceContext中并沒有existsInDatabase屬性,所以會出現錯誤,導致無法保存。
為此,我們改進上述程序:
/** * 測試uuid主鍵生成策略 */ public void testSave4(){ /*定義的Session和事物*/ Session session = null; Transaction transaction = null; try { /*獲取session和事物*/ session = HibernateUtils.getSession(); transaction = session.beginTransaction(); /*創建用戶*/ User user = new User(); user.setUname("胡錦濤"); user.setBirthday(new Date()); /** * 因為User的主鍵生成策略為uuid,所以調用完save之后,只是將User納入到Session管理 * 不會發出insert語句,但是ID已經生成。PersistenceContext中的existsInDatebase狀態為false */ session.save(user); /** * flush后Hibernate會清理緩存,會將user對象保存到數據庫中,將session中的insertions中的user對象 * 清除,并且設置PersistenceContext中existsInDatabase的狀態為true */ session.flush(); /*將user對象從session中逐出,即從PersistenceContext的EntityEntries屬性中逐出*/ session.evict(user); /** * 可以成功提交,因為Hibernate在清理緩存時,在session的insertions集合中無法 * 找到user對象(調用flush時清空了),所以就不會發出insert語句,也不會更新session中的existsInDatabase的狀態 */ transaction.commit(); } catch (Exception e) { e.printStackTrace(); transaction.rollback(); } finally{ HibernateUtils.closeSession(session); } }
注:修改后的程序我們在save之后顯示的調用了flush()方法,再調用evict()方法。
通過斷點調試:
1.因為還是UUID的生成策略,所以在調用save之后,不會發出insert語句,只是將對象納入session管理,PersistenceContext中的existsInDatabase屬性為false。
2.調用完save()之后,我們又調用了flush()方法,這個方法的作用是清理緩存,即發出insert語句,將session中的insertions中的臨時對象插入到數據庫,然后清空該臨時集合,并且將PersistenceContext中的existsInDatabase屬性設置為true。
3.調用完flush()之后又調用evict()方法,它的作用是將user對象從session中清除,即清除PersistenceContext的EntityEntries屬性。
4.調用完evict()方法之后又調用commit()方法,它的會隱式的先調用flush()方法,而flush的作用是清除緩存,即將session->insertions臨時集合中的對象insert到數據庫中,但是我們之前就調用了flush()方法(注:調用完這個方法之后會清空臨時集合),所以臨時集合根本就沒有對象,所以不會發出insert語句。也不會去更新PersistenceContext中的existsInDatabase狀態。可以成功提交。
我們再來考慮下native方式的主鍵生成策略中使用evict()方法:
/** * 測試native主鍵生成策略 */ public void testSave5(){ /*定義的Session和事物*/ Session session = null; Transaction transaction = null; try { /*獲取session和事物*/ session = HibernateUtils.getSession(); transaction = session.beginTransaction(); /*創建用戶*/ User1 user = new User1(); user.setUname("馬英九"); user.setBirthday(new Date()); /** * 因為User1的主鍵生成策略是native,所以調用Session.save()后,將執行insert語句, * 返回由數據庫生成的ID,納入Session的管理,修改了Session中existsInDatabase狀態為true,并且清空了臨時集合 * 如果數據庫的隔離級別設置為未提交讀,那么我們可以看到save過的數據 */ session.save(user); /*將user對象從session中逐出,即從PersistenceContext的EntityEntries屬性中逐出*/ session.evict(user); /** * 可以成功提交,因為Hibernate在清理緩存的時候在session的insertions集合中 * 無法找到user對象,所以就不會發出insert語句,也不會更新session中的existtsInDatabase的狀態 */ transaction.commit(); } catch (Exception e) { e.printStackTrace(); transaction.rollback(); } finally{ HibernateUtils.closeSession(session); } }
通過調試:
1.由于主鍵生成策略為native,所以調用完save方法之后,馬上就會發出insert語句,返回由數據庫生成的ID,將對象納入session管理,修改PersistenceContext中的existsInDatabase屬性為true即數據庫中有與之對應的數據,并且會清空臨時集合中的對象。但是由于MySQL隔離級別的原因我們在沒有提交之前是看不到數據的。
2.調用完save之后又調用evict()方法,將對象從session中逐出,即從PersistenceContext中的EntityEntries中逐出。
3.調用完evict()方法之后又調用commit()方法,此時是可以成功保存提交的,因為調用commit()之前會隱式調用flush()方法,即清理緩存,去臨時集合中找對象insert到數據庫,但是會發現臨時集合中已經沒有數據了,所以不會發出insert語句,也就不會去更新PersistenceContext中的existsInDatabase屬性。
通過上述幾個案例,我們可以看出,有時候我們需要顯示的調用flush()方法,去清理緩存。另外,從上面我們也發現了一個問題,那就是當我們save()了數據,沒提交之前是看不到數據的,即數據庫的隔離界別限制了,現在我們來說說MySQL的隔離級別:
1.查看MySQL數據庫當前的隔離級別:
select @@tx_isolation;
注:從圖中,我們可以看出,MySQL數據庫默認的隔離級別為可重復讀,也就是說不會出現不可重復讀,即必須提交之后才能讀。
2.修改MySQL當前的隔離級別(假設修改為未提交讀,即沒有提交就可以讀):
set transaction isolation level read uncommited;
以上就是本文關于Hibernate的Session_flush與隔離級別代碼詳解的全部內容,希望對大家有所幫助。感興趣的朋友可以繼續參閱本站其他相關專題,如有不足之處,歡迎留言指出。感謝朋友們對本站的支持!
免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。