Hibernate(六)一二级缓存

Hibernate的缓存

         缓存是hibernate进行性能优化的一个很好的手段,上面我们学习了抓取策略也是hibernate的性能优化的方式,现在我们来看hibernate的另一个优化方式—缓存。

         Hibernate中的缓存包括了一级缓存和二级缓存,一级缓存是session级别的缓存,就是说,只要这个session不关闭,之前查询出来的数据就会被缓存到session中,当我们再去查询该数据时,hibernate不会再次发生sql查询,而是会取出session中的数据使用。这就是一级缓存。

一级缓存和N+1事件

         与一级缓存相关的就是N+1事件,什么是N+1事件呢?下面我们来看看,简单的演示一个N+1事件:

@Test

publicvoid test02() {

    Session session = null;

    try {

       session = HibernateUtil.openSession();

       /**

        * HQL语句中存在一个iterate方法,该方法可以将一个list列表

        * 直接转换为一个迭代器,如果我们使用了这个方法,我们会发现一旦

        * 我们使用了数据,一条数据会发送一条sql,这就是N+1事件

        */

Iterator<Student> it = session.createQuery("from Student").iterate();

       while (it.hasNext()) {

           System.out.println(it.next().getName());

       }

    } catch (Exception e) {

       e.printStackTrace();

       if(session != null) {

           session.getTransaction().rollback();

       }

    } finally {

       HibernateUtil.closed(session);

    }

}

代码运行效果如下:

         我们发现一条语句会发送一条sql,这样效率肯定很低,那么hibernate为什么提供这个方法呢?

         如果大家仔细看的话,会发现,sql比数据多了一条,就是第一条sql,我们研究这条sql发现这条sql仅仅查询了一个所有数据的id的sql,那么为什么会这样了,这就说的hibernate的一级缓存了。

         hibernate中的一级缓存就是如果数据已经在前面查询出来了,那么如果session此时没有被关闭,这数据会缓存到session,这样我们如果在后面使用到该数据,hibernate就不会发生sql了,这就是一级缓存,所以一级缓存也叫做session级别的缓存。而iterate方法只会去查询数据的id,hibernate是这样认为的,如果我们前面使用了list等方法查询出了数据,并且session没有关闭,数据缓存了此时使用iterate方法,我们只要再去查询一下数据的id就行了,id查询回来后就会和缓存的数据对比,找到需要的数据。

         下面我们来在测试下:

@Test

publicvoid test03() {

    Session session = null;

    try {

       session = HibernateUtil.openSession();

       List<Student> list = session.createQuery("from Student").list();

       Iterator<Student> it = list.iterator();

       while (it.hasNext()) {

           System.out.println(it.next().getName());

       }

       /**

        * 重新去查,看看会不会发送sql,我们发现发送了一条sql

        * 但是这条sql仅仅查询了id而已,当我们在前面查询了数据,

        * 此时数据缓存了,我们再使用iterate方法查询的时候,只会发送一条

        * 查询IDsql,这样就减少了对内存的消耗

        */

       it = session.createQuery("from Student").iterate();

       while (it.hasNext()) {

           System.out.println(it.next().getName());

       }

    } catch (Exception e) {

       e.printStackTrace();

       if(session != null) {

           session.getTransaction().rollback();

       }

    } finally {

       HibernateUtil.closed(session);

    }

}

         当然iterate方法建议大家慎重使用。我们一般不会使用它,只有我们保证数据之前查询过,并且session没有关闭,否则使用它很容易触发N+1事件的。其实iterate方法主要是在二级缓存中使用的,这个我们后面讲解二级缓存的时候再说

         其实一级缓存我们发现不是很常用,因为我们在查询数据之后都会关闭session的,这样来说一级缓存的用处就不多了,其次如果我们不使用iterate方法,再次使用list方法也行,只是内存占用比较多,但是现在服务器的数据瓶颈早已经不是内存这儿了,内存现在比较便宜,储量现在都比较大,不值得为了一点内存使用iterate方法,而且很容易触发N+1事件。

二级缓存

         Hibernate的效率优化除了抓取策略外,主要就是二级缓存,一级缓存不建议大家使用。前面我们说过一级缓存是session级别的缓存,session关闭,则一级缓存失效,而二级缓存则是SessionFactory级别的缓存,所以只要SessionFactory存在,则二级缓存就有效。但是Hibernate本身没有提供二级缓存,所以我们要使用二级缓存的话,需要借助第三方的工具,常用的二级缓存工具有—EHCache。下面我们就以EHCache为例,讲解hibernate中的二级缓存的使用。

Hibernate配置二级缓存的步骤

1、 导入EHCache的jar包;

2、 在hibernate.cfg.xml文件中开启相应的配置

3、 在src目录下创建一个ehcache.xml文件(EHCache的配置文件)

4、 开启二级缓存

下面我们来详细的配置一遍二级缓存吧。

导入EHCache的jar包

首先需要一个hibernate项目,我们使用前面XML配置的项目,其次需要ehcache的jar包,注意:hibernate给我们提供了ehcache的jar包,在lib文件夹下的option文件夹下,有一个ehcache的文件夹,这个下面的三个jar文件都是EHCache所需的jar包:

         将这三个jar文件拷入项目中。

在hibernate.cfg.xml文件中开启相应的配置

         我们要在hibernate中开启二级缓存,需要配置如下信息:

<!-- 二级缓存部分 -->

<!-- 表示该项目要使用二级缓存,开启二级缓存 -->

<property name="hibernate.cache.use_second_level_cache">true</property>

<!-- 告诉hibernate我们要使用的缓存提供类是哪个(我们使用的是EhCacheProvider -->

<property name="hibernate.cache.provider_class">net.sf.ehcache.hibernate.EhCacheProvider</property>

<!--4.0之后需要使用这个配置,不能少,据说可以提高效率 -->

<property name="hibernate.cache.region.factory_class">org.hibernate.cache.ehcache.EhCacheRegionFactory</property>

在src目录下创建一个ehcache.xml文件(EHCache的配置文件)

         这个文件其实hibernate给我们已经提供了,在hibernate的project/etc下就有,这个目录下hibernate为我们提供了好多我们可能用的配置。

         复制到项目的src目录下。

         下面我们详细的说明下ehcache.xml文件中的一些配置说明,其他的后面再说。

<!-- 这个说明我们的缓存保存在项目的临时文件夹下 -->

<diskStore path="java.io.tmpdir"/>

<!-- 这个是默认的配置

    maxElementsInMemory="10000"表示最大的缓存对象是一万个

    eternal="false" 表示是否永久缓存,一般不会,所以值一般是false

    timeToIdleSeconds="120" 表示不活动(空闲)的对象多久更新一次,单位是秒

    timeToLiveSeconds="120" 表示活动的对象多久更新一次,单位是秒

    overflowToDisk="true" 表示如果超过了缓存对象的数量,我们就放入硬盘中

     -->

    <defaultCache

        maxElementsInMemory="10000"

        eternal="false"

        timeToIdleSeconds="120"

        timeToLiveSeconds="120"

        overflowToDisk="true"

        />

         之后在hibernate.cfg.xml文件中再次配置一下,告诉hibernate配置的ehcache.xml文件的位置:

<!—告诉hibernate,我们的ehcache配置文件的位置 -->

<property name="hibernate.cache.provider_configuration_file_resource_path">

ehcache.xml</property>

这样我们就完成了ehcache.xml的配置了。

开启二级缓存

         在项目中要开启二级缓存有两种方式,就是在实体类的配置文件中去写,如下我们在Student.hbm.xml文件中配置:

<hibernate-mapping package="com.lzcc.hibernate.entity">

    <class name="Student" table="student">

 <!-- 注意:我们一般二级缓存都只配置read-only,不会配置可以变化的对象的 -->

       <cache usage="read-only" />

        <id name="id" >

           <generator class="native" />

        </id>

        <property name="name" />

        <property name="sex" />

        <many-to-one name="classroom" column="cid" fetch="join"/>

    </class>

</hibernate-mapping>

         我们在class节点下面配置cache节点,注意的是,usage一般都是只读。

这样我们就完成了二级缓存的配置了,Student类现在就是存在了二级缓存的。下面我们来测试是否配置成功。

@Test

publicvoid test01() {

    Session session = null;

    try {

       session = HibernateUtil.openSession();

       Student stu = (Student) session.load(Student.class, 2);

       System.out.println(stu.getName());

    } catch (Exception e) {

       e.printStackTrace();

       if(session != null) {

           session.getTransaction().rollback();

       }

    } finally {

       HibernateUtil.closed(session);

    }

    /**

     * 没有再发送sql,说明hibernate的二级缓存生效了

     */

    session = HibernateUtil.openSession();

    Student stu = (Student) session.load(Student.class, 2);

    System.out.println(stu.getName());

}

         我们发现第二次查询的时候,hibernate并没有发送sql,这就说明二级缓存已经生效了。

         下面我们来详细的看看二级缓存的一些知识吧。

Hibernate二级缓存的细节

         前面我们给实体类配置二级缓存是ehcache.xml中默认的配置,其实我们还可以为一个对象配置一个独立的缓存配置,就是在ehcache.xml文件中如下配置:

<!-- 我们也可以在这个给实体类配置二级缓存,如下

       注意:指明类时要加包路径,这样Student这个对象

       就不会使用上面默认的配置,而是使用下面的配置,其他

       还是使用上面默认的配置-->

   <cache name="com.lzcc.hibernate.entity.Student"

        maxElementsInMemory="10000"

        eternal="false"

        timeToIdleSeconds="120"

        timeToLiveSeconds="120"

        overflowToDisk="true"

        />

         再次测试代码:

         这样Student就是使用了我们上面配置的缓存配置,没有使用默认的配置。

         下面我们来看其他的一些二级缓存的问题:

@Test

publicvoid test02() {

    Session session = null;

    try {

       session = HibernateUtil.openSession();

       Student stu = (Student) session.load(Student.class, 2);

       System.out.println(stu.getName());

    } catch (Exception e) {

       e.printStackTrace();

       if(session != null) {

           session.getTransaction().rollback();

       }

    } finally {

        HibernateUtil.closed(session);

    }

    /**

     * 没有再发送sql,说明hibernate的二级缓存生效了

     */

    try {

       session = HibernateUtil.openSession();

       session.beginTransaction();

       Student stu = (Student) session.load(Student.class, 2);

       /**

        * 注意,代码会报错,因为这个的对象是二级缓存中的对象,

        * 而二级缓存中的对象是只读的对象,如果我们要修改,则会

        * 报错

        */

       stu.setName("王老板");

       session.getTransaction().commit();

    } catch (Exception e) {

       e.printStackTrace();

       if(session != null) {

           session.getTransaction().rollback();

       }

    } finally {

       HibernateUtil.closed(session);

    }

}

         注意:二级缓存中的对象我们一般配置成了只读的对象,不要去更新它

@Test

publicvoid test03() {

    Session session = null;

    try {

       session = HibernateUtil.openSession();

       List<Student> stus = (List<Student>) session.createQuery("from Student").list();

       for (Student student : stus) {

           System.out.println(student.getName());

       }

    } catch (Exception e) {

       e.printStackTrace();

       if(session != null) {

           session.getTransaction().rollback();

       }

    } finally {

       HibernateUtil.closed(session);

    }

    try {

       session = HibernateUtil.openSession();

       session.beginTransaction();

       /**

        * 没有再发送sql,因为上面已经缓存了对象,所以就不会发生sql

        */

       Student stu = (Student) session.load(Student.class, 2);

       System.out.println(stu.getName()+"***********");

    } catch (Exception e) {

       e.printStackTrace();

       if(session != null) {

           session.getTransaction().rollback();

       }

    } finally {

       HibernateUtil.closed(session);

    }

}

         注意:二级缓存是缓存持久化对象,它是把所有的对象缓存到内存中了,一定要记得,hibernate的二级缓存是基于存持久化对象的缓存

         下面我们来看看为什么我们说hibernate的二级缓存是基于对象的缓存呢?

@Test

publicvoid test04() {

    Session session = null;

    try {

       session = HibernateUtil.openSession();

       List<Object[]> stus = (List<Object[]>) session.createQuery("select s.id, s.name from Student s").list();

    } catch (Exception e) {

       e.printStackTrace();

       if(session != null) {

           session.getTransaction().rollback();

       }

    } finally {

       HibernateUtil.closed(session);

    }

    try {

       session = HibernateUtil.openSession();

       session.beginTransaction();

       /**

        * 会发送sql,因为hibernate的二级缓存是基于对象的缓存,上面的

        * 查询不是Student对象,所以此处话发送一条sql完成查询

        */

       Student stu = (Student) session.load(Student.class, 2);

       System.out.println(stu.getName()+"***********");

    } catch (Exception e) {

       e.printStackTrace();

       if(session != null) {

           session.getTransaction().rollback();

       }

    } finally {

       HibernateUtil.closed(session);

    }

}

@Test

publicvoid test05() {

    Session session = null;

    try {

       session = HibernateUtil.openSession();

       List<Student> stus = (List<Student>) session.createQuery("from Student").list();

       for (Student student : stus) {

           System.out.println(student.getName());

       }

    } catch (Exception e) {

       e.printStackTrace();

       if(session != null) {

           session.getTransaction().rollback();

       }

    } finally {

       HibernateUtil.closed(session);

    }

    try {

       session = HibernateUtil.openSession();

       /**

        * 我们发现没有了N+1事件,这是因为二级缓存生效了

        * 所以iterate方法主要是在二级缓存中使用

        */

       Iterator<Student> stus = session.createQuery("from Student").iterate();

       while (stus.hasNext()) {

           System.out.println(stus.next().getName());

       }

    } catch (Exception e) {

       e.printStackTrace();

       if(session != null) {

           session.getTransaction().rollback();

       }

    } finally {

       HibernateUtil.closed(session);

    }

}

         Iterate方法主要是在二级缓存中使用的,配合二级缓存,这样就没有了N+1事件了。但是二级缓存在HQL语句中除了iterate方法外,list则失效,如下代码:

@Test

publicvoid test06() {

    Session session = null;

    try {

       session = HibernateUtil.openSession();

       List<Student> stus = (List<Student>) session.createQuery("from Student").list();

       for (Student student : stus) {

           System.out.println(student.getName());

       }

    } catch (Exception e) {

       e.printStackTrace();

       if(session != null) {

           session.getTransaction().rollback();

       }

    } finally {

       HibernateUtil.closed(session);

    }

    try {

       session = HibernateUtil.openSession();

       /**

        * 这里会发送两条一模一样的sql,因为二级缓存仅仅只是缓存对象,

        * hql查询如果使用了二级缓存,iterate方法可以使用,

        * list不能缓存,如果这里也想发送一条sql,则要使用查询缓存

        */

       List<Student> stus = session.createQuery("from Student").list();

       for (Student student : stus) {

           System.out.println(student.getName());

       }

    } catch (Exception e) {

       e.printStackTrace();

       if(session != null) {

           session.getTransaction().rollback();

       }

    } finally {

       HibernateUtil.closed(session);

    }

}

         如果这里也想发送一条sql,则要使用查询缓存

查询缓存

         Hibernate中的查询缓存是针对HQL语句的缓存,查询缓存只会缓存hql语句和id,而不会缓存对象(二级缓存缓存的是对象,这里再次强调)。这里注意:查询缓存也是SessionFactory级别的缓存

         我们要使用查询缓存,我们要在SessionFactory中配置开启查询缓存,如果配置呢?在hibernate.cfg.xml文件中配置如下的信息:

<property name="hibernate.cache.use_second_level_cache">true</property>

<!-- 表示开启查询缓存 -->

<property name="hibernate.cache.use_query_cache">true</property>

         这样就开启了查询缓存了,那么我们怎么使用查询缓存呢?比较简单,因为查询缓存是针对hql语句的,使用如下:

@Test

publicvoid test07() {

    Session session = null;

    try {

       session = HibernateUtil.openSession();

       List<Student> stus = (List<Student>) session

           .createQuery("from Student")

           .setCacheable(true)//设置setCacheable(true)表示使用查询缓存

           .list();

       for (Student student : stus) {

           System.out.println(student.getName());

       }

    } catch (Exception e) {

       e.printStackTrace();

       if(session != null) {

           session.getTransaction().rollback();

       }

    } finally {

       HibernateUtil.closed(session);

    }

    try {

       session = HibernateUtil.openSession();

       /**

        * 我们发现使用了查询缓存后,这段代码只是发送了一条sql

        */

       List<Student> stus = session.

              createQuery("from Student")

              .setCacheable(true)//再次查询时必须也设置

              .list();

       for (Student student : stus) {

           System.out.println(student.getName());

       }

    } catch (Exception e) {

       e.printStackTrace();

       if(session != null) {

           session.getTransaction().rollback();

       }

    } finally {

       HibernateUtil.closed(session);

    }

}

         但是查询缓存一定要慎重使用,如下代码:

@Test

publicvoid test08() {

    Session session = null;

    try {

       session = HibernateUtil.openSession();

       List<Student> stus = (List<Student>) session

              .createQuery("from Student where name like ?")

              .setCacheable(true)//设置setCacheable(true)表示使用查询缓存

              .setParameter(0, "%%")

              .list();

       for (Student student : stus) {

           System.out.println(student.getName());

       }

    } catch (Exception e) {

       e.printStackTrace();

       if(session != null) {

           session.getTransaction().rollback();

       }

    } finally {

       HibernateUtil.closed(session);

    }

    try {

       session = HibernateUtil.openSession();

       /**

        * 我们发现查询缓存无效,这是因为查询缓存缓存的是hql,一旦hql语句不

        * 一致,则查询缓存失效,即便只是参数不一致

        * 所以查询缓存慎重使用,在项目设计之初就要考虑,一般只有一些对象如果

        * 查询时语句一直不变,否则不建议使用查询缓存

        */

       List<Student> stus = session

              .createQuery("from Student where name like ?")

              .setCacheable(true)//再次查询时必须也设置

              .setParameter(0, "%%")

              .list();

       for (Student student : stus) {

           System.out.println(student.getName());

       }

    } catch (Exception e) {

       e.printStackTrace();

       if(session != null) {

           session.getTransaction().rollback();

       }

    } finally {

       HibernateUtil.closed(session);

    }

}

         我们发现查询缓存只会缓存hql语句一致的,所以建议大家慎重使用,在项目设计之初就要考虑如果存在查询对象hql语句一直不变的才建议使用查询缓存,否则不建议使用,并且因为查询缓存缓存的是id,所以如果我们一旦某个对象没有使用二级缓存,则查询缓存因为缓存id,会发送大量的sql完成查询,因此更加不建议大家使用查询缓存(这个是真的新手,因为不熟悉查询缓存,可能关闭二级缓存,引起大量sql,这也是引起N+1事件的第二个原因)。

查询缓存的最佳使用建议

1、 只有在查询hql不发生变化的对象使用查询缓存

2、 使用查询缓存的对象时,该对象的二级缓存一定要开启,否则会触发N+1事件。

使用annotation配置二级缓存

         前面我们使用XML来配置二级缓存,在hibernate.cfg.xml文件中先配置,再在需要二级缓存的对象的xxx.hbm.xml文件中开启二级缓存,那么annotation如何配置二级缓存呢?其实使用annotation配置二级缓存和XML差不多,hibernate.cfg.xml文件一样,只是因为annotation配置的hibernate没有实体类对应的xxx.hbm.xml文件,所以需要一个annotation来配置二级缓存,如下:

还是XML配置的几个步骤:

1、 添加三个jar文件

2、 在hibernate.cfg.xml文件中配置

3、 在src目录下创建一个ehcache.xml文件(EHCache的配置文件)

4、 使用@Cache 这个annotation来开启对象的二级缓存

前面的步骤就不写了,我们看第四步:

package com.lzcc.hibernate.entity;

 

import javax.persistence.Entity;

import javax.persistence.FetchType;

import javax.persistence.GeneratedValue;

import javax.persistence.Id;

import javax.persistence.JoinColumn;

import javax.persistence.ManyToOne;

import javax.persistence.Table;

import org.hibernate.annotations.Cache;//导入的是hibernate的路径

import org.hibernate.annotations.CacheConcurrencyStrategy;

 

@Entity

@Table(name="student")

@Cache(usage=CacheConcurrencyStrategy.READ_ONLY)//开启Student的二级缓存

public class Student {

         private int id;

         private String name;

         private String sex;

         private Classroom classroom;

 

         @Id

         @GeneratedValue

         public int getId() {

                  return id;

         }

 

         public void setId(int id) {

                  this.id = id;

         }

 

         public String getName() {

                  return name;

         }

 

         public void setName(String name) {

                  this.name = name;

         }

 

         public String getSex() {

                  return sex;

         }

 

         public void setSex(String sex) {

                  this.sex = sex;

         }

 

         //注意:annotation配置的二级缓存如果存在关联关系

         //想要二级缓存生效,则注意:

         //如果关联关系没有使用二级缓存,则开启延迟加载,否则

         //因为要查询关系关系对象,所以查询缓存失效

         //如果关联关系已经都使用了二级缓存,则延迟加载可开启

         //也可不开启,不会影响二级缓存的使用

         @ManyToOne

         @JoinColumn(name="cid")

         public Classroom getClassroom() {

                  return classroom;

         }

 

         public void setClassroom(Classroom classroom) {

                  this.classroom = classroom;

         }

 

         public Student(String name, String sex, Classroom classroom) {

                  super();

                  this.name = name;

                  this.sex = sex;

                  this.classroom = classroom;

         }

 

         public Student() {

         }

 

         @Override

         public String toString() {

                  return "Student [id=" + id + ", name=" + name + ", sex=" + sex

                                   + ", classroom=" + classroom + "]";

         }

 

}

         这样就完成了使用annotation配置hibernate的二级缓存了。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值