Session 介紹
Session 接口是 Hibernate 向應(yīng)用程序提供的操縱數(shù)據(jù)庫的最主要的接口, 它提供了基本的保存, 更新, 刪除和加載 Java 對(duì)象的方法.
Session 具有一個(gè)緩存, 位于緩存中的對(duì)象稱為持久化對(duì)象, 它和數(shù)據(jù)庫中的相關(guān)記錄對(duì)應(yīng). Session 能夠在某些時(shí)間點(diǎn), 按照緩存中對(duì)象的變化來執(zhí)行相關(guān)的 SQL 語句, 來同步更新數(shù)據(jù)庫, 這一過程被稱為刷新緩存(flush)
-
站在持久化的角度, Hibernate 把對(duì)象分為 4 種狀態(tài):
- 持久化狀態(tài)
- 臨時(shí)狀態(tài)
- 游離狀態(tài)
- 刪除狀態(tài)
Session 的特定方法能使對(duì)象從一個(gè)狀態(tài)轉(zhuǎn)換到另一個(gè)狀態(tài).
Session 緩存
- 在 Session 接口的實(shí)現(xiàn)中包含一系列的 Java 集合, 這些 Java 集合構(gòu)成了 Session 緩存. 只要 Session 實(shí)例沒有結(jié)束生命周期, 且沒有清理緩存,則存放在它緩存中的對(duì)象也不會(huì)結(jié)束生命周期
- Session 緩存可減少 Hibernate 應(yīng)用程序訪問數(shù)據(jù)庫的頻率
flush 緩存
- flush:Session 按照緩存中對(duì)象的屬性變化來同步更新數(shù)據(jù)庫
- 默認(rèn)情況下 Session 在以下時(shí)間點(diǎn)刷新緩存:
- 顯式調(diào)用 Session 的 flush() 方法
- 當(dāng)應(yīng)用程序調(diào)用 Transaction 的 commit()方法的時(shí), 該方法先 flush 斋日,然后在向數(shù)據(jù)庫提交事務(wù)
- 當(dāng)應(yīng)用程序執(zhí)行一些查詢(HQL, Criteria)操作時(shí),如果緩存中持久化對(duì)象的屬性已經(jīng)發(fā)生了變化寥枝,會(huì)先 flush 緩存,以保證查詢結(jié)果能夠反映持久化對(duì)象的最新狀態(tài)
- flush 緩存的例外情況: 如果對(duì)象使用 native 生成器生成 OID, 那么當(dāng)調(diào)用 Session 的 save() 方法保存對(duì)象時(shí), 會(huì)立即執(zhí)行向數(shù)據(jù)庫插入該實(shí)體的 insert 語句.
- commit() 和 flush() 方法的區(qū)別:flush 執(zhí)行一系列 sql 語句将塑,但不提交事務(wù)脉顿;commit 方法先調(diào)用flush() 方法,然后提交事務(wù). 意味著提交事務(wù)意味著對(duì)數(shù)據(jù)庫操作永久保存下來点寥。
設(shè)定刷新緩存的時(shí)間點(diǎn)
若希望改變 flush 的默認(rèn)時(shí)間點(diǎn), 可以通過 Session 的 setFlushMode() 方法顯式設(shè)定 flush 的時(shí)間點(diǎn)
清理緩存的模式 | 各種查詢方法 | Transaction 的 commit() 方法 | Session 的 flush() 方法 |
---|---|---|---|
FlushMode.AUTO(默認(rèn)模式) | 清理 | 清理 | 清理 |
FlushMode.COMMIT | 不清理 | 清理 | 清理 |
FlushMode.NEVER | 不清理 | 不清理 | 清理 |
refresh()
會(huì)強(qiáng)制發(fā)送 SELECT 語句, 以使 Session 緩存中對(duì)象的狀態(tài)和數(shù)據(jù)表中對(duì)應(yīng)的記錄保持一致!
@Test
public void testRefresh() {
Student student = session.get(Student.class, 1);
System.out.println(student);
//在此處斷點(diǎn)艾疟, 然后修改數(shù)據(jù)庫中數(shù)據(jù),向下執(zhí)行
session.refresh(student);
System.out.println(student);
}
持久化對(duì)象的狀態(tài)
- 臨時(shí)對(duì)象(Transient):
- 在使用代理主鍵的情況下, OID 通常為 null
- 不處于 Session 的緩存中
- 在數(shù)據(jù)庫中沒有對(duì)應(yīng)的記錄
- 持久化對(duì)象(也叫”托管”)(Persist):
- OID 不為 null
- 位于 Session 緩存中
- 若在數(shù)據(jù)庫中已經(jīng)有和其對(duì)應(yīng)的記錄, 持久化對(duì)象和數(shù)據(jù)庫中的相關(guān)記錄對(duì)應(yīng)
- Session 在 flush 緩存時(shí), 會(huì)根據(jù)持久化對(duì)象的屬性變化, 來同步更新數(shù)據(jù)庫
- 在同一個(gè) Session 實(shí)例的緩存中, 數(shù)據(jù)庫表中的每條記錄只對(duì)應(yīng)唯一的持久化對(duì)象
- 刪除對(duì)象(Removed)
- 在數(shù)據(jù)庫中沒有和其 OID 對(duì)應(yīng)的記錄
- 不再處于 Session 緩存中
- 一般情況下, 應(yīng)用程序不該再使用被刪除的對(duì)象
- 游離對(duì)象(也叫”脫管”) (Detached):
- OID 不為 null
- 不再處于 Session 緩存中
- 一般情況需下, 游離對(duì)象是由持久化對(duì)象轉(zhuǎn)變過來的, 因此在數(shù)據(jù)庫中可能還存在與它對(duì)應(yīng)的記錄
-
對(duì)象的狀態(tài)轉(zhuǎn)換圖
Session API 使用
Save() 方法
- Session 的 save() 方法使一個(gè)臨時(shí)對(duì)象轉(zhuǎn)變?yōu)槌志没瘜?duì)象
- Session 的 save() 方法完成以下操作:
- 把映射對(duì)象加入到 Session 緩存中, 使它進(jìn)入持久化狀態(tài)
- 選用映射文件指定的標(biāo)識(shí)符生成器, 為持久化對(duì)象分配唯一的 OID. 在使用代理主鍵的情況下, setId() 方法為映射對(duì)象設(shè)置 OID 是無效的.
- 計(jì)劃執(zhí)行一條 insert 語句:在 flush 緩存的時(shí)候
- Hibernate 通過持久化對(duì)象的 OID 來維持它和數(shù)據(jù)庫相關(guān)記錄的對(duì)應(yīng)關(guān)系. 當(dāng)映射對(duì)象處于持久化狀態(tài)時(shí), 不允許程序隨意修改它的 ID
- persist() 和 save() 區(qū)別:
- 當(dāng)對(duì)一個(gè) OID 不為 Null 的對(duì)象執(zhí)行 save() 方法時(shí), 會(huì)把該對(duì)象以一個(gè)新的 oid 保存到數(shù)據(jù)庫中; 但執(zhí)行 persist() 方法時(shí)會(huì)拋出一個(gè)異常.
/**
* 1. save() 方法
* 1). 使一個(gè)臨時(shí)對(duì)象變?yōu)槌志没瘜?duì)象
* 2). 為對(duì)象分配 ID.
* 3). 在 flush 緩存時(shí)會(huì)發(fā)送一條 INSERT 語句.
* 4). 在 save 方法之前的 id 是無效的
* 5). 持久化對(duì)象的 ID 是不能被修改的!
*/
@Test
public void testSave() {
Student student = new Student("小明", 56, new Date(new java.util.Date().getTime()));
session.save(student);
// student.setId(200);
}
/**
* persist(): 也會(huì)執(zhí)行 INSERT 操作
* 和 save() 的區(qū)別 :
* 在調(diào)用 persist 方法之前, 若對(duì)象已經(jīng)有 id 了, 則不會(huì)執(zhí)行 INSERT, 而拋出異常
*/
@Test
public void testPersist() {
Student student = new Student("小明", 57, new Date(new java.util.Date().getTime()));
//student.setId(35);
session.persist(student);
}
get() 和 load() 方法
- 都可以根據(jù)跟定的 OID 從數(shù)據(jù)庫中加載一個(gè)持久化對(duì)象
- 區(qū)別
- 執(zhí)行 get 方法: 會(huì)立即加載對(duì)象. 執(zhí)行 load 方法, 若不適用該對(duì)象, 則不會(huì)立即執(zhí)行查詢操作, 而返回一個(gè)代理對(duì)象敢辩,get 是 立即檢索, load 是延遲檢索.
- 在需要初始化代理對(duì)象之前已經(jīng)關(guān)閉了 Session 蔽莱, load 方法可能會(huì)拋出 LazyInitializationException 異常。
- 若數(shù)據(jù)表中沒有對(duì)應(yīng)的記錄, Session 也沒有被關(guān)閉.get 返回 null戚长,load 若不使用該對(duì)象的任何屬性, 沒問題; 若一旦使用該對(duì)象盗冷,就會(huì)去獲取對(duì)象, 就會(huì)拋出異常(ObjectNotFoundException ).
@Test
public void testGet() {
//get 獲取對(duì)象時(shí)立即加載
Student student = session.get(Student.class, 1);
System.out.println(student.getName());
}
@Test
public void testLoad() {
//load 獲取對(duì)象時(shí),等到使用時(shí)才會(huì)加載同廉,如果使用時(shí)加載的對(duì)象不存在仪糖,就會(huì)拋出 ObjectNotFoundException 異常
Student student = session.load(Student.class, 100);
System.out.println(student.getName());
}
update() 方法
- Session 的 update() 方法使一個(gè)游離對(duì)象轉(zhuǎn)變?yōu)槌志没瘜?duì)象, 并且計(jì)劃執(zhí)行一條 update 語句.
- 若更新一個(gè)持久化對(duì)象, 不需要顯示的調(diào)用 update 方法. 因?yàn)樵谡{(diào)用 Transaction的 commit() 方法時(shí), 會(huì)先執(zhí)行 session 的 flush 方法.
- 若希望 Session 僅當(dāng)修改了 News 對(duì)象的屬性時(shí), 才執(zhí)行 update() 語句, 可以把映射文件中 <class> 元素的 select-before-update 設(shè)為 true. 該屬性的默認(rèn)值為 false
- 當(dāng) update() 方法關(guān)聯(lián)一個(gè)游離對(duì)象時(shí), 如果在 Session 的緩存中已經(jīng)存在相同 OID 的持久化對(duì)象, 會(huì)拋出異常
- 當(dāng) update() 方法關(guān)聯(lián)一個(gè)游離對(duì)象時(shí), 如果在數(shù)據(jù)庫中不存在相應(yīng)的記錄, 也會(huì)拋出異常.
<class name="com.cfox.hibernate.Student" table="STUDENT_INFO" select-before-update="true">
/**
* update:
* 1. 若更新一個(gè)持久化對(duì)象, 不需要顯示的調(diào)用 update 方法. 因?yàn)樵谡{(diào)用 Transaction
* 的 commit() 方法時(shí), 會(huì)先執(zhí)行 session 的 flush 方法.
* 2. 更新一個(gè)游離對(duì)象, 需要顯式的調(diào)用 session 的 update 方法. 可以把一個(gè)游離對(duì)象
* 變?yōu)槌志没瘜?duì)象
*
* 需要注意的:
* 1. 無論要更新的游離對(duì)象和數(shù)據(jù)表的記錄是否一致, 都會(huì)發(fā)送 UPDATE 語句.
* 如何能讓 updat 方法不再盲目的出發(fā) update 語句呢 ? 在 .hbm.xml 文件的 class 節(jié)點(diǎn)設(shè)置
* select-before-update=true (默認(rèn)為 false). 但通常不需要設(shè)置該屬性.
*
* 2. 若數(shù)據(jù)表中沒有對(duì)應(yīng)的記錄, 但還調(diào)用了 update 方法, 會(huì)拋出異常
*
* 3. 當(dāng) update() 方法關(guān)聯(lián)一個(gè)游離對(duì)象時(shí),
* 如果在 Session 的緩存中已經(jīng)存在相同 OID 的持久化對(duì)象, 會(huì)拋出異常. 因?yàn)樵?Session 緩存中
* 不能有兩個(gè) OID 相同的對(duì)象!
*
*/
@Test
public void testUpdate() {
Student student = session.get(Student.class, 1);
student.setName("AAA");
session.close();
session = sessionFactory.openSession();
Student student1 = session.get(Student.class, 1);
session.update(student);
}
saveOrUpdate() 方法
- Session 的 saveOrUpdate() 方法同時(shí)包含了 save() 與 update() 方法的功能
- 執(zhí)行流程圖:
-
判定對(duì)象為臨時(shí)對(duì)象的標(biāo)準(zhǔn)
- Java 對(duì)象的 OID 為 null
- 映射文件中為 <id> 設(shè)置了 unsaved-value 屬性, 并且 Java 對(duì)象的 OID 取值與這個(gè) unsaved-value 屬性值匹配
<id name="id" type="java.lang.Integer" unsaved-value="12">
@Test
public void testSaveOrUpdate() {
Student student = new Student("小li", 35, new java.util.Date());
session.saveOrUpdate(student);
}
delete() 方法
Session 的 delete() 方法既可以刪除一個(gè)游離對(duì)象, 也可以刪除一個(gè)持久化對(duì)象
-
Session 的 delete() 方法處理過程
- 計(jì)劃執(zhí)行一條 delete 語句
- 把對(duì)象從 Session 緩存中刪除, 該對(duì)象進(jìn)入刪除狀態(tài).
-
Hibernate 的 cfg.xml 配置文件中有一個(gè)
hibernate.use_identifier_rollback
屬性, 其默認(rèn)值為 false, 若把它設(shè)為 true, 將改變 delete() 方法的運(yùn)行行為: delete() 方法會(huì)把持久化對(duì)象或游離對(duì)象的 OID 設(shè)置為 null, 使它們變?yōu)榕R時(shí)對(duì)象<property name="hibernate.use_identifier_rollback">true</property>
/**
* delete: 執(zhí)行刪除操作. 只要 OID 和數(shù)據(jù)表中一條記錄對(duì)應(yīng), 就會(huì)準(zhǔn)備執(zhí)行 delete 操作
* 若 OID 在數(shù)據(jù)表中沒有對(duì)應(yīng)的記錄, 則拋出異常
*
* 可以通過設(shè)置 hibernate 配置文件 hibernate.use_identifier_rollback 為 true,
* 使刪除對(duì)象后, 把其 OID 置為 null
*/
@Test
public void testDelete() {
Student student = session.get(Student.class, 2);
session.delete(student);
System.out.println(student.toString());
}
doWork(Work) 方法
通過 Hibernate 調(diào)用存儲(chǔ)過程
- Work 接口: 直接通過 JDBC API 來訪問數(shù)據(jù)庫的操作
public interface Work {
void execute(Connection connection) throws SQLException;
}
- Session 的 doWork(Work) 方法用于執(zhí)行 Work 對(duì)象指定的操作, 即調(diào)用 Work 對(duì)象的 execute() 方法. Session 會(huì)把當(dāng)前使用的數(shù)據(jù)庫連接傳遞給 execute() 方法.
@Test
public void testDoWork() {
Work work = new Work() {
// 通過jdbc API 來訪問數(shù)據(jù)庫
@Override
public void execute(Connection connection) throws SQLException {
System.out.println(connection);
}
};
session.doWork(work);
}
Hibernate 與觸發(fā)器協(xié)同工作
- Hibernate 與數(shù)據(jù)庫中的觸發(fā)器協(xié)同工作時(shí), 會(huì)造成兩類問題
觸發(fā)器使 Session 的緩存中的持久化對(duì)象與數(shù)據(jù)庫中對(duì)應(yīng)的數(shù)據(jù)不一致:觸發(fā)器運(yùn)行在數(shù)據(jù)庫中, 它執(zhí)行的操作對(duì) Session 是透明的 - Session 的 update() 方法盲目地激發(fā)觸發(fā)器: 無論游離對(duì)象的屬性是否發(fā)生變化, 都會(huì)執(zhí)行 update 語句, 而 update 語句會(huì)激發(fā)數(shù)據(jù)庫中相應(yīng)的觸發(fā)器
解決方案:- 在執(zhí)行完 Session 的相關(guān)操作后, 立即調(diào)用 Session 的 flush() 和 refresh() 方法, 迫使 Session
- 的緩存與數(shù)據(jù)庫同步(refresh() 方法重新從數(shù)據(jù)庫中加載對(duì)象)
- 在映射文件的的 <class> 元素中設(shè)置 select-before-update 屬性: 當(dāng) Session 的 update 或 saveOrUpdate() 方法更新一個(gè)游離對(duì)象時(shí), 會(huì)先執(zhí)行 Select 語句, 獲得當(dāng)前游離對(duì)象在數(shù)據(jù)庫中的最新數(shù)據(jù), 只有在不一致的情況下才會(huì)執(zhí)行 update 語句