Hibernate的缓存机制

来源:互联网 发布:淘宝怎么找同城店铺 编辑:程序博客网 时间:2024/06/08 16:48

一、N+1问题

首先我们来探讨一下N+1的问题,我们先通过一个例子来看一下,什么是N+1问题:
list()获得对象:

       /**              * 此时会发出一条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语句,将所有的对象查询出来,这点相信大家都能理解

Hibernate: select student0_.id as id2_, student0_.name as name2_, student0_.rid as rid2_, student0_.sex as sex2_ from t_student student0_ limit ?

那么,我们再来看看iterator()这种情况

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 的问题。

那么这种 N+1 问题我们如何解决呢,其实我们只需要使用 list() 方法来获得对象即可。但是既然可以通过 list() 我们就不会出现 N+1的问题,那么我们为什么还要保留 iterator()这种形式呢?我们考虑这样一种情况,如果我们需要在一个session当中要两次查询出很多对象,此时我们如果写两条 list()时,hibernate此时会发出两条 sql 语句,而且这两条语句是一样的,但是我们如果第一条语句使用 list(),而第二条语句使用 iterator()的话,此时我们也会发两条sql语句,但是第二条语句只会将查询出对象的id,所以相对应取出所有的对象而已,显然这样可以节省内存,而如果再要获取对象的时候,因为第一条语句已经将对象都查询出来了,此时会将对象保存到session的一级缓存中去,所以再次查询时,就会首先去缓存中查找,如果找到,则不发sql语句了。这里就牵涉到了接下来这个概念:hibernate的一级缓存。

二、一级缓存(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级别的缓存。

三、二级缓存(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的版本的话,那么二级缓存的提供类则要配置成这个:


net.sf.ehcache.hibernate.EhCacheProvider

3.配置hibernate的二级缓存是通过使用 ehcache的缓存包,所以我们需要创建一个 ehcache.xml 的配置文件,来配置我们的缓存信息,将其放到项目根目录下

复制代码

<!-- 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    />

  
  

原创粉丝点击