本文包括:
1褐筛、Hibernate的持久化類
2互广、Hibernate 持久化對象的三個狀態(tài)(難點)
3、Hibernate 的一級緩存
4祭饭、Hibernate 中的事務(wù)與并發(fā)
5、Hibernate 的查詢方式(HQL:Hibernate Query Language)
1叙量、Hibernate的持久化類
-
什么是持久化類倡蝙?
持久化類:就是一個 Java 類(咱們編寫的 JavaBean),這個 Java 類與表建立了映射關(guān)系就可以成為是持久化類绞佩。
簡單記:持久化類 = JavaBean + xxx.hbm.xml
-
持久化類的編寫規(guī)則:
提供一個無參數(shù) public訪問控制符的構(gòu)造器 —— 底層需要進行反射寺鸥。
提供一個標識屬性,映射數(shù)據(jù)表主鍵字段 —— 唯一標識 OID征炼,數(shù)據(jù)庫中通過主鍵析既,Java 對象通過地址確定對象,持久化類通過唯一標識 OID 確定記錄谆奥。
SQL 語句:
cust_id
bigint(32) NOT NULL AUTO_INCREMENT COMMENT '客戶編號(主鍵)'JavaBean 代碼:private Long cust_id;
結(jié)論:JavaBean 中的 cust_id 即為唯一標識 OID眼坏。
所有屬性提供 public 訪問控制符的 set 和 get 方法。
標識屬性應(yīng)盡量使用基本數(shù)據(jù)類型的包裝類型(默認值為 null)酸些。
-
自然主鍵和代理主鍵的區(qū)別:
自然主鍵:該主鍵是對象本身的一個屬性宰译。例如:創(chuàng)建一個人員表,每個人都有一個身份證號(唯一的)魄懂。使用身份證號作為表的主鍵沿侈,稱為自然主鍵。(開發(fā)中不會使用這種方式)
代理主鍵:該主鍵不是對象本身的一個屬性市栗。例如:創(chuàng)建一個人員表缀拭,為每個人員單獨創(chuàng)建一個字段咳短,用這個字段作為主鍵,稱為代理主鍵蛛淋。(開發(fā)中推薦使用這種方式)
簡單記:創(chuàng)建表時咙好,新增一個毫無關(guān)系的字段,用該字段作為主鍵褐荷。
-
主鍵的生成策略:
-
increment:適用于 short,int,long 作為主鍵勾效,沒有使用數(shù)據(jù)庫的自動增長機制。
-
Hibernate 中提供的一種增長機制叛甫,具體步驟如下:
先進行查詢:select max(id) from user;
再進行插入:獲得最大值+1作為新的記錄的主鍵层宫。
問題:不能在集群環(huán)境下或者有并發(fā)訪問的情況下使用。
分析:在查詢時其监,有可能有兩個用戶幾乎同時得到相同的 id萌腿,再插入時就有可能主鍵沖突!
-
-
identity:適用于 short,int,long 作為主鍵棠赛。但是必須使用在有自動增長機制的數(shù)據(jù)庫中哮奇,并且該數(shù)據(jù)庫采用的是數(shù)據(jù)庫底層的自動增長機制。
- 底層使用的是數(shù)據(jù)庫的自動增長(auto_increment)睛约,像 Oracle 數(shù)據(jù)庫沒有自動增長機制鼎俘,而MySql、DB2 等數(shù)據(jù)庫有自動增長的機制辩涝。
-
sequence:適用于 short,int,long 作為主鍵贸伐,底層使用的是序列的增長方式。
- Oracle 數(shù)據(jù)庫底層沒有自動增長,若想自動增長需要使用序列怔揩。
-
uuid:適用于 char,varchar 類型的作為主鍵捉邢。
- 使用隨機的字符串作為主鍵.
-
native:本地策略。根據(jù)底層的數(shù)據(jù)庫不同,自動選擇適用于該種數(shù)據(jù)庫的生成策略(short,int,long)商膊。
如果底層使用的 MySQL 數(shù)據(jù)庫:相當于 identity.
如果底層使用 Oracle 數(shù)據(jù)庫:相當于 sequence.
assigned:主鍵的生成不用 Hibernate 管理了伏伐,必須手動設(shè)置主鍵。
重點掌握:uuid(字符串)晕拆、native(數(shù)字)
-
2藐翎、Hibernate 持久化對象的三個狀態(tài)(難點)
-
持久化對象的狀態(tài)
Hibernate 的持久化類(前文已寫)
-
Hibernate 的持久化類的狀態(tài)
- Hibernate為了管理持久化類:將持久化類分成了三個狀態(tài)
-
瞬時態(tài):Transient Object
- 沒有持久化標識 OID, 沒有被納入到 Session 對象的管理(即沒有關(guān)系)
-
持久態(tài):Persistent Object
- 有持久化標識 OID,已經(jīng)被納入到 Session 對象的管理.
-
托管態(tài):Detached Object
- 有持久化標識 OID,沒有被納入到 Session 對象的管理.
-
- Hibernate為了管理持久化類:將持久化類分成了三個狀態(tài)
-
Hibernate 持久化對象的狀態(tài)的轉(zhuǎn)換
-
瞬時態(tài) -- 沒有持久化標識 OID, 沒有被納入到 Session 對象的管理
-
獲得瞬時態(tài)的對象
User user = new User();
創(chuàng)建了持久化類的對象,該對象還沒有 OID实幕,也和 Session 對象無關(guān)吝镣,所以是瞬時態(tài)。
-
瞬時態(tài)對象轉(zhuǎn)換持久態(tài)
session.save(user); 或者 session.saveOrUpdate(user);
user對象進入緩存昆庇,且自動生成了 OID末贾,故為持久態(tài)。
-
瞬時態(tài)對象轉(zhuǎn)換成托管態(tài)
user.setId(1);
手動設(shè)置了 OID整吆,但沒有和 Session 對象發(fā)生關(guān)系拱撵,故為托管態(tài)辉川。
-
-
持久態(tài) -- 有持久化標識OID,已經(jīng)被納入到Session對象的管理
-
獲得持久態(tài)的對象
get()/load();
-
持久態(tài)轉(zhuǎn)換成瞬時態(tài)對象
delete(); --- 比較有爭議的,進入特殊的狀態(tài)(刪除態(tài):Hibernate中不建議使用的)
-
持久態(tài)對象轉(zhuǎn)成脫管態(tài)對象
session的close()/evict()/clear();
Session 對象被銷毀拴测,所以持久化類的對象沒有被 session 管理员串,所以為托管態(tài)。
-
-
脫管態(tài) -- 有持久化標識OID,沒有被納入到Session對象的管理
-
獲得托管態(tài)對象:不建議直接獲得脫管態(tài)的對象.
User user = new User(); user.setId(1);
-
脫管態(tài)對象轉(zhuǎn)換成持久態(tài)對象
update();/saveOrUpdate()/lock();
-
脫管態(tài)對象轉(zhuǎn)換成瞬時態(tài)對象
user.setId(null);
-
-
注意:持久態(tài)對象有自動更新數(shù)據(jù)庫的能力!!!
-
測試代碼:
/** * 持久態(tài)的對象有自動更新數(shù)據(jù)庫的能力 * session的一級緩存V缈浮! */ @Test public void run1(){ Session session = HibernateUtils.getSession(); Transaction tr = session.beginTransaction(); // 獲取到持久態(tài)的對象 User user = session.get(User.class,1); // user是持久態(tài)欲诺,有自動更新數(shù)據(jù)庫的能力 System.out.println(user.getName()); // 重新設(shè)置新的名稱 user.setName("隔離老王"); // 正常編寫代碼 // session.update(user); tr.commit(); session.close(); }
-
執(zhí)行后抄谐,控制臺輸出:
Hibernate: select user0_.id as id1_1_0_, user0_.version as version2_1_0_, user0_.name as name3_1_0_, user0_.age as age4_1_0_ from t_user user0_ where user0_.id=? 小風 Hibernate: update t_user set version=?, name=?, age=? where id=? and version=?
從中可以發(fā)現(xiàn),在測試代碼中我們把
session.update(user);
注釋掉了扰法,但是在控制臺中發(fā)現(xiàn)仍有update
語句蛹含,這就是持久態(tài)有自動更新數(shù)據(jù)庫的能力,具體原因是 Session 對象的一級緩存(見下節(jié))塞颁。
-
-
-
三個狀態(tài)之間的轉(zhuǎn)換圖解:
3浦箱、Hibernate 的一級緩存
-
Session 對象的一級緩存
-
什么是緩存?
- 其實就是一塊內(nèi)存空間祠锣,將數(shù)據(jù)源(數(shù)據(jù)庫或者文件)中的數(shù)據(jù)存放到緩存中酷窥。再次獲取的時候,直接從緩存中獲取伴网,可以提升程序的性能蓬推!
-
Hibernate 框架提供了兩種緩存
- 一級緩存 -- 自帶的不可卸載的。一級緩存的生命周期與 session 一致澡腾,一級緩存稱為 session 級別的緩存.
- 二級緩存 -- 默認沒有開啟沸伏,需要手動配置才可以使用的。二級緩存可以在多個 session 中共享數(shù)據(jù),二級緩存稱為是 sessionFactory 級別的緩存.
-
Session 對象的緩存概述
- Session 接口中动分,有一系列的 java 的集合,這些 java 集合構(gòu)成了 Session 級別的緩存(一級緩存)毅糟。將對象存入到一級緩存中,session 沒有結(jié)束生命周期,那么對象在 session 中存放著。
- 內(nèi)存中包含 Session 實例 --> Session 的緩存(一些集合) --> 集合中包含的是緩存對象澜公!
-
證明一級緩存的存在姆另,編寫查詢的代碼即可證明
- 在同一個 Session 對象中兩次查詢,可以證明使用了緩存玛瘸。
-
測試代碼:
@Test public void run3(){ Session session = HibernateUtils.getSession(); Transaction tr = session.beginTransaction(); // 最簡單的證明蜕青,查詢兩次 User user1 = session.get(User.class, 1); System.out.println(user1.getName()); User user2 = session.get(User.class, 1); System.out.println(user2.getName()); tr.commit(); session.close(); }
-
控制臺只輸出一次如下信息:
Hibernate: insert into t_user (version, name, age) values (?, ?, ?)
進一步分析,若采用斷點的方式 debug糊渊,發(fā)現(xiàn)在執(zhí)行
User user2 = session.get(User.class, 1);
時右核,控制臺不輸出任何信息,所以證明了一級緩存的存在渺绒。
-
Hibernate 框架是如何做到數(shù)據(jù)發(fā)生變化時進行同步操作的呢贺喝?
- 實驗步驟: 使用 get 方法查詢 User 對象菱鸥,然后設(shè)置 User 對象的一個屬性,注意:沒有做 update 操作躏鱼。最后發(fā)現(xiàn)氮采,數(shù)據(jù)庫中的記錄也改變了。
- 原因:利用快照機制來完成的(SnapShot)染苛,且該特性正好和持久態(tài)擁有自動更新數(shù)據(jù)庫能力相符合鹊漠。
-
快照機制:
-
-
控制 Session 的一級緩存(了解)
- 學習Session接口中與一級緩存相關(guān)的方法
-
Session.clear() -- 清空緩存。
-
測試代碼:
/** * Session.clear() -- 清空緩存茶行。 */ @Test public void run5(){ Session session = HibernateUtils.getSession(); Transaction tr = session.beginTransaction(); // 最簡單的證明躯概,查詢兩次 User user1 = session.get(User.class, 1); System.out.println(user1.getName()); // 清空緩存 session.clear(); User user2 = session.get(User.class, 1); System.out.println(user2.getName()); tr.commit(); session.close(); }
-
控制臺輸出:
Hibernate: select user0_.id as id1_1_0_, user0_.version as version2_1_0_, user0_.name as name3_1_0_, user0_.age as age4_1_0_ from t_user user0_ where user0_.id=? 隔離老王 Hibernate: select user0_.id as id1_1_0_, user0_.version as version2_1_0_, user0_.name as name3_1_0_, user0_.age as age4_1_0_ from t_user user0_ where user0_.id=? 隔離老王
由此可見,clear方法可以清除緩存畔师。
-
-
Session.evict(Object entity) -- 從一級緩存中清除指定的實體對象娶靡。
- 若把上面代碼中的
session.clear();
改為session.evict(user1);
,則控制臺輸出仍如上看锉。
- 若把上面代碼中的
-
Session.flush() -- 刷出緩存
在一般的快照機制中姿锭,是在事務(wù)提交時(
tr.commit();
)伯铣,比較緩存與快照的不同呻此,然后 Hibernate 框架自動執(zhí)行update
SQL 語句,再更新數(shù)據(jù)庫腔寡。而如果調(diào)用 flush 方法趾诗,則在執(zhí)行該方法時就比較緩存與快照的不同,然后 Hibernate 框架自動執(zhí)行
update
SQL 語句蹬蚁,最后在事務(wù)提交時更新數(shù)據(jù)庫恃泪。
上面兩點的區(qū)別本人經(jīng)過斷點調(diào)試一一驗證,以保證其正確性犀斋。
-
- 學習Session接口中與一級緩存相關(guān)的方法
4贝乎、Hibernate 中的事務(wù)與并發(fā)
-
事務(wù)相關(guān)的概念
-
什么是事務(wù)
- 事務(wù)就是邏輯上的一組操作,組成事務(wù)的各個執(zhí)行單元叽粹,操作要么全都成功览效,要么全都失敗.
- 轉(zhuǎn)賬的例子:冠希給美美轉(zhuǎn)錢,扣錢虫几,加錢锤灿。兩個操作組成了一個事情!
-
事務(wù)的特性
- 原子性 -- 事務(wù)不可分割.
- 一致性 -- 事務(wù)執(zhí)行的前后數(shù)據(jù)的完整性保持一致.
- 隔離性 -- 一個事務(wù)執(zhí)行的過程中,不應(yīng)該受到其他的事務(wù)的干擾.
- 持久性 -- 事務(wù)一旦提交,數(shù)據(jù)就永久保持到數(shù)據(jù)庫中.
-
如果不考慮隔離性:引發(fā)一些讀的問題
- 臟讀 -- 一個事務(wù)讀到了另一個事務(wù)未提交的數(shù)據(jù)(數(shù)據(jù)庫隔離中最重要的問題)
- 不可重復(fù)讀 -- 一個事務(wù)讀到了另一個事務(wù)已經(jīng)提交的 update 數(shù)據(jù),導(dǎo)致多次查詢結(jié)果不一致.
- 虛讀 -- 一個事務(wù)讀到了另一個事務(wù)已經(jīng)提交的 insert 數(shù)據(jù),導(dǎo)致多次查詢結(jié)構(gòu)不一致.
-
通過設(shè)置數(shù)據(jù)庫的隔離級別來解決上述讀的問題
- 未提交讀:以上的讀的問題都有可能發(fā)生.
- 已提交讀:避免臟讀,但是不可重復(fù)讀辆脸,虛讀都有可能發(fā)生.
- 可重復(fù)讀:避免臟讀但校,不可重復(fù)讀.但是虛讀是有可能發(fā)生.
- 串行化:以上讀的情況都可以避免.
-
如果想在Hibernate的框架中來設(shè)置隔離級別,需要在 hibernate.cfg.xml 的配置文件中通過標簽來配置
- 通過:hibernate.connection.isolation = 4 來配置
- 取值:
- 1—Read uncommitted isolation
- 2—Read committed isolation
- 4—Repeatable read isolation
- 8—Serializable isolation
-
-
丟失更新的問題
如果不考慮隔離性啡氢,也會產(chǎn)生寫入數(shù)據(jù)的問題状囱,這一類的問題叫丟失更新的問題术裸。
-
例如:兩個事務(wù)同時對某一條記錄做修改,就會引發(fā)丟失更新的問題亭枷。
- A 事務(wù)和 B 事務(wù)同時獲取到一條數(shù)據(jù)袭艺,同時再做修改
- 如果 A 事務(wù)修改完成后,提交了事務(wù)
-
B 事務(wù)修改完成后叨粘,不管是提交還是回滾猾编,如果不做處理,都會對數(shù)據(jù)產(chǎn)生影響
-
解決方案有兩種
-
悲觀鎖
-
采用的是數(shù)據(jù)庫提供的一種鎖機制升敲,如果采用做了這種機制袍镀,在SQL語句的后面添加 for update 子句
當A事務(wù)在操作該條記錄時,會把該條記錄鎖起來冻晤,其他事務(wù)是不能操作這條記錄的。
只有當A事務(wù)提交后绸吸,鎖釋放了鼻弧,其他事務(wù)才能操作該條記錄
-
-
樂觀鎖
-
使用的不是數(shù)據(jù)庫鎖機制,而是采用版本號的機制來解決的锦茁。給表結(jié)構(gòu)添加一個字段
version=0
攘轩,默認值是0當 A 事務(wù)在操作完該條記錄,提交事務(wù)時码俩,會先檢查版本號度帮,如果發(fā)生版本號的值相同時,才可以提交事務(wù)稿存。同時會更新版本號
version=1
.當 B 事務(wù)操作完該條記錄時笨篷,提交事務(wù)時,會先檢查版本號瓣履,如果發(fā)現(xiàn)版本不同時率翅,程序會出現(xiàn)錯誤。
-
-
-
使用 Hibernate 框架解決丟失更新的問題
-
悲觀鎖(效率較低袖迎,不常見)
- 使用
session.get(Customer.class, 1,LockMode.UPGRADE);
方法
- 使用
-
樂觀鎖
在對應(yīng)的 JavaBean 中添加一個屬性冕臭,名稱可以是任意的。例如:
private Integer version;
提供 get 和 set 方法在映射的配置文件中燕锥,提供
<version name="version"/>
標簽即可辜贵。
-
附上本人以前學習的筆記,從 JDBC 角度分析事務(wù)归形、隔離級別托慨、丟失更新問題:http://www.reibang.com/p/aacde54542b5
-
綁定本地的 Session
-
在上文所附的文章里,講解了 JavaWEB 的事務(wù)暇榴,需要在業(yè)務(wù)層使用 Connection 來開啟事務(wù)榴芳。
- 一種是通過參數(shù)的方式嗡靡,一層一層的傳遞下去
- 另一種是把 Connection 綁定到 ThreadLocal 對象中,因為 ThreadLocal 對于內(nèi)通過 map 存儲了
key = 當前線程
,value= Connection對象
ThreadLocal 兩個重要方法
- get
public T get() 返回此線程局部變量的當前線程副本中的值窟感。如果變量沒有用于當前線程的值讨彼,則先將其初始化為調(diào)用 initialValue() 方法返回的值。
返回:
此線程局部變量的當前線程的值
- set
public void set(T value) 將此線程局部變量的當前線程副本中的值設(shè)置為指定值柿祈。大部分子類不需要重寫此方法哈误,它們只依靠 initialValue() 方法來設(shè)置線程局部變量的值。
參數(shù):
value - 存儲在此線程局部變量的當前線程副本中的值躏嚎。
-
在 Hibernate 框架中蜜自,使用 session 對象開啟事務(wù),而 session 對象存在于業(yè)務(wù)層中卢佣,所以需要把 session 對象傳遞到持久層重荠,在持久層使用 session 對象操作數(shù)數(shù)據(jù)對象,框架提供了 ThreadLocal 的方式:
-
首先需要在 hibernate.cfg.xml 的配置文件中提供配置
<property name="hibernate.current_session_context_class">thread</property>
-
然后重寫 HibernateUtil 的工具類虚茶,使用 SessionFactory 的 getCurrentSession( )方法戈鲁,獲取當前的 Session 對象。并且該 Session 對象不用手動關(guān)閉嘹叫,線程結(jié)束了婆殿,會自動關(guān)閉。
-
HibernateUtil 工具類:
package com.itheima.utils; import org.hibernate.Session; import org.hibernate.SessionFactory; import org.hibernate.cfg.Configuration; /** * Hibernate框架的工具類 * @author Administrator */ public class HibernateUtils { // ctrl + shift + x private static final Configuration CONFIG; private static final SessionFactory FACTORY; // 編寫靜態(tài)代碼塊 static{ // 加載XML的配置文件 CONFIG = new Configuration().configure(); // 構(gòu)造工廠 FACTORY = CONFIG.buildSessionFactory(); } /** * 從工廠中獲取Session對象 * @return */ public static Session getSession(){ return FACTORY.openSession(); } /** * // 從ThreadLocal類中獲取到session的對象 * @return */ public static Session getCurrentSession(){ return FACTORY.getCurrentSession(); } }
-
UserService 業(yè)務(wù)層:
package com.itheima.service; import org.hibernate.Session; import org.hibernate.Transaction; import com.itheima.dao.UserDao; import com.itheima.domain.User; import com.itheima.utils.HibernateUtils; public class UserService { public void save(User u1,User u2){ UserDao dao = new UserDao(); // 獲取session Session session = HibernateUtils.getCurrentSession(); // 開啟事務(wù) Transaction tr = session.beginTransaction(); try { dao.save1(u1); int a = 10/0; dao.save2(u2); // 提交事務(wù) tr.commit(); } catch (Exception e) { e.printStackTrace(); // 出現(xiàn)問題:回滾事務(wù) tr.rollback(); }finally{ // 自己釋放資源罩扇,現(xiàn)在婆芦,session不用關(guān)閉,線程結(jié)束了喂饥,自動關(guān)閉的O肌! } } }
-
UserDao 持久層:
package com.itheima.dao; import org.hibernate.Session; import com.itheima.domain.User; import com.itheima.utils.HibernateUtils; public class UserDao { public void save1(User u1){ Session session = HibernateUtils.getCurrentSession(); session.save(u1); //不用寫 session.close; } public void save2(User u2){ Session session = HibernateUtils.getCurrentSession(); session.save(u2); } }
-
注意:想使用 getCurrentSession() 方法员帮,必須要先配置才能使用荆陆。
-
-
5、Hibernate 的查詢方式(HQL:Hibernate Query Language)
-
Query 查詢接口
-
具體的查詢代碼如下:
// 1.查詢所有記錄 /*Query query = session.createQuery("from Customer"); List<Customer> list = query.list(); System.out.println(list);*/ // 2.條件查詢(? 從0開始) /*Query query = session.createQuery("from Customer where name = ?"); query.setString(0, "李健"); List<Customer> list = query.list(); System.out.println(list);*/ // 3.條件查詢(設(shè)置別名) /*Query query = session.createQuery("from Customer where name = :aaa and age = :bbb"); query.setString("aaa", "李健"); query.setInteger("bbb", 38); List<Customer> list = query.list(); System.out.println(list);*/ // 4.模糊查詢(注意 % 要寫在 setString 里面) /*Query query = session.createQuery("from User where name like ?"); query.setString(0, "%老%"); List<Customer> list = query.list(); System.out.println(list);*/
在 JDBC 中集侯,記錄從1開始被啼!
在 HQL 中,記錄從0開始棠枉!
-
-
Criteria 查詢接口(做條件查詢非常合適)
完全面向?qū)ο笈ㄌ澹a中基本不會體現(xiàn) SQL 語言的特點
Criterion 是 Hibernate 提供的條件查詢的對象,如果想傳入條件辈讶,可以使用工具類 Restrictions 命浴,在其內(nèi)部有許多靜態(tài)方法可以用來描述條件
-
具體的查詢代碼如下
// 1.查詢所有記錄 /*Criteria criteria = session.createCriteria(Customer.class); List<Customer> list = criteria.list(); System.out.println(list);*/ // 2.條件查詢(查詢 name 字段為李健的記錄) /*Criteria criteria = session.createCriteria(Customer.class); criteria.add(Restrictions.eq("name", "李健")); List<Customer> list = criteria.list(); System.out.println(list);*/ // 3.條件查詢(查詢 name 字段為李健、age 字段為38的記錄) /*Criteria criteria = session.createCriteria(Customer.class); criteria.add(Restrictions.eq("name", "李健")); criteria.add(Restrictions.eq("age", 38)); List<Customer> list = criteria.list(); System.out.println(list);*/ // Restrictions 提供了許多靜態(tài)方法來描述條件 criteria.add(Restrictions.gt("age", 18)); // gt:大于 criteria.add(Restrictions.like("name", "%小%")); // like:模糊查詢