hibernate 一级缓存、二级缓存

  1. hibernate的缓存机制,包括一级缓存(session级别)、二级缓存(sessionFactory级别)以及查询缓存,  
  2. 一、N+1问题  
  3. 首先我们来探讨一下N+1的问题,我们先通过一个例子来看一下,什么是N+1问题:  
  4.   
  5. list()获得对象:  
  6.   
  7.        /**  
  8.              * 此时会发出一条sql,将30个学生全部查询出来  
  9.              */  
  10.             List<Student> ls = (List<Student>)session.createQuery("from Student")  
  11.                                 .setFirstResult(0).setMaxResults(30).list();  
  12.             Iterator<Student> stus = ls.iterator();  
  13.             for(;stus.hasNext();)  
  14.             {  
  15.                 Student stu = (Student)stus.next();  
  16.                 System.out.println(stu.getName());  
  17.             }  
  18.   
  19. 如果通过list()方法来获得对象,毫无疑问,hibernate会发出一条sql语句,将所有的对象查询出来,这点相信大家都能理解  
  20.   
  21. Hibernate: select student0_.id as id2_, student0_.name as name2_, student0_.rid as rid2_, student0_.sex as sex2_ from t_student student0_ limit ?  
  22.   
  23. 那么,我们再来看看iterator()这种情况  
  24.   
  25. iterator()获得对象  
  26. 复制代码  
  27.   
  28.        /**  
  29.              * 如果使用iterator方法返回列表,对于hibernate而言,它仅仅只是发出取id列表的sql  
  30.              * 在查询相应的具体的某个学生信息时,会发出相应的SQL去取学生信息  
  31.              * 这就是典型的N+1问题  
  32.              * 存在iterator的原因是,有可能会在一个session中查询两次数据,如果使用list每一次都会把所有的对象查询上来  
  33.              * 而是要iterator仅仅只会查询id,此时所有的对象已经存储在一级缓存(session的缓存)中,可以直接获取  
  34.              */  
  35.             Iterator<Student> stus = (Iterator<Student>)session.createQuery("from Student")  
  36.                                 .setFirstResult(0).setMaxResults(30).iterate();  
  37.             for(;stus.hasNext();)  
  38.             {  
  39.                 Student stu = (Student)stus.next();  
  40.                 System.out.println(stu.getName());  
  41.             }  
  42.   
  43. 在执行完上述的测试用例后,我们来看看控制台的输出,看会发出多少条 sql 语句:  
  44.   
  45. Hibernate: select student0_.id as col_0_0_ from t_student student0_ limit ?  
  46. 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=?  
  47. 沈凡  
  48. 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=?  
  49. 王志名  
  50. 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=?  
  51. 叶敦  
  52. .........  
  53.   
  54. 我们看到,当如果通过iterator()方法来获得我们对象的时候,hibernate首先会发出1条sql去查询出所有对象的 id 值,当我们如果需要查询到某个对象的具体信息的时候,hibernate此时会根据查询出来的 id 值再发sql语句去从数据库中查询对象的信息,这就是典型的N+1 的问题。  
  55.   
  56. 那么这种 N+1 问题我们如何解决呢,其实我们只需要使用 list() 方法来获得对象即可。但是既然可以通过 list() 我们就不会出现 N+1的问题,那么我们为什么还要保留 iterator()这种形式呢?我们考虑这样一种情况,如果我们需要在一个session当中要两次查询出很多对象,此时我们如果写两条 list()时,hibernate此时会发出两条 sql 语句,而且这两条语句是一样的,但是我们如果第一条语句使用 list(),而第二条语句使用 iterator()的话,此时我们也会发两条sql语句,但是第二条语句只会将查询出对象的id,所以相对应取出所有的对象而已,显然这样可以节省内存,而如果再要获取对象的时候,因为第一条语句已经将对象都查询出来了,此时会将对象保存到session的一级缓存中去,所以再次查询时,就会首先去缓存中查找,如果找到,则不发sql语句了。这里就牵涉到了接下来这个概念:hibernate的一级缓存。  
  57.   
  58. 二、一级缓存(session级别)  
  59.   
  60. 我们来看看hibernate提供的一级缓存:  
  61.   
  62.        /**  
  63.              * 此时会发出一条sql,将所有学生全部查询出来,并放到session的一级缓存当中  
  64.              * 当再次查询学生信息时,会首先去缓存中看是否存在,如果不存在,再去数据库中查询  
  65.              * 这就是hibernate的一级缓存(session缓存)  
  66.              */  
  67.             List<Student> stus = (List<Student>)session.createQuery("from Student")  
  68.                                     .setFirstResult(0).setMaxResults(30).list();  
  69.             Student stu = (Student)session.load(Student.class, 1);  
  70.   
  71. 我们来看看控制台输出:  
  72.   
  73. Hibernate: select student0_.id as id2_, student0_.name as name2_, student0_.rid as rid2_, student0_.sex as sex2_ from t_student student0_ limit ?  
  74.   
  75. 我们看到此时hibernate仅仅只会发出一条 sql 语句,因为第一行代码就会将整个的对象查询出来,放到session的一级缓存中去,当我如果需要再次查询学生对象时,此时首先会去缓存中看是否存在该对象,如果存在,则直接从缓存中取出,就不会再发sql了,但是要注意一点:hibernate的一级缓存是session级别的,所以如果session关闭后,缓存就没了,此时就会再次发sql去查数据库。  
  76.   
  77.      try  
  78.         {  
  79.             session = HibernateUtil.openSession();  
  80.               
  81.             /**  
  82.              * 此时会发出一条sql,将所有学生全部查询出来,并放到session的一级缓存当中  
  83.              * 当再次查询学生信息时,会首先去缓存中看是否存在,如果不存在,再去数据库中查询  
  84.              * 这就是hibernate的一级缓存(session缓存)  
  85.              */  
  86.             List<Student> stus = (List<Student>)session.createQuery("from Student")  
  87.                                     .setFirstResult(0).setMaxResults(30).list();  
  88.             Student stu = (Student)session.load(Student.class, 1);  
  89.             System.out.println(stu.getName() + "-----------");  
  90.         }  
  91.         catch (Exception e)  
  92.         {  
  93.             e.printStackTrace();  
  94.         }  
  95.         finally  
  96.         {  
  97.             HibernateUtil.close(session);  
  98.         }  
  99.         /**  
  100.          * 当session关闭以后,session的一级缓存也就没有了,这时就又会去数据库中查询  
  101.          */  
  102.         session = HibernateUtil.openSession();  
  103.         Student stu = (Student)session.load(Student.class, 1);  
  104.         System.out.println(stu.getName() + "-----------");  
  105.   
  106. Hibernate: select student0_.id as id2_, student0_.name as name2_, student0_.sex as sex2_, student0_.rid as rid2_ from t_student student0_ limit ?  
  107.   
  108. 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=?  
  109.   
  110. 我们看到此时会发出两条sql语句,因为session关闭以后,一级缓存就不存在了,所以如果再查询的时候,就会再发sql。要解决这种问题,我们应该怎么做呢?这就要我们来配置hibernate的二级缓存了,也就是sessionFactory级别的缓存。  
  111.   
  112. 三、二级缓存(sessionFactory级别)  
  113.   
  114. 使用hibernate二级缓存,我们首先需要对其进行配置,配置步骤如下:  
  115.   
  116. 1.hibernate并没有提供相应的二级缓存的组件,所以需要加入额外的二级缓存包,常用的二级缓存包是EHcache。这个我们在下载好的hibernate的lib->optional->ehcache下可以找到(我这里使用的hibernate4.1.7版本),然后将里面的几个jar包导入即可。  
  117.   
  118. 2.在hibernate.cfg.xml配置文件中配置我们二级缓存的一些属性:  
  119.   
  120.      <!-- 开启二级缓存 -->  
  121.         <property name="hibernate.cache.use_second_level_cache">true</property>  
  122.         <!-- 二级缓存的提供类 在hibernate4.0版本以后我们都是配置这个属性来指定二级缓存的提供类-->  
  123.         <property name="hibernate.cache.region.factory_class">org.hibernate.cache.ehcache.EhCacheRegionFactory</property>  
  124.         <!-- 二级缓存配置文件的位置 -->  
  125.         <property name="hibernate.cache.provider_configuration_file_resource_path">ehcache.xml</property>  
  126.   
  127. 我这里使用的是hibernate4.1.7版本,如果是使用hibernate3的版本的话,那么二级缓存的提供类则要配置成这个:  
  128.   
  129. <!--这个类在4.0版本以后已经不建议被使用了-->  
  130. <property name="hibernate.cache.provider_class">net.sf.ehcache.hibernate.EhCacheProvider</property>  
  131.   
  132. 3.配置hibernate的二级缓存是通过使用 ehcache的缓存包,所以我们需要创建一个 ehcache.xml 的配置文件,来配置我们的缓存信息,将其放到项目根目录下  
  133.   
  134. <ehcache>  
  135.   
  136.     <!-- Sets the path to the directory where cache .data files are created.  
  137.   
  138.          If the path is a Java System Property it is replaced by  
  139.          its value in the running VM.  
  140.   
  141.          The following properties are translated:  
  142.          user.home - User's home directory  
  143.          user.dir - User's current working directory  
  144.          java.io.tmpdir - Default temp file path -->  
  145.     
  146.   <!--指定二级缓存存放在磁盘上的位置-->  
  147.     <diskStore path="user.dir"/>    
  148.   
  149.   <!--我们可以给每个实体类指定一个对应的缓存,如果没有匹配到该类,则使用这个默认的缓存配置-->  
  150.     <defaultCache  
  151.         maxElementsInMemory="10000"  //在内存中存放的最大对象数  
  152.         eternal="false"         //是否永久保存缓存,设置成false  
  153.         timeToIdleSeconds="120"      
  154.         timeToLiveSeconds="120"      
  155.         overflowToDisk="true"     //如果对象数量超过内存中最大的数,是否将其保存到磁盘中,设置成true  
  156.         />  
  157.     
  158.   <!--  
  159.   
  160.     1、timeToLiveSeconds的定义是:以创建时间为基准开始计算的超时时长;  
  161.     2、timeToIdleSeconds的定义是:在创建时间和最近访问时间中取出离现在最近的时间作为基准计算的超时时长;  
  162.     3、如果仅设置了timeToLiveSeconds,则该对象的超时时间=创建时间+timeToLiveSeconds,假设为A;  
  163.     4、如果没设置timeToLiveSeconds,则该对象的超时时间=max(创建时间,最近访问时间)+timeToIdleSeconds,假设为B;  
  164.     5、如果两者都设置了,则取出A、B最少的值,即min(A,B),表示只要有一个超时成立即算超时。  
  165.   
  166.   -->  
  167.   
  168.   <!--可以给每个实体类指定一个配置文件,通过name属性指定,要使用类的全名-->  
  169.     <cache name="com.xiaoluo.bean.Student"  
  170.         maxElementsInMemory="10000"  
  171.         eternal="false"  
  172.         timeToIdleSeconds="300"  
  173.         timeToLiveSeconds="600"  
  174.         overflowToDisk="true"  
  175.         />  
  176.   
  177.     <cache name="sampleCache2"  
  178.         maxElementsInMemory="1000"  
  179.         eternal="true"  
  180.         timeToIdleSeconds="0"  
  181.         timeToLiveSeconds="0"  
  182.         overflowToDisk="false"  
  183.         /> -->  
  184.   
  185.   
  186. </ehcache>  
  187.   
  188. 4.开启我们的二级缓存  
  189.   
  190. ①如果使用xml配置,我们需要在 Student.hbm.xml 中加上一下配置:  
  191.   
  192. <hibernate-mapping package="com.xiaoluo.bean">  
  193.     <class name="Student" table="t_student">  
  194.         <!-- 二级缓存一般设置为只读的 -->  
  195.         <cache usage="read-only"/>  
  196.         <id name="id" type="int" column="id">  
  197.             <generator class="native"/>  
  198.         </id>  
  199.         <property name="name" column="name" type="string"></property>  
  200.         <property name="sex" column="sex" type="string"></property>  
  201.         <many-to-one name="room" column="rid" fetch="join"></many-to-one>  
  202.     </class>  
  203. </hibernate-mapping>  
  204.   
  205. 二级缓存的使用策略一般有这几种:read-only、nonstrict-read-write、read-write、transactional。注意:我们通常使用二级缓存都是将其配置成 read-only ,即我们应当在那些不需要进行修改的实体类上使用二级缓存,否则如果对缓存进行读写的话,性能会变差,这样设置缓存就失去了意义。  
  206.   
  207. ②如果使用annotation配置,我们需要在Student这个类上加上这样一个注解:  
  208.   
  209. @Entity  
  210. @Table(name="t_student")  
  211. @Cache(usage=CacheConcurrencyStrategy.READ_ONLY)  //  表示开启二级缓存,并使用read-only策略  
  212. public class Student  
  213. {  
  214.     private int id;  
  215.     private String name;  
  216.     private String sex;  
  217.     private Classroom room;  
  218.     .......  
  219. }  
  220.   
  221. 这样我们的二级缓存配置就算完成了,接下来我们来通过测试用例测试下我们的二级缓存是否起作用  
  222.   
  223. ①二级缓存是sessionFactory级别的缓存  
  224.   
  225. TestCase1:  
  226.   
  227. public class TestSecondCache  
  228. {  
  229.     @Test  
  230.     public void testCache1()  
  231.     {  
  232.         Session session = null;  
  233.         try  
  234.         {  
  235.             session = HibernateUtil.openSession();  
  236.   
  237.             Student stu = (Student) session.load(Student.class, 1);  
  238.             System.out.println(stu.getName() + "-----------");  
  239.         }  
  240.         catch (Exception e)  
  241.         {  
  242.             e.printStackTrace();  
  243.         }  
  244.         finally  
  245.         {  
  246.             HibernateUtil.close(session);  
  247.         }  
  248.         try  
  249.         {  
  250.             /**  
  251.              * 即使当session关闭以后,因为配置了二级缓存,而二级缓存是sessionFactory级别的,所以会从缓存中取出该数据  
  252.              * 只会发出一条sql语句  
  253.              */  
  254.             session = HibernateUtil.openSession();  
  255.             Student stu = (Student) session.load(Student.class, 1);  
  256.             System.out.println(stu.getName() + "-----------");  
  257.             /**  
  258.              * 因为设置了二级缓存为read-only,所以不能对其进行修改  
  259.              */  
  260.             session.beginTransaction();  
  261.             stu.setName("aaa");  
  262.             session.getTransaction().commit();  
  263.         }  
  264.         catch (Exception e)  
  265.         {  
  266.             e.printStackTrace();  
  267.             session.getTransaction().rollback();  
  268.         }  
  269.         finally  
  270.         {  
  271.             HibernateUtil.close(session);  
  272.         }  
  273.     }  
  274.   
  275. 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=?  
  276. aaa-----------  
  277. aaa-----------  
  278.   
  279. 因为二级缓存是sessionFactory级别的缓存,我们看到,在配置了二级缓存以后,当我们session关闭以后,我们再去查询对象的时候,此时hibernate首先会去二级缓存中查询是否有该对象,有就不会再发sql了。  
  280.   
  281. ②二级缓存缓存的仅仅是对象,如果查询出来的是对象的一些属性,则不会被加到缓存中去  
  282.   
  283. TestCase2:  
  284.   
  285.   @Test  
  286.     public void testCache2()  
  287.     {  
  288.         Session session = null;  
  289.         try  
  290.         {  
  291.             session = HibernateUtil.openSession();  
  292.   
  293.             /**  
  294.              * 注意:二级缓存中缓存的仅仅是对象,而下面这里只保存了姓名和性别两个字段,所以 不会被加载到二级缓存里面  
  295.              */  
  296.             List<Object[]> ls = (List<Object[]>) session  
  297.                     .createQuery("select stu.name, stu.sex from Student stu")  
  298.                     .setFirstResult(0).setMaxResults(30).list();  
  299.         }  
  300.         catch (Exception e)  
  301.         {  
  302.             e.printStackTrace();  
  303.         }  
  304.         finally  
  305.         {  
  306.             HibernateUtil.close(session);  
  307.         }  
  308.         try  
  309.         {  
  310.             /**  
  311.              * 由于二级缓存缓存的是对象,所以此时会发出两条sql  
  312.              */  
  313.             session = HibernateUtil.openSession();  
  314.             Student stu = (Student) session.load(Student.class, 1);  
  315.             System.out.println(stu);  
  316.         }  
  317.         catch (Exception e)  
  318.         {  
  319.             e.printStackTrace();  
  320.         }  
  321.     }  
  322.   
  323. Hibernate: select student0_.name as col_0_0_, student0_.sex as col_1_0_ from t_student student0_ limit ?  
  324. 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=?  
  325.   
  326. 我们看到这个测试用例,如果我们只是取出对象的一些属性的话,则不会将其保存到二级缓存中去,因为二级缓存缓存的仅仅是对象。  
  327.   
  328. ③通过二级缓存来解决 N+1 的问题  
  329.   
  330. TestCase3:  
  331.   
  332.   @Test  
  333.     public void testCache3()  
  334.     {  
  335.         Session session = null;  
  336.         try  
  337.         {  
  338.             session = HibernateUtil.openSession();  
  339.             /**  
  340.              * 将查询出来的Student对象缓存到二级缓存中去  
  341.              */  
  342.             List<Student> stus = (List<Student>) session.createQuery(  
  343.                     "select stu from Student stu").list();  
  344.         }  
  345.         catch (Exception e)  
  346.         {  
  347.             e.printStackTrace();  
  348.         }  
  349.         finally  
  350.         {  
  351.             HibernateUtil.close(session);  
  352.         }  
  353.         try  
  354.         {  
  355.             /**  
  356.              * 由于学生的对象已经缓存在二级缓存中了,此时再使用iterate来获取对象的时候,首先会通过一条  
  357.              * 取id的语句,然后在获取对象时去二级缓存中,如果发现就不会再发SQL,这样也就解决了N+1问题   
  358.              * 而且内存占用也不多  
  359.              */  
  360.             session = HibernateUtil.openSession();  
  361.             Iterator<Student> iterator = session.createQuery("from Student")  
  362.                     .iterate();  
  363.             for (; iterator.hasNext();)  
  364.             {  
  365.                 Student stu = (Student) iterator.next();  
  366.                 System.out.println(stu.getName());  
  367.             }  
  368.         }  
  369.         catch (Exception e)  
  370.         {  
  371.             e.printStackTrace();  
  372.         }  
  373.     }  
  374.   
  375. 当我们如果需要查询出两次对象的时候,可以使用二级缓存来解决N+1的问题。  
  376.   
  377. ④二级缓存会缓存 hql 语句吗?  
  378.   
  379. TestCase4:  
  380.   
  381.   @Test  
  382.     public void testCache4()  
  383.     {  
  384.         Session session = null;  
  385.         try  
  386.         {  
  387.             session = HibernateUtil.openSession();  
  388.             List<Student> ls = session.createQuery("from Student")  
  389.                     .setFirstResult(0).setMaxResults(50).list();  
  390.         }  
  391.         catch (Exception e)  
  392.         {  
  393.             e.printStackTrace();  
  394.         }  
  395.         finally  
  396.         {  
  397.             HibernateUtil.close(session);  
  398.         }  
  399.         try  
  400.         {  
  401.             /**  
  402.              * 使用List会发出两条一模一样的sql,此时如果希望不发sql就需要使用查询缓存  
  403.              */  
  404.             session = HibernateUtil.openSession();  
  405.             List<Student> ls = session.createQuery("from Student")  
  406.                     .setFirstResult(0).setMaxResults(50).list();  
  407.             Iterator<Student> stu = ls.iterator();  
  408.             for(;stu.hasNext();)  
  409.             {  
  410.                 Student student = stu.next();  
  411.                 System.out.println(student.getName());  
  412.             }  
  413.         }  
  414.         catch (Exception e)  
  415.         {  
  416.             e.printStackTrace();  
  417.         }  
  418.         finally  
  419.         {  
  420.             HibernateUtil.close(session);  
  421.         }  
  422.     }  
  423.   
  424. Hibernate: select student0_.id as id2_, student0_.name as name2_, student0_.sex as sex2_, student0_.rid as rid2_ from t_student student0_ limit ?  
  425. Hibernate: select student0_.id as id2_, student0_.name as name2_, student0_.sex as sex2_, student0_.rid as rid2_ from t_student student0_ limit ?  
  426.   
  427. 我们看到,当我们如果通过 list() 去查询两次对象时,二级缓存虽然会缓存查询出来的对象,但是我们看到发出了两条相同的查询语句,这是因为二级缓存不会缓存我们的hql查询语句,要想解决这个问题,我们就要配置我们的查询缓存了。  
  428.   
  429. 四、查询缓存(sessionFactory级别)  
  430.   
  431. 我们如果要配置查询缓存,只需要在hibernate.cfg.xml中加入一条配置即可:  
  432.   
  433.      <!-- 开启查询缓存 -->  
  434.         <property name="hibernate.cache.use_query_cache">true</property>  
  435.   
  436. 然后我们如果在查询hql语句时要使用查询缓存,就需要在查询语句后面设置这样一个方法:  
  437.   
  438. List<Student> ls = session.createQuery("from Student where name like ?")  
  439.                     .setCacheable(true)  //开启查询缓存,查询缓存也是SessionFactory级别的缓存  
  440.                     .setParameter(0, "%王%")  
  441.                     .setFirstResult(0).setMaxResults(50).list();  
  442.   
  443. 如果是在annotation中,我们还需要在这个类上加上这样一个注解:@Cacheable  
  444.   
  445. 接下来我们来通过测试用例来看看我们的查询缓存  
  446.   
  447. ①查询缓存也是sessionFactory级别的缓存  
  448.   
  449. TestCase1:  
  450.   
  451.   @Test  
  452.     public void test2() {  
  453.         Session session = null;  
  454.         try {  
  455.             /**  
  456.              * 此时会发出一条sql取出所有的学生信息  
  457.              */  
  458.             session = HibernateUtil.openSession();  
  459.             List<Student> ls = session.createQuery("from Student")  
  460.                     .setCacheable(true)  //开启查询缓存,查询缓存也是sessionFactory级别的缓存  
  461.                     .setFirstResult(0).setMaxResults(50).list();  
  462.             Iterator<Student> stus = ls.iterator();  
  463.             for(;stus.hasNext();) {  
  464.                 Student stu = stus.next();  
  465.                 System.out.println(stu.getName());  
  466.             }  
  467.         } catch (Exception e) {  
  468.             e.printStackTrace();  
  469.         } finally {  
  470.             HibernateUtil.close(session);  
  471.         }  
  472.         try {  
  473.             /**  
  474.              * 此时会发出一条sql取出所有的学生信息  
  475.              */  
  476.             session = HibernateUtil.openSession();  
  477.             List<Student> ls = session.createQuery("from Student")  
  478.                     .setCacheable(true)  //开启查询缓存,查询缓存也是sessionFactory级别的缓存  
  479.                     .setFirstResult(0).setMaxResults(50).list();  
  480.             Iterator<Student> stus = ls.iterator();  
  481.             for(;stus.hasNext();) {  
  482.                 Student stu = stus.next();  
  483.                 System.out.println(stu.getName());  
  484.             }  
  485.         } catch (Exception e) {  
  486.             e.printStackTrace();  
  487.         } finally {  
  488.             HibernateUtil.close(session);  
  489.         }  
  490.     }  
  491.   
  492. Hibernate: select student0_.id as id2_, student0_.name as name2_, student0_.sex as sex2_, student0_.rid as rid2_ from t_student student0_ limit ?  
  493.   
  494. 我们看到,此时如果我们发出两条相同的语句,hibernate也只会发出一条sql,因为已经开启了查询缓存了,并且查询缓存也是sessionFactory级别的  
  495.   
  496. ②只有当 HQL 查询语句完全相同时,连参数设置都要相同,此时查询缓存才有效  
  497.   
  498. TestCase2:  
  499.   
  500.   @Test  
  501.     public void test3() {  
  502.         Session session = null;  
  503.         try {  
  504.             /**  
  505.              * 此时会发出一条sql取出所有的学生信息  
  506.              */  
  507.             session = HibernateUtil.openSession();  
  508.             List<Student> ls = session.createQuery("from Student where name like ?")  
  509.                     .setCacheable(true)//开启查询缓存,查询缓存也是SessionFactory级别的缓存  
  510.                     .setParameter(0, "%王%")  
  511.                     .setFirstResult(0).setMaxResults(50).list();  
  512.             Iterator<Student> stus = ls.iterator();  
  513.             for(;stus.hasNext();) {  
  514.                 Student stu = stus.next();  
  515.                 System.out.println(stu.getName());  
  516.             }  
  517.         } catch (Exception e) {  
  518.             e.printStackTrace();  
  519.         } finally {  
  520.             HibernateUtil.close(session);  
  521.         }  
  522.         session = null;  
  523.         try {  
  524.             /**  
  525.              * 此时会发出一条sql取出所有的学生信息  
  526.              */  
  527.             session = HibernateUtil.openSession();  
  528.             /**  
  529.              * 只有当HQL完全相同的时候,连参数都要相同,查询缓存才有效  
  530.              */  
  531. //            List<Student> ls = session.createQuery("from Student where name like ?")  
  532. //                    .setCacheable(true)//开启查询缓存,查询缓存也是SessionFactory级别的缓存  
  533. //                    .setParameter(0, "%王%")  
  534. //                    .setFirstResult(0).setMaxResults(50).list();  
  535.             List<Student> ls = session.createQuery("from Student where name like ?")  
  536.                     .setCacheable(true)//开启查询缓存,查询缓存也是SessionFactory级别的缓存  
  537.                     .setParameter(0, "%张%")  
  538.                     .setFirstResult(0).setMaxResults(50).list();  
  539.             Iterator<Student> stus = ls.iterator();  
  540.             for(;stus.hasNext();) {  
  541.                 Student stu = stus.next();  
  542.                 System.out.println(stu.getName());  
  543.             }  
  544.         } catch (Exception e) {  
  545.             e.printStackTrace();  
  546.         } finally {  
  547.             HibernateUtil.close(session);  
  548.         }  
  549.     }  
  550.   
  551. 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 ?  
  552.   
  553. 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 ?  
  554.   
  555. 我们看到,如果我们的hql查询语句不同的话,我们的查询缓存也没有作用  
  556.   
  557. ③查询缓存也能引起 N+1 的问题  
  558.   
  559. 查询缓存也能引起 N+1 的问题,我们这里首先先将 Student 对象上的二级缓存先注释掉:  
  560.   
  561.      <!-- 二级缓存一般设置为只读的 -->  
  562.         <!--  <cache usage="read-only"/>  -->  
  563.   
  564. TestCase4:  
  565.   
  566.   @Test  
  567.     public void test4() {  
  568.         Session session = null;  
  569.         try {  
  570.             /**  
  571.              * 查询缓存缓存的不是对象而是id  
  572.              */  
  573.             session = HibernateUtil.openSession();  
  574.             List<Student> ls = session.createQuery("from Student where name like ?")  
  575.                     .setCacheable(true)//开启查询缓存,查询缓存也是SessionFactory级别的缓存  
  576.                     .setParameter(0, "%王%")  
  577.                     .setFirstResult(0).setMaxResults(50).list();  
  578.             Iterator<Student> stus = ls.iterator();  
  579.             for(;stus.hasNext();) {  
  580.                 Student stu = stus.next();  
  581.                 System.out.println(stu.getName());  
  582.             }  
  583.         } catch (Exception e) {  
  584.             e.printStackTrace();  
  585.         } finally {  
  586.             HibernateUtil.close(session);  
  587.         }  
  588.           
  589.         session = null;  
  590.         try {  
  591.             /**  
  592.              * 查询缓存缓存的是id,此时由于在缓存中已经存在了这样的一组学生数据,但是仅仅只是缓存了  
  593.              * id,所以此处会发出大量的sql语句根据id取对象,这也是发现N+1问题的第二个原因  
  594.              * 所以如果使用查询缓存必须开启二级缓存  
  595.              */  
  596.             session = HibernateUtil.openSession();  
  597.             List<Student> ls = session.createQuery("from Student where name like ?")  
  598.                     .setCacheable(true)//开启查询缓存,查询缓存也是SessionFactory级别的缓存  
  599.                     .setParameter(0, "%王%")  
  600.                     .setFirstResult(0).setMaxResults(50).list();  
  601.             Iterator<Student> stus = ls.iterator();  
  602.             for(;stus.hasNext();) {  
  603.                 Student stu = stus.next();  
  604.                 System.out.println(stu.getName());  
  605.             }  
  606.         } catch (Exception e) {  
  607.             e.printStackTrace();  
  608.         } finally {  
  609.             HibernateUtil.close(session);  
  610.         }  
  611.     }  
  612.   
  613. 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 ?  
  614.   
  615. 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=?  
  616. 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=?  
  617. 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=?  
  618. 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=?  
  619.   
  620. .........................  
  621.   
  622. 我们看到,当我们将二级缓存注释掉以后,在使用查询缓存时,也会出现 N+1 的问题,为什么呢?因为查询缓存缓存的也仅仅是对象的id,所以第一条 sql 也是将对象的id都查询出来,但是当我们后面如果要得到每个对象的信息的时候,此时又会发sql语句去查询,所以,如果要使用查询缓存,我们一定也要开启我们的二级缓存,这样就不会出现 N+1 问题了  
  623.   
  624.   
  625.   
  626. Hibernate缓存原理:  
  627.   
  628.   对于Hibernate这类ORM而言,缓存显的尤为重要,它是持久层性能提升的关键.简单来讲Hibernate就是对JDBC进行封装,以实现内部状态的管理,OR关系的映射等,但随之带来的就是数据访问效率的降低,和性能的下降,而缓存就是弥补这一缺点的重要方法.  
  629.   
  630.      缓存就是数据库数据在内存中的临时容器,包括数据库数据在内存中的临时拷贝,它位于数据库与数据库访问层中间.ORM在查询数据时首先会根据自身的缓存管理策略,在缓存中查找相关数据,如发现所需的数据,则直接将此数据作为结果加以利用,从而避免了数据库调用性能的开销.而相对内存操作而言,数据库调用是一个代价高昂的过程.  
  631.      一般来讲ORM中的缓存分为以下几类:  
  632.          1:事务级缓存:即在当前事务范围内的数据缓存.就Hibernate来讲,事务级缓存是基于Session的生命周期实现的,每个Session内部会存在一个数据缓存,它随着 Session的创建而存在,随着Session的销毁而灭亡,因此也称为Session Level Cache.  
  633.          2:应用级缓存:即在某个应用中或应用中某个独立数据库访问子集中的共享缓存,此缓存可由多个事务共享(数据库事务或应用事务),事务之间的缓存共享策略与应用的事务隔离机制密切相关.在Hibernate中,应用级缓存由SessionFactory实现,所有由一个SessionFactory创建的 Session实例共享此缓存,因此也称为SessionFactory Level Cache.  
  634.          3:分布式缓存:即在多个应用实例,多个JVM间共享的缓存策略.分布式缓存由多个应用级缓存实例组成,通过某种远程机制(RMI,JMS)实现各个缓存实例间的数据同步,任何一个实例的数据修改,将导致整个集群间的数据状态同步.  
  635.   
  636. Hibernate的一,二级缓存策略:  
  637.   
  638.   Hibernate中提供了两级Cache,第一级别的缓存是Session级别的缓存,它是属于事务范围的缓存。这一级别的缓存由hibernate管理的,一般情况下无需进行干预;第二级别的缓存是SessionFactory级别的缓存,它是属于进程范围或群集范围的缓存。这一级别的缓存可以进行配置和更改,并且可以动态加载和卸载,属于多事务级别,要防止事务并发性。  
  639.   
  640. 缓存是以map的形式进行存储的(key-id,value-object)  
  641.   
  642. 一级缓存(Session):  
  643.   
  644.    事务范围,每个事务(Session)都有单独的第一级缓存.  
  645.   
  646.    一级缓存的管理:当应用程序调用Session的save()、update()、saveOrUpdate()、get()或load(),以及调用查询接口的 list()、iterate()--(用的是n+1次查询,先查id)或filter()方法时,如果在Session缓存中还不存在相应的对象,Hibernate就会把该对象加入到第一级缓存中。当清理缓存时,Hibernate会根据缓存中对象的状态变化来同步更新数据库。 Session为应用程序提供了两个管理缓存的方法: evict(Object obj):从缓存中清除参数指定的持久化对象。 clear():清空缓存中所有持久化对象,flush():使缓存与数据库同步。  
  647.   
  648. 当查询相应的字段如(name),而不是对象时,不支持缓存。  
  649.   
  650. 二级缓存(SessionFactory):  
  651.   
  652.   Hibernate的二级缓存策略的一般过程如下:  
  653.   
  654.    1:条件查询的时候,总是发出一条select * from table_name where …. (选择所有字段)这样的SQL句查询数据库,一次获得所有的数据对象(这个问题要考虑,如果你查询十万条数据时,内存不是被占用)。  
  655.   
  656.  2:把获得的所有数据对象根据ID放入到第二级缓存中。  
  657.   
  658.  3: 当Hibernate根据ID访问数据对象的时候,首先从Session一级缓存中查;查不到,如果配置了二级缓存,那么从二级缓存中查;查不到,再查询数据库,把结果按照ID放入到缓存。  
  659.   
  660.    4:删除、更新、增加数据的时候,同时更新缓存。  
  661.   
  662.   Hibernate的二级缓存策略,是针对于ID查询的缓存策略,对于条件查询则毫无作用。为此,Hibernate提供了针对条件查询的Query Cache。  
  663.   
  664.    
  665.   
  666. Q:什么样的数据适合存放到第二级缓存中?  
  667.   
  668.     1.很少被修改的数据  
  669.   
  670.     2.不是很重要的数据,允许出现偶尔并发的数据  
  671.   
  672.     3.不会被并发访问的数据  
  673.   
  674.     4.参考数据,指的是供应用参考的常量数据,它的实例数目有限,它的实例会被许多其他类的实例引用,实例极少或者从来不会被修改。  
  675.   
  676.  不适合存放到第二级缓存的数据?  
  677.   
  678.     1 经常被修改的数据  
  679.   
  680.     2 财务数据,绝对不允许出现并发  
  681.   
  682.     3 与其他应用共享的数据。  
  683.   
  684.  常用的缓存插件 Hibernater 的二级缓存是一个插件,下面是几种常用的缓存插件:  
  685.   
  686.   EhCache:可作为进程范围的缓存,存放数据的物理介质可以是内存或硬盘,对Hibernate的查询缓存提供了支持。  
  687.   
  688.   OSCache:可作为进程范围的缓存,存放数据的物理介质可以是内存或硬盘,提供了丰富的缓存数据过期策略,对Hibernate的查询缓存提供了支持。  
  689.   
  690.   SwarmCache:可作为群集范围内的缓存,但不支持Hibernate的查询缓存。  
  691.   
  692.   JBossCache:可作为群集范围内的缓存,支持事务型并发访问策略,对Hibernate的查询缓存提供了支持。  
  693.   
  694. 配置二级缓存的主要步骤:  
  695.   
  696.   1 选择需要使用二级缓存的持久化类,设置它的命名缓存的并发访问策略。这是最值得认真考虑的步骤。  
  697.   
  698.   2 选择合适的缓存插件,然后编辑该插件的配置文件。  
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值