02. Hibernate的二级缓存

Hibernate的二级缓存

我使用的Hibernate版本:5.2.0 final

Hibernate二级缓存的概念

需要配置才能使用,该缓存用来存放被程序读过的数据,将来其他程序可能会重用这些数据,因此这个二级缓存中的数据能被所有的程序共享。

一级缓存,如果我们发起一个请求,那么将创建一个线程,该线程绑定一个Session对象,此Session对象对应一个事务,并维护一个一级缓存,也就是说,一级缓存的范围是一个事务范围,或者说是一个线程范围。如果我们可能有多个线程需要共享同一块缓存,那么此时这块缓存的范围至少为进程级别。

Hibernate的二级缓存是一个可插拔的缓存插件,它由SessionFactory负责管理。

由于SessionFactory对象(重量级)的生命周期和应用进程对应的整个进程对应,因此二级缓存是进程范围或集群范围的(范围更广,可能对应多个数据库)缓存。

二级缓存中存放的是对象的散装数据(存放的并非是对象的引用,而是对象的属性),必要时,将散装的数据重新封装成对象。

二级缓存是可选的,可以在每个类或每个集合的粒度上配置第二级缓存。

Hibernate的二级缓存结构

SessionFactory的外置缓存:Hibernate的二级缓存实际上是一块内存,如果存放的信息过大,则把信息方法外存中

缓存并发访问策略:设置并发访问策略,例如:只读,读写

这里写图片描述

Hibernate提供了内置的缓存插件来实现CacheProvider

  • org.hibernate.cache.EhCacheProvider

  • org.hibernate.cache.OSCacheProvider

二级缓存的配置

  • hibernate.cache.region.factory_class(Hibernate 4.x)

  • hibernate.cache.provide_class(Hibernate 3.x)

  • hibernate.cache.use_second_level_cache

  • hibernate.cache.use_query_cache

  • ehcache.xml

可以到 Hibernate 官网或者 Hibernate 开发包中(document文件下的HTML)查看 Configuring second-level caching

将下面内容配置到核心配置文件(hibernate.cfg.xml)的session-factory标签中

<!-- 配置Hibernate二级缓存的实现CacheProvide -->
<property name="hibernate.cache.region.factory_class">org.hibernate.cache.ehcache.EhCacheRegionFactory</property>
<!-- 开始二级缓存 -->
<property name="hibernate.cache.use_second_level_cache">true</property>

配置ehcache.xml文件

我们可以到HIbernate的开发包,进入project->etc目录下,找到ehcache.xml的模板文件。

<!-- 配置外置缓存路径 -->
<diskStore path="java.io.tmpdir"/>

<defaultCache
        maxElementsInMemory="10000"  最大的缓存对象数目
        eternal="false"              缓存对象是否永远不过期,如果不永恒的,设置下面两个
        timeToIdleSeconds="120"      设置空闲对象的过期时间,单位为秒
        timeToLiveSeconds="120"      设置缓存状态对象的总的生存时间,单位为秒
        overflowToDisk="true"        设置如果二级缓存满了,是否溢出缓存到硬盘
/>

<!-- 针对具体的类型创建的对象配置缓存 -->
<cache name="simple1"
        maxElementsInMemory="10000"
        eternal="false"
        timeToIdleSeconds="300"
        timeToLiveSeconds="600"
        overflowToDisk="true"
/>

我们将ehcache.xml文件放置到src跟目录下,如下:

<ehcache>


    <diskStore path="D:/Java/hibernate_temp"/>

    <defaultCache
        maxElementsInMemory="10000"
        eternal="false"
        timeToIdleSeconds="120"
        timeToLiveSeconds="120"
        overflowToDisk="true"
        />
    <cache name="com.li.pojo.Student"
        maxElementsInMemory="10000"
        eternal="false"
        timeToIdleSeconds="10"
        timeToLiveSeconds="120"
        overflowToDisk="true"
        />

</ehcache>

此外,我们还需在对应类型的映射文件中配置实体类型的访问策略,否则,查询出来的记录是不会放到二级缓存中的。

usage属性的值可以有”read-only” 、”read-write” 等四个。

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE hibernate-mapping PUBLIC
        "-//Hibernate/Hibernate Mapping DTD 3.0//EN"
        "http://www.hibernate.org/dtd/hibernate-mapping-3.0.dtd">

<hibernate-mapping package="com.li.pojo">
    <class name="Student" table="STU_INFO">
        <!-- 设置并发访问策略(设置后查询到记录后才会放置到二级缓存中) -->
        <cache usage="read-only"/>

        <id name="id" type="string" column="STU_NO">
            <generator class="uuid" />
        </id>
        <property name="name" type="string" column="STU_NAME"></property>
        <property name="gender" type="string" column="STU_GENDER"></property>
        <property name="age" type="int" column="STU_AGE"></property>
    </class>
</hibernate-mapping>

当然,我们还需要将 ehache 二级缓存的实现的JAR包导入我们工程中,可以在 Hibernate 开发包中 lib->optional 这种可选的开发包中找到 hibernate-ehcache JAR 包。

测试:

package com.li.test;

import org.hibernate.Session;
import org.hibernate.Transaction;

import com.li.common.HibernateSessionFactory;
import com.li.pojo.Student;

public class SecondCacheTest {

    public static void main(String[] args) {
        Session session=HibernateSessionFactory.getSession();
        Transaction trans=null;

        try {

            //启动事务1
            trans=session.beginTransaction();
            Student student=(Student)session.get(Student.class, "2015");
            System.out.println(student);
            //自动关闭session1
            trans.commit();

            session=HibernateSessionFactory.getSession();
            //启动事务2
            trans=session.beginTransaction();
            student=(Student)session.get(Student.class, "2015");
            System.out.println(student);
            //自动关闭session2
            trans.commit();
        } catch (Exception e) {
            e.printStackTrace();
            trans.rollback();
        }
    }

}

运行结果:

Hibernate: select student0_.STU_NO as STU_NO1_0_0_, student0_.STU_NAME as STU_NAME2_0_0_, student0_.STU_GENDER as STU_GEND3_0_0_, student0_.STU_AGE as STU_AGE4_0_0_ from STU_INFO student0_ where student0_.STU_NO=?
Student [id=2015, name=Hibernate, age=20, gender=male]
Student [id=2015, name=Hibernate, age=20, gender=male]

两次get()操作,只使用了一次select语句,并且还不是在同一个session中,实际上第二次查询是从二级缓存中查询的。如果我们把二级缓存访问策略去掉,那么两次上面get()方法将会执行两次select语句。如果我们在核心配置文件中将开启二级缓存的配置去掉,再测试一下,我们发现,还是只有一条select语句,那么实际上,在我这个版本中(5.2.0版本)已经默认开启二级缓存了,如果我们将开启二级缓存的值设置为false,那么将会有两条select语句。

第二次使用使用新的session并调用get()方法查询时,还是会把查询出来的对象放到此session的一级缓存中,当然,也会放在二级缓存中,放置的顺序为:先放置到一级缓存中,再放置到二级缓存中。在缓存中查询的顺序为:先到一级缓存中查询,如果没找到,再到二级缓存中查询。

//启动事务
trans=session.beginTransaction();
Student student=(Student)session.get(Student.class, "2015");
System.out.println(student);


student=(Student)session.get(Student.class, "2015");
System.out.println(student);

trans.commit();

上面程序,我们第一次查询之后没有提交事务,那么便没有清理缓存,第二次再查询时,也还是先到一级缓存中查找(一级缓存中有此对象)。

我们在设置类对象的并发访问策略时,可以将其设置的类对应的映射文件中,也可以设置在核心配置文件里。通过以下方式:

<class-cache usage="read-only" class="com.li.pojo.Student"/>

因为我们设置的缓存访问策略为 “read-only”,所以当我们从试图修改二级缓存中取出的数据时,将会报错,如下:

//启动事务1
trans=session.beginTransaction();
Student student=(Student)session.get(Student.class, "2015");
System.out.println(student);
//自动关闭session1
trans.commit();

session=HibernateSessionFactory.getSession();
//启动事务2
trans=session.beginTransaction();
student=(Student)session.get(Student.class, "2015");
student.setName("Lee");
System.out.println(student);
//自动关闭session2
trans.commit();

报错:Can’ t write to a readonly object

此时我们如果需要,可以将访问策略修改为 “read-write”,当然我们一般设置为只读就可以了,不需要更新二级缓存。

Query查询方式使用二级缓存

Hibernate中的Query查询方式是一种对象查询方式,我们这里使用的是5.2.0版本的Query查询

5.2.0版本中,推荐使用 org.hibernate.query.Query,之前的 org.hibernate.Query已经 deprecateed了

//启动事务1
trans=session.beginTransaction();
Query<Student> query=session.createQuery("from Student", Student.class);

List<Student> list=query.getResultList();
System.out.println(list.size());
trans.commit();

session=HibernateSessionFactory.getSession();
//启动事务2
trans=session.beginTransaction();
query=session.createQuery("from Student", Student.class);
list=query.getResultList();
System.out.println(list.size());
//自动关闭session2
trans.commit();

运行结果:

Hibernate: select student0_.STU_NO as STU_NO1_0_, student0_.STU_NAME as STU_NAME2_0_, student0_.STU_GENDER as STU_GEND3_0_, student0_.STU_AGE as STU_AGE4_0_ from STU_INFO student0_
2
Hibernate: select student0_.STU_NO as STU_NO1_0_, student0_.STU_NAME as STU_NAME2_0_, student0_.STU_GENDER as STU_GEND3_0_, student0_.STU_AGE as STU_AGE4_0_ from STU_INFO student0_
2

可以看出,这里使用了两次 select 语句,并找出了两条数据库中的记录,所以,这里的Query查询并没有使用到二级缓存,要Query查询使用二级缓存,我们还需做些配置,在核心配置文件中配置开启查询缓存功能:

<!-- 配置Hibernate二级缓存的实现CacheProvide -->
<property name="hibernate.cache.region.factory_class">org.hibernate.cache.ehcache.EhCacheRegionFactory</property>
<!-- 开始二级缓存 -->
<property name="hibernate.cache.use_second_level_cache">true</property>
<!-- 开启查询缓存功能Query和Criteria查询,如:getResultList() -->
<property name="hibernate.cache.use_query_cache">true</property>

并且还要在代码中使用 query.setCacheable(true); 开启查询缓存,如下:

//启动事务1
trans=session.beginTransaction();
Query<Student> query=session.createQuery("from Student", Student.class);
/*设置使用二级缓存*/
query.setCacheable(true);
List<Student> list=query.getResultList();
System.out.println(list.size());
trans.commit();

session=HibernateSessionFactory.getSession();
//启动事务2
trans=session.beginTransaction();
query=session.createQuery("from Student", Student.class);
/*设置使用二级缓存*/
query.setCacheable(true);
list=query.getResultList();
System.out.println(list.size());
//自动关闭session2
trans.commit();

运行结果:

Hibernate: select student0_.STU_NO as STU_NO1_0_, student0_.STU_NAME as STU_NAME2_0_, student0_.STU_GENDER as STU_GEND3_0_, student0_.STU_AGE as STU_AGE4_0_ from STU_INFO student0_
2
2

可以看到,程序只在数据库中做了一次查询,第二次是在二级缓存中查询的。

集合查询使用二级缓存

上面我使用到的都是单一的实体对象,然而有时候我们的实体类之间的关联关系有一对多或者多对多,这个时候我们还要使用二级缓存的话,还需不同的配置。

这里我使用的是 Order(订单) 和 OrderLine(订单项) 作为例子,它们是一对多(one-to-many)的关系。

  1. 先在映射文件中修改类使用二级缓存,注意两个类都要修改,并且在集合配置中也需要修改。

    OrderLine.xml:访问策略为 “read-only”

    <?xml version="1.0" encoding="UTF-8"?>
    <!DOCTYPE hibernate-mapping PUBLIC
        "-//Hibernate/Hibernate Mapping DTD 3.0//EN"
        "http://www.hibernate.org/dtd/hibernate-mapping-3.0.dtd">
    
    <hibernate-mapping package="com.li.association.one2many.pojo">
        <class name="OrderLine" table="T_ORDERLINE">
            <cache usage="read-only"/>
            <id name="id" type="long" column="ID"></id>
            <property name="price" type="double" column="PRICE"></property>
            <property name="quantity" type="long" column="QUANTITY"></property>
            <property name="product" type="string" column="PRODUCT"></property>
    
            <many-to-one name="order" column="ORDER_ID"></many-to-one>
        </class>
    </hibernate-mapping>

    Order.xml:访问策略为 “read-only”(注意修改集合中使用二级缓存)

    <?xml version="1.0" encoding="UTF-8"?>
    <!DOCTYPE hibernate-mapping PUBLIC
        "-//Hibernate/Hibernate Mapping DTD 3.0//EN"
        "http://www.hibernate.org/dtd/hibernate-mapping-3.0.dtd">
    
    <hibernate-mapping package="com.li.association.one2many.pojo">
        <class name="Order" table="T_ORDER">
            <cache usage="read-only"/>
            <id name="id" type="long" column="ID"></id>
            <property name="orderedDate" type="date" column="ORDERED_DATE"></property>
            <property name="shippedDate" type="date" column="SHIPPED_DATE"></property>
            <property name="total" type="double" column="TOTAL"></property>
    
            <bag name="orderLines" cascade="all" inverse="true">
                <cache usage="read-only"/>
                <key column="ORDER_ID"/>
                <one-to-many class="OrderLine"/>
            </bag>
        </class>
    </hibernate-mapping>

    或者我们也可以在核心配置文件中来配置类和集合使用二级缓存,两者取其一即可

    <session-factory>
        <class-cache usage="read-only" class="com.li.association.one2many.pojo.Order"/>
        <class-cache usage="read-only" class="com.li.association.one2many.pojo.OrderLine"/>
        <collection-cache usage="read-only" collection="com.li.association.one2many.pojo.Order.OrderLines"/>
    </session-factory>
  2. 修改 ehcache.xml 文件配置类使用二级缓存,注意,还需配置类中的集合使用二级缓存

        <cache name="com.li.association.one2many.pojo.Order"
        maxElementsInMemory="10000"
        eternal="false"
        timeToIdleSeconds="10"
        timeToLiveSeconds="120"
        overflowToDisk="true"
        />
    
    <cache name="com.li.association.one2many.pojo.Order.OrderLines"
        maxElementsInMemory="10000"
        eternal="false"
        timeToIdleSeconds="10"
        timeToLiveSeconds="120"
        overflowToDisk="true"
        />    
    
    <cache name="com.li.association.one2many.pojo.OrderLine"
        maxElementsInMemory="10000"
        eternal="false"
        timeToIdleSeconds="10"
        timeToLiveSeconds="120"
        overflowToDisk="true"
        />   

测试

//启动事务1
trans=session.beginTransaction();
Order order=(Order)session.get(Order.class, 991L);
System.out.println(order);
trans.commit();

session=HibernateSessionFactory.getSession();
//启动事务2
trans=session.beginTransaction();
order=(Order)session.get(Order.class, 991L);
System.out.println(order);
//自动关闭session2
trans.commit();

运行输出:

Hibernate: select order0_.ID as ID1_5_0_, order0_.ORDERED_DATE as ORDERED_2_5_0_, order0_.SHIPPED_DATE as SHIPPED_3_5_0_, order0_.TOTAL as TOTAL4_5_0_ from T_ORDER order0_ where order0_.ID=?
Hibernate: select orderlines0_.ORDER_ID as ORDER_ID5_6_0_, orderlines0_.ID as ID1_6_0_, orderlines0_.ID as ID1_6_1_, orderlines0_.PRICE as PRICE2_6_1_, orderlines0_.QUANTITY as QUANTITY3_6_1_, orderlines0_.PRODUCT as PRODUCT4_6_1_, orderlines0_.ORDER_ID as ORDER_ID5_6_1_ from T_ORDERLINE orderlines0_ where orderlines0_.ORDER_ID=?
Order [id=991, orderedDate=2018-06-02, shippenDate=2018-06-05, total=741.0, orderLines=[OrderLine [id=1891, price=123.5, quantity=3, product=milk], OrderLine [id=48916, price=123.5, quantity=3, product=book]]]
Order [id=991, orderedDate=2018-06-02, shippenDate=2018-06-05, total=741.0, orderLines=[OrderLine [id=1891, price=123.5, quantity=3, product=milk], OrderLine [id=48916, price=123.5, quantity=3, product=book]]]

虽然有两条select语句,但实际上只从数据库中查询了一次,第二次从二级缓存中查找。

Hibernate的二级缓存机制

如果在事务范围的缓存(一级缓存)中没有查找到相应的数据,还可以到进程范围或集群范围的缓存(二级缓存)内查询,如果进程范围或集群范围的缓存内也没有找到该数据,那么只好去数据库中查找。

猜你喜欢

转载自blog.csdn.net/sinat_37976731/article/details/80663708