Hibernate 的一級緩存

一、概述

Session 是 Hibernate 嚮應用程序提供操縱數據的主要接口,它提供了基本的保存、更新、刪除和加載 Java 對象的方法。

二、Session 緩存

1.簡介

(1)Session 有一個緩存,稱爲 Hibernate 一級緩存。位於緩存中的對象稱爲持久化對象,每一個持久化對象與數據庫中的一條記錄對應。

(2)站在持久化的角度,Hibernate 將對象分爲 4 種狀態:臨時狀態、持久化狀態、遊離狀態、刪除狀態。

2.測試 Session 緩存

(1)準備

①hibernate.cfg.xml 文件請參看上一篇文章。

②SessionFactory、Session、Transaction

複製代碼
複製代碼
private SessionFactory sessionFactory;
private Session session;
private Transaction transaction;

@Before
public void init() {
Configuration configuration = new Configuration().configure();
ServiceRegistry serviceRegistry = new ServiceRegistryBuilder().applySettings(configuration.getProperties()).buildServiceRegistry();
sessionFactory = configuration.buildSessionFactory(serviceRegistry);
session = sessionFactory.openSession();
transaction = session.beginTransaction();
}

@After
public void destroy() {
transaction.commit();
session.close();
sessionFactory.close();
}
複製代碼
複製代碼
說明:使用單元測試類進行測試。因爲是測試環境,不存在併發的情況,創建了一個 Session 對象。

(2)測試

複製代碼br/>複製代碼
@Test
public void testSession() {
News news = (News) session.get(News.class, 1);
System.out.println(news);

News news2 = (News) session.get(News.class, 1);
System.out.println(news2);

System.out.println(news.equals(news2));

}
複製代碼
複製代碼
測試結果:

複製代碼
複製代碼
Hibernate:
select
news0_.id as id1_00,
news0_.title as title2_00,
news0_.author as author3_00,
news0_.date as date4_00
from
hibernate.news news0
where
news0
.id=?
News{id=1, title='Title', author='tom', date=2016-09-28}
News{id=1, title='Title', author='tom', date=2016-09-28}
true
複製代碼
複製代碼
說明:

第一次查詢的時候,會將引用賦值給 news,同時向 Session 緩存中存入了一份。

第二次查詢的時候,並沒有發送 select 語句,而是從 Session 緩存中直接獲取的。

3.操縱 Session 緩存
Hibernate 的一級緩存

(1)flush() :使數據表中的記錄和 Session 緩存中的對象的狀態保持一致。

① 在 Transaction 的 commit() 方法中,先調用 session 的 flush 方法,再提交事務。

org.hibernate.engine.transaction.spi.AbstractTransactionImpl#commit

複製代碼br/>複製代碼
@Override
public void commit() throws HibernateException {
if ( localStatus != LocalStatus.ACTIVE ) {
throw new TransactionException( "Transaction not successfully started" );
}

LOG.debug( "committing" );

beforeTransactionCommit();

try {
    doCommit();
    localStatus = LocalStatus.COMMITTED;
    afterTransactionCompletion( Status.STATUS_COMMITTED );
}
catch ( Exception e ) {
    localStatus = LocalStatus.FAILED_COMMIT;
    afterTransactionCompletion( Status.STATUS_UNKNOWN );
    throw new TransactionException( "commit failed", e );
}
finally {
    invalidate();
    afterAfterCompletion();
}

}
複製代碼
複製代碼
org.hibernate.engine.transaction.internal.jdbc.JdbcTransaction#beforeTransactionCommit

複製代碼
複製代碼
protected void beforeTransactionCommit() {
this.transactionCoordinator().sendBeforeTransactionCompletionNotifications(this);
if(this.isDriver && !this.transactionCoordinator().getTransactionContext().isFlushModeNever()) {
this.transactionCoordinator().getTransactionContext().managedFlush();
}

if(this.isDriver) {
    this.transactionCoordinator().getTransactionContext().beforeTransactionCompletion(this);
}

}
複製代碼
複製代碼
② 可能會打印 SQL 語句,但是不會提交事務。

③ 在未提交事務或顯式的調用 flush() 方法前,也可能會進行 flush() 操作。

執行 HQL 或 QBC 查詢,會先進行 flush() 操作,以得到數據表的最新記錄。
若記錄的 ID 是由數據庫使用的自增的方式生成的,則在調用 save() 方法時,就會立即發送 INSERT 語句,因爲 save 方法後,必須保證對象的 ID 存在。
(2)refresh():會強制發送 SELECT 語句,以使 Session 緩存中對象的狀態和數據表中對應的記錄保持一致。

複製代碼
複製代碼
1 @Test
2 public void testRefresh() {
3 News news = (News) session.get(News.class, 1);
4 System.out.println(news);
5 session.refresh(news);
6 System.out.println(news);
7 }
複製代碼
複製代碼
我在第5行斷點,然後修改數據庫中 News 的 author 字段,改爲 jerry。執行。

兩次打印結果相同。

View Code
原因:數據庫的隔離級別,Mysql 默認隔離級別爲 REPEATABLE READ。

在 Hibernate 的配置文件中可以顯式的設置隔離級別. 每一個隔離級別都對應一個整數:

  1. READ UNCOMMITED

  2. READ COMMITED

  3. REPEATABLE READ

  4. SERIALIZEABLE

Hibernate 通過爲 Hibernate 映射文件指定 hibernate.connection.isolation 屬性來設置事務的隔離級別。
修改後的打印結果:

兩次打印結果不同。

View Code
(3)clear():清理緩存。

@Test
public void testClear() {
session.get(News.class, 1);
session.clear();
session.get(News.class, 1);
}
輸出結果:

View Code
三、Session API

1.四種狀態的轉換圖
Hibernate 的一級緩存

(1)臨時對象

在使用代理主鍵的情況下,OID 通常爲 null
不處於 Session 的緩存中
在數據庫中沒有對應的記錄
(2)持久化對象

OID 不爲空
位於 Session 緩存中
在同一個 Session 實例的緩存中,數據庫表中的每條記錄只對應唯一的持久化對象
(3)遊離對象

OID 不爲空
不處於 Session 緩存中
(4)刪除對象

在數據庫中沒有和其 OID 對應的記錄
不再處於 Session 緩存中
2.save()

(1)將一個臨時對象轉變爲持久化對象

(2)爲對象分配 ID

(3)在 flush 緩存的時候,計劃執行一條 INSERT 語句

(4)在 save() 方法前的 id 是無效的

(5)持久化對象的 ID 是不能被更改的。因爲 Hibernate 通過持久化對象的 OID 來維持它與數據庫相關記錄的對應關係。

  • persist() 和 save() 區別

對一個 OID 不爲 Null 的對象執行 save() 方法時,會把該對象以一個新的 OID 保存到數據庫中,而 persist() 則會拋出一個異常。

3.get()/load()

(1)都可以根據 OID 從數據庫中加載一個持久化對象。

(2)執行 get() 時會立即加載對象。執行 load() ,若不使用該對象,則不會立即執行查詢操作,而是返回一個代理對象。

(3)get() 是立即檢索,而 load() 是延遲檢索。

(4)若數據表中沒有對應記錄,Session 也沒有被關閉。get() 返回 null,load() 使用返回對象時拋出異常。

(5)load() 可能會拋出 LozyInitizationException 異常:在需要初始化代理對象之前已經關閉了 Session。

@Test
public void testLoad() {
News news = (News) session.load(News.class, 1);
session.close();
System.out.println(news);
}
org.hibernate.LazyInitializationException: could not initialize proxy - no Session
4.update()

(1)將一個遊離對象轉變爲持久化對象,並且計劃執行一條 update 語句。

(2)若更新一個持久化對象,不需要顯式的調用 update() 方法。因爲在調用 Transaction 的 commit() 方法時,會先執行 session 的 flush() 方法。

(3)注意

無論要更新的遊離對象和數據表的記錄是否一致,都會發送 UPADATE 語句。如何讓只在不一致的情況下發送 UPDATE 語句?在 entity.hbm.xml 文件的 class 節點設置 select-before-update=true(默認爲 false)。通常不需要設置,與觸發器協同工作時需要注意。
若數據表中沒有對應的記錄,但還是調用了 update() 方法,會拋出異常。
當 update() 方法關聯一個遊離對象時,如果在 Session 緩存中已經存在相同 OID 的持久化對象,會拋出異常。因爲在 Session 緩存中不能有兩個 OID 相同的對象。
5.saveOrUpdate()

(1)同時包含了 save() 和 update() 方法的功能。

(2)判斷是否是遊離對象還是臨時對象是根據 對象的 OID 來判定的。若爲 null ,則執行 save() ,若不爲 null,則判定爲遊離對象,執行 update() 。

(3)若 OID 不爲 null,但數據中還沒有與之對應的記錄,則會拋出一個異常。

(4)瞭解:OID 值等於 id 的 unsaved-value 屬性值的對象,也被認爲是一個遊離對象。

6.delete()

(1)既可以刪除一個遊離對象,也可以刪除一個持久化對象。

(2)只要 OID 和數據表中一條記錄對應,就會準備執行 delete 操作,若 OID 在數據表中沒有對應的記錄,則拋出異常。

(3)在執行 delete() 後,還是可以獲取到對象的 OID,防止對該對象的其他持久化操作,可以通過設置 hibernate 配置文件的 hibernate.use_identifier_rollback 爲 true,

使刪除對象後,把其 OID 值爲 null。

7.evict()

把指定持久化對象從 session 緩存中移除。

8.調用存儲過程

複製代碼br/>複製代碼
@Test
public void testWork() {
session.doWork(new Work() {
br/>@Override
public void execute(Connection connection) throws SQLException {
System.out.println(connection);
// 調用存儲過程
}
});
}
複製代碼
複製代碼
四、總結

介紹了 Hibernate 的一級緩存,包括如何操縱 Session 的緩存,以及四種狀態之間的轉換,以及建立在 Session 緩存和四種狀態基礎上的 Session API。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章