版權聲明:轉載時請以超連接形式標明文章原始出處和做者信息及本聲明
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中開啓二級緩存再次運行代碼便可