Hibernate get load 區別 誤區

版權聲明:轉載時請以超連接形式標明文章原始出處和做者信息及本聲明
http://www.blogbus.com/fallenlord-logs/57543854.html

html

最近面試別人,正好出的筆試題中有道關於Hibernate延遲加載的問題,聊天過程當中發現不少人對Hibernate的延遲加載有些理解誤區,寫些東東在這裏,但願對你們有所幫助。java

首先是第一個誤區:延遲加載只能做用於關聯實體
看到這個是否是在想:非關聯實體延遲加載有什麼用?
爲了解答上面這個問題,咱們能夠先考慮另外一個問題:Hibernate Session的get和load方法有什麼區別?
若是你的回答是:當方法參數爲數據庫不存在的id時,get會返回null,load會拋出異常,那麼恭喜你,進入了第二個誤區
若是此時你還想補充一下:load會從緩存中取出數據而get不會,再次恭喜,進入第三個誤區面試

若是你在上面三個誤區中有一個踏入了,那麼我敢打賭,你必定是被網上那些半吊子的工程師們寫的博客給戕害了。。。。
此時是否是很憤怒?這些長久以來你牢記在心的Hibernate的特性原來都是浮雲。。。。sql

呵呵,接下來咱們一個個來走出這些誤區。
Mop上無圖無真相,咱們這裏無碼無真相——不要誤會,我是說代碼數據庫

首先看看第二個誤區:當方法參數爲數據庫不存在的id時,get會返回null,load會拋出異常
若是你如今想說:沒錯啊,我本身就測試過,get確實返回了null,load確實拋出了異常。
那麼請回答:load是在執行load語句時拋出異常的嗎?爲何?若是你答不上來,那麼接着看下面的代碼吧:緩存

@Test(expected = IllegalArgumentException.class)
public void 延遲加載() throws Exception {
    // 啓動
    Session session = sessionFactory.openSession();
    Transaction tx = session.beginTransaction();
    User user = (User)session.load(User.class, 100L);  // 不存在的ID
    try {
        user.getName();
    } catch (ObjectNotFoundException ex) {
        // 命中數據庫發現沒有對象即拋出ObjectNotFoundException異常
        throw new IllegalArgumentException("隨便拋出一個不可能的異常");
    }
    tx.commit();
    session.close();
}

由這個test case咱們能夠知道load並非在執行時就立刻拋出不存在數據的異常的(ObjectNotFoundException),這是爲何呢?再看代碼:session

@Test(expected = IllegalArgumentException.class)
public void 延遲加載() throws Exception {
    // 啓動
    Session session = sessionFactory.openSession();
    Transaction tx = session.beginTransaction();
    User user = (User)session.load(User.class, 100L);  // 不存在的ID
    Assert.assertTrue(user instanceof HibernateProxy);
    user.getId();  // 因爲ID是不被延遲加載的屬性,所以不會拋出異常
    try {
        Hibernate.initialize(user);  // 此時纔會觸發命中數據庫
        //user.getName();
    } catch (ObjectNotFoundException ex) {
        // 命中數據庫發現沒有對象即拋出ObjectNotFoundException異常
        throw new IllegalArgumentException("隨便拋出一個不可能的異常");
    }
    tx.commit();
    session.close();
}

看高亮的幾行,代碼已經把問題說得很清楚了,get和load最大的區別是(假設緩存皆空的狀況):get是當即命中數據庫去查詢這條記錄,而load則是直接返回一個代理對象(HibernateProxy)而不命中數據庫,換句話來講load是爲單個對象進行了延遲加載,若是你不去訪問這個對象的除ID外的屬性,即便目標記錄不存在它也永遠都不會拋出異常。因爲load不當即命中數據庫,它確實有必定概率提升效率測試

OK,我想上面一段話應該能夠解釋第一和第二個誤區了,那麼第三個誤區呢?
再看代碼spa

@Test
public void get和load一級緩存測試() throws Exception {
    // 啓動
    Session session = sessionFactory.openSession();
    Transaction tx = session.beginTransaction();
    // 驗證load在緩存爲空的狀況下是否會使得加載的對象過一級緩存
    User user1 = (User)session.load(User.class, 1L);  // 存在的ID,此時雖然沒有解開Proxy但已經進入緩存
    Assert.assertTrue(user1 instanceof HibernateProxy);
    Hibernate.initialize(user1);  // 解開Proxy,會觸發命中數據庫操做
    User user3 = (User)session.get(User.class, 1L);
    Assert.assertTrue(user3 instanceof HibernateProxy);  // 即便使用get,但因爲緩存中存儲的是一個Proxy,因此這裏獲得的也是Proxy
    Hibernate.initialize(user3);  // 解開Proxy,但不會命中數據庫
    // 驗證在load一個不存在的ID後,不解開而後get
    User user4 = (User)session.load(User.class, 100L);  // 不存在的ID,仍然將Proxy進入緩存
    Assert.assertTrue(user4 instanceof HibernateProxy);
    //Hibernate.initialize(user3);  // 不解開Proxy
    try {
        session.get(User.class, 100L);  // 獲得Proxy,命中數據庫嘗試解開Proxy,因爲ID不存在所以拋出異常
        Assert.fail("ID不存在因此會出錯,不會執行本條");
    } catch (ObjectNotFoundException ex) {
    }
    // 清空緩存
    session.clear();
    // 驗證緩存爲空的狀況下get是否爲Proxy
    User user6 = (User)session.get(User.class, 1L);  // 命中數據庫,直接將組裝完成的User實體進入緩存
    Assert.assertTrue(!(user6 instanceof HibernateProxy));
    // 驗證get從緩存中取出對象
    User user7 = (User)session.get(User.class, 1L);
    Assert.assertTrue(!(user7 instanceof HibernateProxy)); // 緩存中是真實的User對象,get取出的就是真實的User對象
    // 驗證load是否從一級緩存取數據
    User user8 = (User)session.load(User.class, 1L);
    Assert.assertTrue(!(user8 instanceof HibernateProxy));  // 緩存中是真實的User對象,load取出的也是真實的User對象
    tx.commit();
    session.close();
}

相信註釋已經足夠詳細了,打開hibernate.show_sql,總共命中三次數據庫(執行SQL),分別在高亮的三行處,其他的全是從緩存中取數據。
並且值得注意的一點是,若是對象是從load加載到緩存中的,那麼不論get仍是load獲取出來的都是一個Proxy,若是沒有被解開過,那麼get會嘗試解開它;若是對象是從get加載到緩存中的,那麼load和get取出來都會是真實的實體對象。也就是說,get和load都會從緩存中取出對象,且取出的對象老是保持其第一次加載時的狀態(load爲Proxy,get爲真實對象)hibernate

以上代碼是一級緩存的驗證,想驗證二級緩存只須要從Hibernate中開啓二級緩存再次運行代碼便可

相關文章
相關標籤/搜索