Hibernate —— Session

1、概述sql

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

2、Session 緩存緩存

1.簡介session

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

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

2.測試 Session 緩存ide

(1)準備單元測試

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

②SessionFactory、Session、Transactionui

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)測試

@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_0_0_,
        news0_.title as title2_0_0_,
        news0_.author as author3_0_0_,
        news0_.date as date4_0_0_ 
    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 緩存

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

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

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

@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。執行。

兩次打印結果相同。

Hibernate: 
    select
        news0_.id as id1_0_0_,
        news0_.title as title2_0_0_,
        news0_.author as author3_0_0_,
        news0_.date as date4_0_0_ 
    from
        hibernate.news news0_ 
    where
        news0_.id=?
News{id=1, title='Title', author='tom', date=2016-09-28}
Hibernate: 
    select
        news0_.id as id1_0_0_,
        news0_.title as title2_0_0_,
        news0_.author as author3_0_0_,
        news0_.date as date4_0_0_ 
    from
        hibernate.news news0_ 
    where
        news0_.id=?
News{id=1, title='Title', author='tom', date=2016-09-28}
View Code

緣由:數據庫的隔離級別,Mysql 默認隔離級別爲 REPEATABLE READ。

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

1. READ UNCOMMITED

2. READ COMMITED

4. REPEATABLE READ

8. SERIALIZEABLE

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

兩次打印結果不一樣。

Hibernate: 
    select
        news0_.id as id1_0_0_,
        news0_.title as title2_0_0_,
        news0_.author as author3_0_0_,
        news0_.date as date4_0_0_ 
    from
        hibernate.news news0_ 
    where
        news0_.id=?
News{id=1, title='Title', author='jerry', date=2016-09-28}
Hibernate: 
    select
        news0_.id as id1_0_0_,
        news0_.title as title2_0_0_,
        news0_.author as author3_0_0_,
        news0_.date as date4_0_0_ 
    from
        hibernate.news news0_ 
    where
        news0_.id=?
News{id=1, title='Title', author='tom', date=2016-09-28}
View Code

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

@Test
public void testClear() {
    session.get(News.class, 1);
    session.clear();
    session.get(News.class, 1);
}

輸出結果:

Hibernate: 
    select
        news0_.id as id1_0_0_,
        news0_.title as title2_0_0_,
        news0_.author as author3_0_0_,
        news0_.date as date4_0_0_ 
    from
        hibernate.news news0_ 
    where
        news0_.id=?
Hibernate: 
    select
        news0_.id as id1_0_0_,
        news0_.title as title2_0_0_,
        news0_.author as author3_0_0_,
        news0_.date as date4_0_0_ 
    from
        hibernate.news news0_ 
    where
        news0_.id=?
View Code

3、Session API

1.四種狀態的轉換圖

(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.調用存儲過程

@Test
public void testWork() {
    session.doWork(new Work() {
        @Override
        public void execute(Connection connection) throws SQLException {
            System.out.println(connection);
            // 調用存儲過程
        }
    });
}

4、總結

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

相關文章
相關標籤/搜索