本文包括:
1、Hibernate的持久化類
2、Hibernate 持久化對象的三個狀態(難點)
3、Hibernate 的一級緩存
4、Hibernate 中的事務與并發
5、Hibernate 的查詢方式(HQL:Hibernate Query Language)
1、Hibernate的持久化類
-
什么是持久化類?
持久化類:就是一個 Java 類(咱們編寫的 JavaBean),這個 Java 類與表建立了映射關系就可以成為是持久化類。
簡單記:持久化類 = JavaBean + xxx.hbm.xml
-
持久化類的編寫規則:
提供一個無參數 public訪問控制符的構造器 —— 底層需要進行反射。
提供一個標識屬性,映射數據表主鍵字段 —— 唯一標識 OID,數據庫中通過主鍵,Java 對象通過地址確定對象,持久化類通過唯一標識 OID 確定記錄。
SQL 語句:
cust_id
bigint(32) NOT NULL AUTO_INCREMENT COMMENT '客戶編號(主鍵)'JavaBean 代碼:private Long cust_id;
結論:JavaBean 中的 cust_id 即為唯一標識 OID。
所有屬性提供 public 訪問控制符的 set 和 get 方法。
標識屬性應盡量使用基本數據類型的包裝類型(默認值為 null)。
-
自然主鍵和代理主鍵的區別:
自然主鍵:該主鍵是對象本身的一個屬性。例如:創建一個人員表,每個人都有一個身份證號(唯一的)。使用身份證號作為表的主鍵,稱為自然主鍵。(開發中不會使用這種方式)
代理主鍵:該主鍵不是對象本身的一個屬性。例如:創建一個人員表,為每個人員單獨創建一個字段,用這個字段作為主鍵,稱為代理主鍵。(開發中推薦使用這種方式)
簡單記:創建表時,新增一個毫無關系的字段,用該字段作為主鍵。
-
主鍵的生成策略:
-
increment:適用于 short,int,long 作為主鍵,沒有使用數據庫的自動增長機制。
-
Hibernate 中提供的一種增長機制,具體步驟如下:
先進行查詢:select max(id) from user;
再進行插入:獲得最大值+1作為新的記錄的主鍵。
問題:不能在集群環境下或者有并發訪問的情況下使用。
分析:在查詢時,有可能有兩個用戶幾乎同時得到相同的 id,再插入時就有可能主鍵沖突!
-
-
identity:適用于 short,int,long 作為主鍵。但是必須使用在有自動增長機制的數據庫中,并且該數據庫采用的是數據庫底層的自動增長機制。
- 底層使用的是數據庫的自動增長(auto_increment),像 Oracle 數據庫沒有自動增長機制,而MySql、DB2 等數據庫有自動增長的機制。
-
sequence:適用于 short,int,long 作為主鍵,底層使用的是序列的增長方式。
- Oracle 數據庫底層沒有自動增長,若想自動增長需要使用序列。
-
uuid:適用于 char,varchar 類型的作為主鍵。
- 使用隨機的字符串作為主鍵.
-
native:本地策略。根據底層的數據庫不同,自動選擇適用于該種數據庫的生成策略(short,int,long)。
如果底層使用的 MySQL 數據庫:相當于 identity.
如果底層使用 Oracle 數據庫:相當于 sequence.
assigned:主鍵的生成不用 Hibernate 管理了,必須手動設置主鍵。
重點掌握:uuid(字符串)、native(數字)
-
2、Hibernate 持久化對象的三個狀態(難點)
-
持久化對象的狀態
Hibernate 的持久化類(前文已寫)
-
Hibernate 的持久化類的狀態
- Hibernate為了管理持久化類:將持久化類分成了三個狀態
-
瞬時態:Transient Object
- 沒有持久化標識 OID, 沒有被納入到 Session 對象的管理(即沒有關系)
-
持久態:Persistent Object
- 有持久化標識 OID,已經被納入到 Session 對象的管理.
-
托管態:Detached Object
- 有持久化標識 OID,沒有被納入到 Session 對象的管理.
-
- Hibernate為了管理持久化類:將持久化類分成了三個狀態
-
Hibernate 持久化對象的狀態的轉換
-
瞬時態 -- 沒有持久化標識 OID, 沒有被納入到 Session 對象的管理
-
獲得瞬時態的對象
User user = new User();
創建了持久化類的對象,該對象還沒有 OID,也和 Session 對象無關,所以是瞬時態。
-
瞬時態對象轉換持久態
session.save(user); 或者 session.saveOrUpdate(user);
user對象進入緩存,且自動生成了 OID,故為持久態。
-
瞬時態對象轉換成托管態
user.setId(1);
手動設置了 OID,但沒有和 Session 對象發生關系,故為托管態。
-
-
持久態 -- 有持久化標識OID,已經被納入到Session對象的管理
-
獲得持久態的對象
get()/load();
-
持久態轉換成瞬時態對象
delete(); --- 比較有爭議的,進入特殊的狀態(刪除態:Hibernate中不建議使用的)
-
持久態對象轉成脫管態對象
session的close()/evict()/clear();
Session 對象被銷毀,所以持久化類的對象沒有被 session 管理,所以為托管態。
-
-
脫管態 -- 有持久化標識OID,沒有被納入到Session對象的管理
-
獲得托管態對象:不建議直接獲得脫管態的對象.
User user = new User(); user.setId(1);
-
脫管態對象轉換成持久態對象
update();/saveOrUpdate()/lock();
-
脫管態對象轉換成瞬時態對象
user.setId(null);
-
-
注意:持久態對象有自動更新數據庫的能力!!!
-
測試代碼:
/** * 持久態的對象有自動更新數據庫的能力 * session的一級緩存!! */ @Test public void run1(){ Session session = HibernateUtils.getSession(); Transaction tr = session.beginTransaction(); // 獲取到持久態的對象 User user = session.get(User.class,1); // user是持久態,有自動更新數據庫的能力 System.out.println(user.getName()); // 重新設置新的名稱 user.setName("隔離老王"); // 正常編寫代碼 // session.update(user); 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: update t_user set version=?, name=?, age=? where id=? and version=?
從中可以發現,在測試代碼中我們把
session.update(user);
注釋掉了,但是在控制臺中發現仍有update
語句,這就是持久態有自動更新數據庫的能力,具體原因是 Session 對象的一級緩存(見下節)。
-
-
-
三個狀態之間的轉換圖解:
3、Hibernate 的一級緩存
-
Session 對象的一級緩存
-
什么是緩存?
- 其實就是一塊內存空間,將數據源(數據庫或者文件)中的數據存放到緩存中。再次獲取的時候,直接從緩存中獲取,可以提升程序的性能!
-
Hibernate 框架提供了兩種緩存
- 一級緩存 -- 自帶的不可卸載的。一級緩存的生命周期與 session 一致,一級緩存稱為 session 級別的緩存.
- 二級緩存 -- 默認沒有開啟,需要手動配置才可以使用的。二級緩存可以在多個 session 中共享數據,二級緩存稱為是 sessionFactory 級別的緩存.
-
Session 對象的緩存概述
- Session 接口中,有一系列的 java 的集合,這些 java 集合構成了 Session 級別的緩存(一級緩存)。將對象存入到一級緩存中,session 沒有結束生命周期,那么對象在 session 中存放著。
- 內存中包含 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,發現在執行
User user2 = session.get(User.class, 1);
時,控制臺不輸出任何信息,所以證明了一級緩存的存在。
-
Hibernate 框架是如何做到數據發生變化時進行同步操作的呢?
- 實驗步驟: 使用 get 方法查詢 User 對象,然后設置 User 對象的一個屬性,注意:沒有做 update 操作。最后發現,數據庫中的記錄也改變了。
- 原因:利用快照機制來完成的(SnapShot),且該特性正好和持久態擁有自動更新數據庫能力相符合。
-
快照機制:
-
-
控制 Session 的一級緩存(了解)
- 學習Session接口中與一級緩存相關的方法
-
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() -- 刷出緩存
在一般的快照機制中,是在事務提交時(
tr.commit();
),比較緩存與快照的不同,然后 Hibernate 框架自動執行update
SQL 語句,再更新數據庫。而如果調用 flush 方法,則在執行該方法時就比較緩存與快照的不同,然后 Hibernate 框架自動執行
update
SQL 語句,最后在事務提交時更新數據庫。
上面兩點的區別本人經過斷點調試一一驗證,以保證其正確性。
-
- 學習Session接口中與一級緩存相關的方法
4、Hibernate 中的事務與并發
-
事務相關的概念
-
什么是事務
- 事務就是邏輯上的一組操作,組成事務的各個執行單元,操作要么全都成功,要么全都失敗.
- 轉賬的例子:冠希給美美轉錢,扣錢,加錢。兩個操作組成了一個事情!
-
事務的特性
- 原子性 -- 事務不可分割.
- 一致性 -- 事務執行的前后數據的完整性保持一致.
- 隔離性 -- 一個事務執行的過程中,不應該受到其他的事務的干擾.
- 持久性 -- 事務一旦提交,數據就永久保持到數據庫中.
-
如果不考慮隔離性:引發一些讀的問題
- 臟讀 -- 一個事務讀到了另一個事務未提交的數據(數據庫隔離中最重要的問題)
- 不可重復讀 -- 一個事務讀到了另一個事務已經提交的 update 數據,導致多次查詢結果不一致.
- 虛讀 -- 一個事務讀到了另一個事務已經提交的 insert 數據,導致多次查詢結構不一致.
-
通過設置數據庫的隔離級別來解決上述讀的問題
- 未提交讀:以上的讀的問題都有可能發生.
- 已提交讀:避免臟讀,但是不可重復讀,虛讀都有可能發生.
- 可重復讀:避免臟讀,不可重復讀.但是虛讀是有可能發生.
- 串行化:以上讀的情況都可以避免.
-
如果想在Hibernate的框架中來設置隔離級別,需要在 hibernate.cfg.xml 的配置文件中通過標簽來配置
- 通過:hibernate.connection.isolation = 4 來配置
- 取值:
- 1—Read uncommitted isolation
- 2—Read committed isolation
- 4—Repeatable read isolation
- 8—Serializable isolation
-
-
丟失更新的問題
如果不考慮隔離性,也會產生寫入數據的問題,這一類的問題叫丟失更新的問題。
-
例如:兩個事務同時對某一條記錄做修改,就會引發丟失更新的問題。
- A 事務和 B 事務同時獲取到一條數據,同時再做修改
- 如果 A 事務修改完成后,提交了事務
-
B 事務修改完成后,不管是提交還是回滾,如果不做處理,都會對數據產生影響
-
解決方案有兩種
-
悲觀鎖
-
采用的是數據庫提供的一種鎖機制,如果采用做了這種機制,在SQL語句的后面添加 for update 子句
當A事務在操作該條記錄時,會把該條記錄鎖起來,其他事務是不能操作這條記錄的。
只有當A事務提交后,鎖釋放了,其他事務才能操作該條記錄
-
-
樂觀鎖
-
使用的不是數據庫鎖機制,而是采用版本號的機制來解決的。給表結構添加一個字段
version=0
,默認值是0當 A 事務在操作完該條記錄,提交事務時,會先檢查版本號,如果發生版本號的值相同時,才可以提交事務。同時會更新版本號
version=1
.當 B 事務操作完該條記錄時,提交事務時,會先檢查版本號,如果發現版本不同時,程序會出現錯誤。
-
-
-
使用 Hibernate 框架解決丟失更新的問題
-
悲觀鎖(效率較低,不常見)
- 使用
session.get(Customer.class, 1,LockMode.UPGRADE);
方法
- 使用
-
樂觀鎖
在對應的 JavaBean 中添加一個屬性,名稱可以是任意的。例如:
private Integer version;
提供 get 和 set 方法在映射的配置文件中,提供
<version name="version"/>
標簽即可。
-
附上本人以前學習的筆記,從 JDBC 角度分析事務、隔離級別、丟失更新問題:http://www.lxweimin.com/p/aacde54542b5
-
綁定本地的 Session
-
在上文所附的文章里,講解了 JavaWEB 的事務,需要在業務層使用 Connection 來開啟事務。
- 一種是通過參數的方式,一層一層的傳遞下去
- 另一種是把 Connection 綁定到 ThreadLocal 對象中,因為 ThreadLocal 對于內通過 map 存儲了
key = 當前線程
,value= Connection對象
ThreadLocal 兩個重要方法
- get
public T get() 返回此線程局部變量的當前線程副本中的值。如果變量沒有用于當前線程的值,則先將其初始化為調用 initialValue() 方法返回的值。
返回:
此線程局部變量的當前線程的值
- set
public void set(T value) 將此線程局部變量的當前線程副本中的值設置為指定值。大部分子類不需要重寫此方法,它們只依靠 initialValue() 方法來設置線程局部變量的值。
參數:
value - 存儲在此線程局部變量的當前線程副本中的值。
-
在 Hibernate 框架中,使用 session 對象開啟事務,而 session 對象存在于業務層中,所以需要把 session 對象傳遞到持久層,在持久層使用 session 對象操作數數據對象,框架提供了 ThreadLocal 的方式:
-
首先需要在 hibernate.cfg.xml 的配置文件中提供配置
<property name="hibernate.current_session_context_class">thread</property>
-
然后重寫 HibernateUtil 的工具類,使用 SessionFactory 的 getCurrentSession( )方法,獲取當前的 Session 對象。并且該 Session 對象不用手動關閉,線程結束了,會自動關閉。
-
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; // 編寫靜態代碼塊 static{ // 加載XML的配置文件 CONFIG = new Configuration().configure(); // 構造工廠 FACTORY = CONFIG.buildSessionFactory(); } /** * 從工廠中獲取Session對象 * @return */ public static Session getSession(){ return FACTORY.openSession(); } /** * // 從ThreadLocal類中獲取到session的對象 * @return */ public static Session getCurrentSession(){ return FACTORY.getCurrentSession(); } }
-
UserService 業務層:
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(); // 開啟事務 Transaction tr = session.beginTransaction(); try { dao.save1(u1); int a = 10/0; dao.save2(u2); // 提交事務 tr.commit(); } catch (Exception e) { e.printStackTrace(); // 出現問題:回滾事務 tr.rollback(); }finally{ // 自己釋放資源,現在,session不用關閉,線程結束了,自動關閉的!! } } }
-
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.條件查詢(設置別名) /*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 查詢接口(做條件查詢非常合適)
完全面向對象,代碼中基本不會體現 SQL 語言的特點
Criterion 是 Hibernate 提供的條件查詢的對象,如果想傳入條件,可以使用工具類 Restrictions ,在其內部有許多靜態方法可以用來描述條件
-
具體的查詢代碼如下
// 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 提供了許多靜態方法來描述條件 criteria.add(Restrictions.gt("age", 18)); // gt:大于 criteria.add(Restrictions.like("name", "%小%")); // like:模糊查詢