Hibernate缓存机制详解

为什么要用Hibernate缓存?
因为Hibernate是一个持久层框架,经常访问物理数据库。为了降低应用程序对物理数据源访问的频次,从而提高应用程序的运行性能。
缓存内的数据是对物理数据源中的数据的复制,应用程序在运行时从缓存读写数据,在特定的时刻或事件会同步缓存和物理数据源的数据。
缓存范围:

  • 事务范围:缓存只能被独立的当期事务访问
  • 应用范围:可以被应用范围内的所有事务共享,应用并发机制
  • 集群范围:被多个集群机器共享,进程间通过远程通讯保证缓存数据的一致性

Hibernate缓存包括两大类:Hibernate一级缓存和Hibernate二级缓存。
1.Hibernate一级缓存又称为“Session的缓存”。
Session内置不能被卸载,Session的缓存是事务范围的缓存(Session对象的生命周期通常对应一个数据库事务或者一个应用事务)。

2.Hibernate二级缓存又称为“SessionFactory的缓存”。
由于SessionFactory对象的生命周期和应用程序的整个过程对应,因此Hibernate二级缓存是进程范围或者集群范围的缓存,有可能出现并发问题,因此需要采用适当的并发访问策略,该策略为被缓存的数据提供了事务隔离级别。
第二级缓存是可选的,是一个可配置的插件,默认下SessionFactory不会启用这个插件。

hibernate提供的一级缓存:

/**
   * 此时会发出一条sql,将所有学生全部查询出来,并放到session的一级缓存当中
   * 当再次查询学生信息时,会首先去缓存中看是否存在,如果不存在,再去数据库中查询
   * 这就是hibernate的一级缓存(session缓存)
*/
List<Student> stus = (List<Student>)session.createQuery("from Student").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

我们看到此时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").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 

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.4版本),然后将里面的几个jar包导入即可。

2.在hibernate.cfg.xml配置文件中配置我们二级缓存的一些属性:

 <!-- 开启二级缓存 -->
 <property name="hibernate.cache.use_second_level_cache">true</property>
 <!-- 配置二级缓存产品的hibernate产品接口 -->
<property name="hibernate.cache.region.factory_class">org.hibernate.cache.ehcache.EhCacheRegionFactory</property>

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

<?xml version="1.0" encoding="UTF-8"?>
<ehcache>
    <diskStore path="java.io.tmpdir"></diskStore>
    <defaultCache
        maxElementsInMemory="10000"
        eternal="false"
        timeToIdleSeconds="120"
        timeToLiveSeconds="120"
        overflowToDisk="true" 
/>
<!--maxElementsInMemory为缓存对象的最大数目, eternal设置是否永远不过期,timeToIdleSeconds对象处于空闲状态的最多秒数,timeToLiveSeconds对象处于缓存状态的最多秒数,overflowToDisk如果对象数量超过内存中最大的数,是否将其保存到磁盘中 -->
</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 ,即我们应当在那些不需要进行修改的实体类上使用二级缓存,否则如果对缓存进行读写的话,性能会变差,这样设置缓存就失去了意义。

  • read-only:只读.对于不会发生改变的数据可使用
  • nonstrict-read-write:如果程序对并发访问下的数据 同步要求不 严格,且数据更新频率较低,采用本缓存 同步策略可获得较 好性能
  • read-write:严格的读写缓存.基于时间戳判定机制,实 现了“read committed”事务隔离等级.用于对数据同步要求的情况,但 不 支持分布式缓存,实际应用中 使用最多的缓存同步策略
  • transactional:事务型缓存,必须运行在JTA事务环境中.此缓存中, 缓存的相关操作被添加到事务中,如事务失败,则缓冲池的数据 会一同回滚到事务的开始之前的状态.事务型缓存实现了 “Repeatable read”事务隔离等级,有效保证了数据的合法性, 适应于对关键数据的缓存,Hibernate内置缓存中,只有 JBossCache支持事务型缓存.

②如果使用实体类配置,我们需要在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=?

我们看到这个测试用例,如果我们只是取出对象的一些属性的话,则不会将其保存到二级缓存中去,因为二级缓存缓存的仅仅是对象。

查询缓存(sessionFactory级别)
我们如果要配置查询缓存,只需要在hibernate.cfg.xml中加入一条配置即可:

<!-- 开启查询缓存 -->
<property name="hibernate.cache.use_query_cache">true</property>

然后我们如果在查询hql语句时要使用查询缓存,就需要在查询语句后面设置这样一个方法:

List<Student> ls = session.createQuery("from Student ").setCacheable(true)  //开启查询缓存,查询缓存也是SessionFactory级别的缓存
.list();

如果是在实体类中,我们还需要在这个类上加上这样一个注解:@Cacheable

接下来我们来通过测试用例来看看我们的查询缓存
查询缓存也是sessionFactory级别的缓存,只有当 HQL 查询语句完全相同时,连参数设置都要相同,此时查询缓存才有效

public void test2() {
        Session session = null;
        try {
            /**
             * 此时会发出一条sql取出所有的学生信息
             */
            session = HibernateUtil.openSession();
            List<Student> ls = session.createQuery("from Student")
                    .setCacheable(true)  //开启查询缓存,查询缓存也是sessionFactory级别的缓存
                    .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级别的缓存
            .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 

我们看到,此时如果我们发出两条相同的语句,hibernate也只会发出一条sql,因为已经开启了查询缓存了,并且查询缓存也是sessionFactory级别的

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值