在本篇隨筆裏將會分析一下hibernate的緩存機制,包括一級緩存(session級別)、二級緩存(sessionFactory級別)以及查詢緩存,固然還要討論下咱們的N+1的問題。java
隨筆雖長,但我相信看完的朋友絕對能對hibernate的 N+1問題以及緩存有更深的瞭解。sql
1、N+1問題數據庫
首先咱們來探討一下N+1的問題,咱們先經過一個例子來看一下,什麼是N+1問題:緩存
list()得到對象:session
/** * 此時會發出一條sql,將30個學生所有查詢出來 */ List<Student> ls = (List<Student>)session.createQuery("from Student") .setFirstResult(0).setMaxResults(30).list(); Iterator<Student> stus = ls.iterator(); for(;stus.hasNext();) { Student stu = (Student)stus.next(); System.out.println(stu.getName()); }
若是經過list()方法來得到對象,毫無疑問,hibernate會發出一條sql語句,將全部的對象查詢出來,這點相信你們都能理解app
Hibernate: select student0_.id as id2_, student0_.name as name2_, student0_.rid as rid2_, student0_.sex as sex2_ from t_student student0_ limit ?
那麼,咱們再來看看iterator()這種狀況ide
iterator()得到對象性能
/** * 若是使用iterator方法返回列表,對於hibernate而言,它僅僅只是發出取id列表的sql * 在查詢相應的具體的某個學生信息時,會發出相應的SQL去取學生信息 * 這就是典型的N+1問題 * 存在iterator的緣由是,有可能會在一個session中查詢兩次數據,若是使用list每一次都會把全部的對象查詢上來 * 而是要iterator僅僅只會查詢id,此時全部的對象已經存儲在一級緩存(session的緩存)中,能夠直接獲取 */ Iterator<Student> stus = (Iterator<Student>)session.createQuery("from Student") .setFirstResult(0).setMaxResults(30).iterate(); for(;stus.hasNext();) { Student stu = (Student)stus.next(); System.out.println(stu.getName()); }
在執行完上述的測試用例後,咱們來看看控制檯的輸出,看會發出多少條 sql 語句:測試
Hibernate: select student0_.id as col_0_0_ from t_student student0_ limit ? Hibernate: select student0_.id as id2_0_, student0_.name as name2_0_, student0_.rid as rid2_0_, student0_.sex as sex2_0_ from t_student student0_ where student0_.id=? 沈凡 Hibernate: select student0_.id as id2_0_, student0_.name as name2_0_, student0_.rid as rid2_0_, student0_.sex as sex2_0_ from t_student student0_ where student0_.id=? 王志名 Hibernate: select student0_.id as id2_0_, student0_.name as name2_0_, student0_.rid as rid2_0_, student0_.sex as sex2_0_ from t_student student0_ where student0_.id=? 葉敦 .........
咱們看到,當若是經過iterator()方法來得到咱們對象的時候,hibernate首先會發出1條sql去查詢出全部對象的 id 值,當咱們若是須要查詢到某個對象的具體信息的時候,hibernate此時會根據查詢出來的 id 值再發sql語句去從數據庫中查詢對象的信息,這就是典型的 N+1 的問題。fetch
那麼這種 N+1 問題咱們如何解決呢,其實咱們只須要使用 list() 方法來得到對象便可。可是既然能夠經過 list() 咱們就不會出現 N+1的問題,那麼咱們爲何還要保留 iterator()這種形式呢?咱們考慮這樣一種狀況,若是咱們須要在一個session當中要兩次查詢出不少對象,此時咱們若是寫兩條 list()時,hibernate此時會發出兩條 sql 語句,並且這兩條語句是同樣的,可是咱們若是第一條語句使用 list(),而第二條語句使用 iterator()的話,此時咱們也會發兩條sql語句,可是第二條語句只會將查詢出對象的id,因此相對應取出全部的對象而已,顯然這樣能夠節省內存,而若是再要獲取對象的時候,由於第一條語句已經將對象都查詢出來了,此時會將對象保存到session的一級緩存中去,因此再次查詢時,就會首先去緩存中查找,若是找到,則不發sql語句了。這裏就牽涉到了接下來這個概念:hibernate的一級緩存。
2、一級緩存(session級別)
咱們來看看hibernate提供的一級緩存:
/** * 此時會發出一條sql,將全部學生所有查詢出來,並放到session的一級緩存當中 * 當再次查詢學生信息時,會首先去緩存中看是否存在,若是不存在,再去數據庫中查詢 * 這就是hibernate的一級緩存(session緩存) */ List<Student> stus = (List<Student>)session.createQuery("from Student") .setFirstResult(0).setMaxResults(30).list(); Student stu = (Student)session.load(Student.class, 1);
咱們來看看控制檯輸出:
Hibernate: select student0_.id as id2_, student0_.name as name2_, student0_.rid as rid2_, student0_.sex as sex2_ from t_student student0_ limit ?
咱們看到此時hibernate僅僅只會發出一條 sql 語句,由於第一行代碼就會將整個的對象查詢出來,放到session的一級緩存中去,當我若是須要再次查詢學生對象時,此時首先會去緩存中看是否存在該對象,若是存在,則直接從緩存中取出,就不會再發sql了,可是要注意一點:hibernate的一級緩存是session級別的,因此若是session關閉後,緩存就沒了,此時就會再次發sql去查數據庫。
try { session = HibernateUtil.openSession(); /** * 此時會發出一條sql,將全部學生所有查詢出來,並放到session的一級緩存當中 * 當再次查詢學生信息時,會首先去緩存中看是否存在,若是不存在,再去數據庫中查詢 * 這就是hibernate的一級緩存(session緩存) */ List<Student> stus = (List<Student>)session.createQuery("from Student") .setFirstResult(0).setMaxResults(30).list(); Student stu = (Student)session.load(Student.class, 1); System.out.println(stu.getName() + "-----------"); } catch (Exception e) { e.printStackTrace(); } finally { HibernateUtil.close(session); } /** * 當session關閉之後,session的一級緩存也就沒有了,這時就又會去數據庫中查詢 */ session = HibernateUtil.openSession(); Student stu = (Student)session.load(Student.class, 1); System.out.println(stu.getName() + "-----------");
Hibernate: select student0_.id as id2_, student0_.name as name2_, student0_.sex as sex2_, student0_.rid as rid2_ from t_student student0_ limit ? Hibernate: select student0_.id as id2_2_, student0_.name as name2_2_, student0_.sex as sex2_2_, student0_.rid as rid2_2_, classroom1_.id as id1_0_, classroom1_.name as name1_0_, classroom1_.sid as sid1_0_, special2_.id as id0_1_, special2_.name as name0_1_, special2_.type as type0_1_ from t_student student0_ left outer join t_classroom classroom1_ on student0_.rid=classroom1_.id left outer join t_special special2_ on classroom1_.sid=special2_.id where student0_.id=?
咱們看到此時會發出兩條sql語句,由於session關閉之後,一級緩存就不存在了,因此若是再查詢的時候,就會再發sql。要解決這種問題,咱們應該怎麼作呢?這就要咱們來配置hibernate的二級緩存了,也就是sessionFactory級別的緩存。
3、二級緩存(sessionFactory級別)
使用hibernate二級緩存,咱們首先須要對其進行配置,配置步驟以下:
1.hibernate並無提供相應的二級緩存的組件,因此須要加入額外的二級緩存包,經常使用的二級緩存包是EHcache。這個咱們在下載好的hibernate的lib->optional->ehcache下能夠找到(我這裏使用的hibernate4.1.7版本),而後將裏面的幾個jar包導入便可。
2.在hibernate.cfg.xml配置文件中配置咱們二級緩存的一些屬性:
<!-- 開啓二級緩存 --> <property name="hibernate.cache.use_second_level_cache">true</property> <!-- 二級緩存的提供類 在hibernate4.0版本之後咱們都是配置這個屬性來指定二級緩存的提供類--> <property name="hibernate.cache.region.factory_class">org.hibernate.cache.ehcache.EhCacheRegionFactory</property> <!-- 二級緩存配置文件的位置 --> <property name="hibernate.cache.provider_configuration_file_resource_path">ehcache.xml</property>
我這裏使用的是hibernate4.1.7版本,若是是使用hibernate3的版本的話,那麼二級緩存的提供類則要配置成這個:
<!--這個類在4.0版本之後已經不建議被使用了--> <property name="hibernate.cache.provider_class">net.sf.ehcache.hibernate.EhCacheProvider</property>
3.配置hibernate的二級緩存是經過使用 ehcache的緩存包,因此咱們須要建立一個 ehcache.xml 的配置文件,來配置咱們的緩存信息,將其放到項目根目錄下
<ehcache> <!-- Sets the path to the directory where cache .data files are created. If the path is a Java System Property it is replaced by its value in the running VM. The following properties are translated: user.home - User's home directory user.dir - User's current working directory java.io.tmpdir - Default temp file path --> <!--指定二級緩存存放在磁盤上的位置--> <diskStore path="user.dir"/> <!--咱們能夠給每一個實體類指定一個對應的緩存,若是沒有匹配到該類,則使用這個默認的緩存配置--> <defaultCache maxElementsInMemory="10000" //在內存中存放的最大對象數 eternal="false" //是否永久保存緩存,設置成false timeToIdleSeconds="120" timeToLiveSeconds="120" overflowToDisk="true" //若是對象數量超過內存中最大的數,是否將其保存到磁盤中,設置成true /> <!--
一、timeToLiveSeconds的定義是:以建立時間爲基準開始計算的超時時長;
二、timeToIdleSeconds的定義是:在建立時間和最近訪問時間中取出離如今最近的時間做爲基準計算的超時時長;
三、若是僅設置了timeToLiveSeconds,則該對象的超時時間=建立時間+timeToLiveSeconds,假設爲A;
四、若是沒設置timeToLiveSeconds,則該對象的超時時間=max(建立時間,最近訪問時間)+timeToIdleSeconds,假設爲B;
五、若是二者都設置了,則取出A、B最少的值,即min(A,B),表示只要有一個超時成當即算超時。
--> <!--能夠給每一個實體類指定一個配置文件,經過name屬性指定,要使用類的全名--> <cache name="com.xiaoluo.bean.Student" maxElementsInMemory="10000" eternal="false" timeToIdleSeconds="300" timeToLiveSeconds="600" overflowToDisk="true" /> <cache name="sampleCache2" maxElementsInMemory="1000" eternal="true" timeToIdleSeconds="0" timeToLiveSeconds="0" overflowToDisk="false" /> --> </ehcache>
4.開啓咱們的二級緩存
①若是使用xml配置,咱們須要在 Student.hbm.xml 中加上一下配置:
<hibernate-mapping package="com.xiaoluo.bean"> <class name="Student" table="t_student"> <!-- 二級緩存通常設置爲只讀的 --> <cache usage="read-only"/> <id name="id" type="int" column="id"> <generator class="native"/> </id> <property name="name" column="name" type="string"></property> <property name="sex" column="sex" type="string"></property> <many-to-one name="room" column="rid" fetch="join"></many-to-one> </class> </hibernate-mapping>
二級緩存的使用策略通常有這幾種:read-only、nonstrict-read-write、read-write、transactional。注意:咱們一般使用二級緩存都是將其配置成 read-only ,即咱們應當在那些不須要進行修改的實體類上使用二級緩存,不然若是對緩存進行讀寫的話,性能會變差,這樣設置緩存就失去了意義。
②若是使用annotation配置,咱們須要在Student這個類上加上這樣一個註解:
@Entity @Table(name="t_student") @Cache(usage=CacheConcurrencyStrategy.READ_ONLY) // 表示開啓二級緩存,並使用read-only策略 public class Student { private int id; private String name; private String sex; private Classroom room; ....... }
這樣咱們的二級緩存配置就算完成了,接下來咱們來經過測試用例測試下咱們的二級緩存是否起做用
①二級緩存是sessionFactory級別的緩存
TestCase1:
public class TestSecondCache { @Test public void testCache1() { Session session = null; try { session = HibernateUtil.openSession(); Student stu = (Student) session.load(Student.class, 1); System.out.println(stu.getName() + "-----------"); } catch (Exception e) { e.printStackTrace(); } finally { HibernateUtil.close(session); } try { /** * 即便當session關閉之後,由於配置了二級緩存,而二級緩存是sessionFactory級別的,因此會從緩存中取出該數據 * 只會發出一條sql語句 */ session = HibernateUtil.openSession(); Student stu = (Student) session.load(Student.class, 1); System.out.println(stu.getName() + "-----------"); /** * 由於設置了二級緩存爲read-only,因此不能對其進行修改 */ session.beginTransaction(); stu.setName("aaa"); session.getTransaction().commit(); } catch (Exception e) { e.printStackTrace(); session.getTransaction().rollback(); } finally { HibernateUtil.close(session); } }
Hibernate: select student0_.id as id2_2_, student0_.name as name2_2_, student0_.sex as sex2_2_, student0_.rid as rid2_2_, classroom1_.id as id1_0_, classroom1_.name as name1_0_, classroom1_.sid as sid1_0_, special2_.id as id0_1_, special2_.name as name0_1_, special2_.type as type0_1_ from t_student student0_ left outer join t_classroom classroom1_ on student0_.rid=classroom1_.id left outer join t_special special2_ on classroom1_.sid=special2_.id where student0_.id=? aaa----------- aaa-----------
由於二級緩存是sessionFactory級別的緩存,咱們看到,在配置了二級緩存之後,當咱們session關閉之後,咱們再去查詢對象的時候,此時hibernate首先會去二級緩存中查詢是否有該對象,有就不會再發sql了。
②二級緩存緩存的僅僅是對象,若是查詢出來的是對象的一些屬性,則不會被加到緩存中去
TestCase2:
@Test public void testCache2() { Session session = null; try { session = HibernateUtil.openSession(); /** * 注意:二級緩存中緩存的僅僅是對象,而下面這裏只保存了姓名和性別兩個字段,因此 不會被加載到二級緩存裏面 */ List<Object[]> ls = (List<Object[]>) session .createQuery("select stu.name, stu.sex from Student stu") .setFirstResult(0).setMaxResults(30).list(); } catch (Exception e) { e.printStackTrace(); } finally { HibernateUtil.close(session); } try { /** * 因爲二級緩存緩存的是對象,因此此時會發出兩條sql */ session = HibernateUtil.openSession(); Student stu = (Student) session.load(Student.class, 1); System.out.println(stu); } catch (Exception e) { e.printStackTrace(); } }
Hibernate: select student0_.name as col_0_0_, student0_.sex as col_1_0_ from t_student student0_ limit ? Hibernate: select student0_.id as id2_2_, student0_.name as name2_2_, student0_.sex as sex2_2_, student0_.rid as rid2_2_, classroom1_.id as id1_0_, classroom1_.name as name1_0_, classroom1_.sid as sid1_0_, special2_.id as id0_1_, special2_.name as name0_1_, special2_.type as type0_1_ from t_student student0_ left outer join t_classroom classroom1_ on student0_.rid=classroom1_.id left outer join t_special special2_ on classroom1_.sid=special2_.id where student0_.id=?
咱們看到這個測試用例,若是咱們只是取出對象的一些屬性的話,則不會將其保存到二級緩存中去,由於二級緩存緩存的僅僅是對象。
③經過二級緩存來解決 N+1 的問題
TestCase3:
@Test public void testCache3() { Session session = null; try { session = HibernateUtil.openSession(); /** * 將查詢出來的Student對象緩存到二級緩存中去 */ List<Student> stus = (List<Student>) session.createQuery( "select stu from Student stu").list(); } catch (Exception e) { e.printStackTrace(); } finally { HibernateUtil.close(session); } try { /** * 因爲學生的對象已經緩存在二級緩存中了,此時再使用iterate來獲取對象的時候,首先會經過一條 * 取id的語句,而後在獲取對象時去二級緩存中,若是發現就不會再發SQL,這樣也就解決了N+1問題 * 並且內存佔用也很少 */ session = HibernateUtil.openSession(); Iterator<Student> iterator = session.createQuery("from Student") .iterate(); for (; iterator.hasNext();) { Student stu = (Student) iterator.next(); System.out.println(stu.getName()); } } catch (Exception e) { e.printStackTrace(); } }
當咱們若是須要查詢出兩次對象的時候,可使用二級緩存來解決N+1的問題。
④二級緩存會緩存 hql 語句嗎?
TestCase4:
@Test public void testCache4() { Session session = null; try { session = HibernateUtil.openSession(); List<Student> ls = session.createQuery("from Student") .setFirstResult(0).setMaxResults(50).list(); } catch (Exception e) { e.printStackTrace(); } finally { HibernateUtil.close(session); } try { /** * 使用List會發出兩條如出一轍的sql,此時若是但願不發sql就須要使用查詢緩存 */ session = HibernateUtil.openSession(); List<Student> ls = session.createQuery("from Student") .setFirstResult(0).setMaxResults(50).list(); Iterator<Student> stu = ls.iterator(); for(;stu.hasNext();) { Student student = stu.next(); System.out.println(student.getName()); } } catch (Exception e) { e.printStackTrace(); } finally { HibernateUtil.close(session); } }
Hibernate: select student0_.id as id2_, student0_.name as name2_, student0_.sex as sex2_, student0_.rid as rid2_ from t_student student0_ limit ? Hibernate: select student0_.id as id2_, student0_.name as name2_, student0_.sex as sex2_, student0_.rid as rid2_ from t_student student0_ limit ?
咱們看到,當咱們若是經過 list() 去查詢兩次對象時,二級緩存雖然會緩存查詢出來的對象,可是咱們看到發出了兩條相同的查詢語句,這是由於二級緩存不會緩存咱們的hql查詢語句,要想解決這個問題,咱們就要配置咱們的查詢緩存了。
4、查詢緩存(sessionFactory級別)
咱們若是要配置查詢緩存,只須要在hibernate.cfg.xml中加入一條配置便可:
<!-- 開啓查詢緩存 --> <property name="hibernate.cache.use_query_cache">true</property>
而後咱們若是在查詢hql語句時要使用查詢緩存,就須要在查詢語句後面設置這樣一個方法:
List<Student> ls = session.createQuery("from Student where name like ?") .setCacheable(true) //開啓查詢緩存,查詢緩存也是SessionFactory級別的緩存 .setParameter(0, "%王%") .setFirstResult(0).setMaxResults(50).list();
若是是在annotation中,咱們還須要在這個類上加上這樣一個註解:@Cacheable
接下來咱們來經過測試用例來看看咱們的查詢緩存
①查詢緩存也是sessionFactory級別的緩存
TestCase1:
@Test public void test2() { Session session = null; try { /** * 此時會發出一條sql取出全部的學生信息 */ session = HibernateUtil.openSession(); List<Student> ls = session.createQuery("from Student") .setCacheable(true) //開啓查詢緩存,查詢緩存也是sessionFactory級別的緩存 .setFirstResult(0).setMaxResults(50).list(); Iterator<Student> stus = ls.iterator(); for(;stus.hasNext();) { Student stu = stus.next(); System.out.println(stu.getName()); } } catch (Exception e) { e.printStackTrace(); } finally { HibernateUtil.close(session); } try { /** * 此時會發出一條sql取出全部的學生信息 */ session = HibernateUtil.openSession(); List<Student> ls = session.createQuery("from Student") .setCacheable(true) //開啓查詢緩存,查詢緩存也是sessionFactory級別的緩存 .setFirstResult(0).setMaxResults(50).list(); Iterator<Student> stus = ls.iterator(); for(;stus.hasNext();) { Student stu = stus.next(); System.out.println(stu.getName()); } } catch (Exception e) { e.printStackTrace(); } finally { HibernateUtil.close(session); } }
Hibernate: select student0_.id as id2_, student0_.name as name2_, student0_.sex as sex2_, student0_.rid as rid2_ from t_student student0_ limit ?
咱們看到,此時若是咱們發出兩條相同的語句,hibernate也只會發出一條sql,由於已經開啓了查詢緩存了,而且查詢緩存也是sessionFactory級別的
②只有當 HQL 查詢語句徹底相同時,連參數設置都要相同,此時查詢緩存纔有效
TestCase2:
@Test public void test3() { Session session = null; try { /** * 此時會發出一條sql取出全部的學生信息 */ session = HibernateUtil.openSession(); List<Student> ls = session.createQuery("from Student where name like ?") .setCacheable(true)//開啓查詢緩存,查詢緩存也是SessionFactory級別的緩存 .setParameter(0, "%王%") .setFirstResult(0).setMaxResults(50).list(); Iterator<Student> stus = ls.iterator(); for(;stus.hasNext();) { Student stu = stus.next(); System.out.println(stu.getName()); } } catch (Exception e) { e.printStackTrace(); } finally { HibernateUtil.close(session); } session = null; try { /** * 此時會發出一條sql取出全部的學生信息 */ session = HibernateUtil.openSession(); /** * 只有當HQL徹底相同的時候,連參數都要相同,查詢緩存纔有效 */ // List<Student> ls = session.createQuery("from Student where name like ?") // .setCacheable(true)//開啓查詢緩存,查詢緩存也是SessionFactory級別的緩存 // .setParameter(0, "%王%") // .setFirstResult(0).setMaxResults(50).list(); List<Student> ls = session.createQuery("from Student where name like ?") .setCacheable(true)//開啓查詢緩存,查詢緩存也是SessionFactory級別的緩存 .setParameter(0, "%張%") .setFirstResult(0).setMaxResults(50).list(); Iterator<Student> stus = ls.iterator(); for(;stus.hasNext();) { Student stu = stus.next(); System.out.println(stu.getName()); } } catch (Exception e) { e.printStackTrace(); } finally { HibernateUtil.close(session); } }
Hibernate: select student0_.id as id2_, student0_.name as name2_, student0_.sex as sex2_, student0_.rid as rid2_ from t_student student0_ where student0_.name like ? limit ? Hibernate: select student0_.id as id2_, student0_.name as name2_, student0_.sex as sex2_, student0_.rid as rid2_ from t_student student0_ where student0_.name like ? limit ?
咱們看到,若是咱們的hql查詢語句不一樣的話,咱們的查詢緩存也沒有做用
③查詢緩存也能引發 N+1 的問題
查詢緩存也能引發 N+1 的問題,咱們這裏首先先將 Student 對象上的二級緩存先註釋掉:
<!-- 二級緩存通常設置爲只讀的 --> <!-- <cache usage="read-only"/> -->
TestCase4:
@Test public void test4() { Session session = null; try { /** * 查詢緩存緩存的不是對象而是id */ session = HibernateUtil.openSession(); List<Student> ls = session.createQuery("from Student where name like ?") .setCacheable(true)//開啓查詢緩存,查詢緩存也是SessionFactory級別的緩存 .setParameter(0, "%王%") .setFirstResult(0).setMaxResults(50).list(); Iterator<Student> stus = ls.iterator(); for(;stus.hasNext();) { Student stu = stus.next(); System.out.println(stu.getName()); } } catch (Exception e) { e.printStackTrace(); } finally { HibernateUtil.close(session); } session = null; try { /** * 查詢緩存緩存的是id,此時因爲在緩存中已經存在了這樣的一組學生數據,可是僅僅只是緩存了 * id,因此此處會發出大量的sql語句根據id取對象,這也是發現N+1問題的第二個緣由 * 因此若是使用查詢緩存必須開啓二級緩存 */ session = HibernateUtil.openSession(); List<Student> ls = session.createQuery("from Student where name like ?") .setCacheable(true)//開啓查詢緩存,查詢緩存也是SessionFactory級別的緩存 .setParameter(0, "%王%") .setFirstResult(0).setMaxResults(50).list(); Iterator<Student> stus = ls.iterator(); for(;stus.hasNext();) { Student stu = stus.next(); System.out.println(stu.getName()); } } catch (Exception e) { e.printStackTrace(); } finally { HibernateUtil.close(session); } }
Hibernate: select student0_.id as id2_, student0_.name as name2_, student0_.sex as sex2_, student0_.rid as rid2_ from t_student student0_ where student0_.name like ? limit ? Hibernate: select student0_.id as id2_2_, student0_.name as name2_2_, student0_.sex as sex2_2_, student0_.rid as rid2_2_, classroom1_.id as id1_0_, classroom1_.name as name1_0_, classroom1_.sid as sid1_0_, special2_.id as id0_1_, special2_.name as name0_1_, special2_.type as type0_1_ from t_student student0_ left outer join t_classroom classroom1_ on student0_.rid=classroom1_.id left outer join t_special special2_ on classroom1_.sid=special2_.id where student0_.id=? Hibernate: select student0_.id as id2_2_, student0_.name as name2_2_, student0_.sex as sex2_2_, student0_.rid as rid2_2_, classroom1_.id as id1_0_, classroom1_.name as name1_0_, classroom1_.sid as sid1_0_, special2_.id as id0_1_, special2_.name as name0_1_, special2_.type as type0_1_ from t_student student0_ left outer join t_classroom classroom1_ on student0_.rid=classroom1_.id left outer join t_special special2_ on classroom1_.sid=special2_.id where student0_.id=? Hibernate: select student0_.id as id2_2_, student0_.name as name2_2_, student0_.sex as sex2_2_, student0_.rid as rid2_2_, classroom1_.id as id1_0_, classroom1_.name as name1_0_, classroom1_.sid as sid1_0_, special2_.id as id0_1_, special2_.name as name0_1_, special2_.type as type0_1_ from t_student student0_ left outer join t_classroom classroom1_ on student0_.rid=classroom1_.id left outer join t_special special2_ on classroom1_.sid=special2_.id where student0_.id=? Hibernate: select student0_.id as id2_2_, student0_.name as name2_2_, student0_.sex as sex2_2_, student0_.rid as rid2_2_, classroom1_.id as id1_0_, classroom1_.name as name1_0_, classroom1_.sid as sid1_0_, special2_.id as id0_1_, special2_.name as name0_1_, special2_.type as type0_1_ from t_student student0_ left outer join t_classroom classroom1_ on student0_.rid=classroom1_.id left outer join t_special special2_ on classroom1_.sid=special2_.id where student0_.id=? .........................
咱們看到,當咱們將二級緩存註釋掉之後,在使用查詢緩存時,也會出現 N+1 的問題,爲何呢?
由於查詢緩存緩存的也僅僅是對象的id,因此第一條 sql 也是將對象的id都查詢出來,可是當咱們後面若是要獲得每一個對象的信息的時候,此時又會發sql語句去查詢,因此,若是要使用查詢緩存,咱們必定也要開啓咱們的二級緩存,這樣就不會出現 N+1 問題了
好了,整篇隨筆大概花費了2個小時來編寫,能夠說將hibernate的 N+1 問題、一級緩存、二級緩存、查詢緩存的概念以及可能出現的問題都分析了透,但願能對你們提供幫助!