Hibernate学习笔记 | 解析二级缓存

缓存

计算机领域非常通用的概念,它介于应用程序和永久性数据存储源(如硬盘上的文件或者数据库)之间,其作用是降低应用程序直接读写永久性数据存储源的频率,从而提高应用的运行性能,缓存中的数据是数据存储源中数据的拷贝。
缓存的物理介质通常是内存。

Hibernate提供的两种级别的缓存:

  • 第一级别的缓存:Session级别的缓存
    它是属于事务范围的缓存,这一级别的缓存是由Hibernate管理的。
  • 第二级别的缓存:SessionFactory级别的缓存
    它是属于进程范围的缓存。

SessionFactory级别的缓存

SessionFactory级别的缓存可以分为两类

  • 内置缓存
    Hibernate自带的,不可卸载的,通常在Hibernate的初始化阶段,Hibernate会把映射元数据和预定义的SQL语句放到SessionFactory的缓存中,映射元数据是映射文件中的数据(.hbm.xml文件中的数据)的复制,该内置缓存是只读的。

  • 外置缓存(二级缓存)
    一个可配置的缓存插件。在默认情况下,SessionFactory不会启用这个缓存插件,外置缓存中的数据是数据库数据的复制,外置缓存的物理介质可以是内存或硬盘。


Hibernate的二级缓存

适合放入二级缓存中的数据:

  • 很少被修改
  • 不是很重要的数据,允许出现偶尔的并发问题

不适合放入二级缓存中的数据:

  • 经常被修改
  • 财务数据,绝对不允许出现并发问题
  • 与其他应用程序共享的数据

二级缓存的并发访问策略

两个并发的事务同时访问持久层的缓存的相同数据时,也有可能出现各类并发问题。

二级缓存可以设定以下4种类型的并发访问策略,每一种访问策略对应一种事务隔离级别

  • 非严格读写(Nostrict-read-write)
    不保证缓存与数据库中数据的一致性,提供Read Uncommited事务隔离级别,对于极少被修改,而且允许脏读的数据,可以采用这种策略。
  • 读写型(Read-write)
    提供Read Commited数据隔离级别,对于经常读但是很少被修改的数据,可以采用这种隔离类型。因为它可以防止脏读。
  • 事务型(Transactional)
    仅在受管理环境下适用,它提供了Repeatable Read事务隔离级别,对于经常读但是很少被修改的数据,揭阳采用这种隔离类型,因为它可以防止脏读和不可重复读。
  • 只读型(Read-Only)
    提供了Serializable数据隔离级别,对于从来不会被修改的数据,可以采用这种访问策略。

配置进程范围内的二级缓存的步骤

  • 加入二级缓存插件的jar包及配置文件
    jar包有:ehcache-2.10.6.jar,hibernate-ehcache-5.4.3.Final.jar,slf4j-api-1.7.25.jar
    配置文件ehcache.xml如下:
<ehcache>
    <diskStore path="java.io.tmpdir"/>

    <defaultCache
        maxElementsInMemory="10000"
        eternal="false"
        timeToIdleSeconds="120"
        timeToLiveSeconds="120"
        overflowToDisk="true"
        />

    <cache name="sampleCache1"
        maxElementsInMemory="10000"
        eternal="false"
        timeToIdleSeconds="300"
        timeToLiveSeconds="600"
        overflowToDisk="true"
        />

    <cache name="sampleCache2"
        maxElementsInMemory="1000"
        eternal="true"
        timeToIdleSeconds="0"
        timeToLiveSeconds="0"
        overflowToDisk="false"
        /> 
</ehcache>

  • 配置Hibernate.cfg.cml文件
    配置启用hibernate的二级缓存:<property name="cache.use_second_level_cache">true</property>
    配置Hibernate二级缓存使用的产品:<property name="hibernate.cache.region.factory_class">org.hibernate.cache.ehcache.internal.EhcacheRegionFactory</property>
    配置对哪些类使用Hibernate的二级缓存:<class-cache class="com.cerr.hibernate.entities.Employee" usage="read-write" />

实际上也可以在.hbm.xml文件中配置对哪些类使用二级缓存及二级缓存的策略。例如在Employee.hbm.xml文件的class节点里面加上<cache usage="read-write"/>

设置二级缓存之后,测试如下:

@org.junit.Test
    public void testHibernateSecondLevelCache(){
        Employee employee = session.get(Employee.class,1);
        System.out.println(employee.getName());
        //关闭session后重新连接
        transaction.commit();
        session.close();
        session = sessionFactory.openSession();
        transaction = session.beginTransaction();
        Employee employee1 = session.get(Employee.class,1);
        System.out.println(employee1.getName());

    }

在没设置二级缓存的时候,该情形会发送两次sql语句,因为在中间关闭了session后再重新开启。而我们已经设置了二级缓存,因此只会发送一条sql语句。

集合级别的二级缓存的配置

  • 配置对集合使用二级缓存,例如:<collection-cache collection="com.cerr.hibernate.entities.Department.emps" usage="read-write" />,也可以在hbm.xml文件中使用<cache>标签进行配置。
  • 还需要配置集合中的元素对应的持久化类也使用二级缓存,否则将会多出n条SQL语句。

以Department为例,配置文件hibernate.cfg.xml如下:

<?xml version='1.0' encoding='utf-8'?>
<!DOCTYPE hibernate-configuration PUBLIC
        "-//Hibernate/Hibernate Configuration DTD//EN"
        "http://www.hibernate.org/dtd/hibernate-configuration-3.0.dtd">
<hibernate-configuration>
    <session-factory>
        <!-- 配置连接数据库的基本信息 -->
        <property name="connection.driver_class">com.mysql.jdbc.Driver</property>
        <property name="connection.url">jdbc:mysql://localhost:3306/hibernate5</property>
        <property name="connection.username">root</property>
        <property name="connection.password">root</property>

        <!-- 配置hibernate的基本信息-->
        <!-- hibernate所使用的的数据库方言 -->
        <property name="dialect">org.hibernate.dialect.MySQL5Dialect</property>
        <!-- 执行操作时是否在控制台打印SQL-->
        <property name="show_sql">true</property>
        <!-- 是否对SQL进行格式化-->
        <property name="format_sql">true</property>
        <!-- 指定生成数据表的策略-->
        <property name="hibernate.hbm2ddl.auto">update</property>
        <!-- 设置hibernate的事务隔离级别 -->
        <property name="connection.isolation">2</property>
        <property name="use_identifier_rollback">true</property>

        <!-- 配置C3P0数据源 -->
        <property name="hibernate.connection.provider_class">org.hibernate.c3p0.internal.C3P0ConnectionProvider</property>
        <property name="c3p0.max_size">10</property>
        <property name="c3p0.min_size">5</property>
        <property name="c3p0.acquire_increment">2</property>
        <property name="c3p0.idle_test_period">2000</property>
        <property name="c3p0.timeout">2000</property>
        <property name="c3p0.max_statements">10</property>

        <!-- 设定JDBC的Statement读取数据的时候每次从数据库中取出的记录条数-->
        <property name="hibernate.jdbc.fetch_size">100</property>
        <!-- 设定对数据库进行批量删除,更新,插入的时候批次的大小 -->
        <property name="hibernate.jdbc.batch_size">30</property>


        <!-- 启用二级缓存-->
        <property name="cache.use_second_level_cache">true</property>
        <!-- 配置使用的二级缓存的产品 -->
        <property name="hibernate.cache.region.factory_class">org.hibernate.cache.ehcache.internal.EhcacheRegionFactory</property>

        <mapping resource="com/cerr/hibernate/entities/Employee.hbm.xml"/>
        <mapping resource="com/cerr/hibernate/entities/Department.hbm.xml"/>

        <class-cache class="com.cerr.hibernate.entities.Employee" usage="read-write" />
        <class-cache class="com.cerr.hibernate.entities.Department" usage="read-write" />
        <!-- 对Department的集合使用二级缓存-->
        <collection-cache collection="com.cerr.hibernate.entities.Department.emps" usage="read-write" />
    </session-factory>
</hibernate-configuration>

测试:

@org.junit.Test
    public void testCollectionSecondLevelCache(){
        Department department = session.get(Department.class,1);
        System.out.println(department.getName());
        System.out.println(department.getEmps().size());
        transaction.commit();
        session.close();
        session = sessionFactory.openSession();
        transaction = session.beginTransaction();

        Department department1 = session.get(Department.class,1);
        System.out.println(department1.getName());
        System.out.println(department1.getEmps().size());

    }

对ehcache.xml文件的解析

<ehcache>
    <!-- 指定一个目录:当EHCache把数据写到硬盘上时,将把数据写到这个目录下 -->
    <diskStore path="java.io.tmpdir"/>

    <!-- 设置缓存的默认数据过期策略 -->
    <defaultCache
        maxElementsInMemory="10000"
        eternal="false"
        timeToIdleSeconds="120"
        timeToLiveSeconds="120"
        overflowToDisk="true"
        />
    <!-- 设置具体的命名缓存的数据过期策略,每个命名缓存代表一个缓存区域-->
    <cache name="com.cerr.hibernate.entities.Employee"
        maxElementsInMemory="10000"
        eternal="false"
        timeToIdleSeconds="300"
        timeToLiveSeconds="600"
        overflowToDisk="true"
        />

    <cache name="com.cerr.hibernate.entities.Department"
        maxElementsInMemory="1000"
        eternal="true"
        timeToIdleSeconds="0"
        timeToLiveSeconds="0"
        overflowToDisk="false"
        />
</ehcache>

关于几个标签

  • <diskStore>标签
    指定一个目录:当EHCache把数据写到硬盘上时,将把数据写到这个目录下
  • <defaultCache>标签
    设置缓存的默认数据过期策略
  • <cache>标签
    设置具体的命名缓存的数据过期策略,每个命名缓存代表一个缓存区域

Hibernate在不同的缓存区域保存不同的类/集合
对于类而言,区域的名称是类名,例如:com.cerr.domain.Customer
对于集合而言,区域的名称是类名加属性名,例如:com.cerr.domain.Customer.orders

cache元素的属性

  • name:设置缓存的名字,它的取值为类的全限定名或类的集合的名字。
  • maxInMemory:设置基于内存的缓存中可存放的对象的最大数目。
  • etemal:设置对象是否为永久的,true表示永不过期,此时将忽略timeToldleSecondstimeToLiveSeconds属性:默认是false
  • timeToldleSeconds:设置对象空闲最长时间,以秒为单位,超过这个时间,对象过期。当对象过期时,EHCache会把它从缓存中清除。如果此值为0,表示对象可以无限期地处于空闲状态。
  • timeToLiveSeconds:设置对象生存最长时间,超过这个时间,对象过期。如果此值为0,表示对象可以无限期地存在于缓存中,该属性值必须大于或等于timeToldleSeconds属性值。
  • overflowToDisk:设置基于内在的缓存中的对象数目达到上限后,是否把溢出的对象写在基于硬盘的缓存中。

查询缓存

默认情况下,设置的缓存对HQL及QBC查询是无效的,但可以通过设置查询缓存来使其支持这两种查询。

设置的步骤:

  • hibernate.cfg.xml文件中声明开启查询缓存,例如:<property name="cache.use_query_cache">true</property>
  • 调用QueryCriteriasetCacheable(true)

注意:查询缓存依赖于二级缓存,即若要使用查询缓存,必须先配置二级缓存,否则无法使用。

Demo如下:

@org.junit.Test
    public void testQueryCache(){
        Query query = session.createQuery("FROM Employee ");
        //设置查询缓存
        query.setCacheable(true);

        List<Employee> employees = query.list();
        System.out.println(employees.size());

        employees = query.list();
        System.out.println(employees.size());
    }

时间戳缓存区域

时间戳缓存区域存放了对于查询结果相关的表进行插入,更新或删除操作的时间戳,Hibernate通过时间戳缓存区域来判断被缓存的查询结果是否过期,其运行过程如下:

  • T1时刻执行查询操作,把查询结果存放在QueryCache区域,记录该区域的时间戳为T1.
  • T2时刻对查询结果相关的表进行更新操作,Hibernate把T2时刻存放在UpdateTimestampCache区域。
  • T3时刻执行查询结果前,先比较QueryCache区域的时间戳和UpdateTimestampCache区域的时间戳,若T2>T1,那么就丢弃原先存放在QueryCache区域的查询结果,重新到数据库中查询数据,再把结果存放到QueryCache区域;若T2<T1,直接从QueryCache中获得查询结果。

Query接口的iterate()方法

list()一样也能执行查询操作。
list()执行的SQL语句包含实体类对应的数据表的所有字段。
iterator()执行的SQL语句中仅包含实体类对应的数据表的ID字段
当遍历访问结果集时,该方法先到Session缓存及二级缓存中查看是否存在特定OID的对象,如果存在,就直接返回该对象,如果不存在该对象就通过相应的SQL Select语句到数据库中加载特定的实体对象

大多数情况下,应考虑使用list()执行查询操作,iterator()仅仅在满足以下条件的场合,可以稍微提高查询性能:

  • 要查询的数据表中包含大量字段
  • 启用了二级缓存,且二级缓存中可能已经包含了待查询的对象。
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 215,539评论 6 497
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 91,911评论 3 391
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 161,337评论 0 351
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 57,723评论 1 290
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 66,795评论 6 388
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,762评论 1 294
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,742评论 3 416
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,508评论 0 271
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,954评论 1 308
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,247评论 2 331
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,404评论 1 345
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,104评论 5 340
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,736评论 3 324
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,352评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,557评论 1 268
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,371评论 2 368
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,292评论 2 352

推荐阅读更多精彩内容

  • 一、Hibernate 缓存 缓存(Cache): 计算机领域非常通用的概念。它介于应用程序和永久性数据存储源(如...
    leeqico阅读 497评论 0 0
  • Hibernate是一个开放源代码的对象关系映射框架,它对JDBC进行了非常轻量级的对象封装,它将POJO与数据库...
    兰缘小妖阅读 1,202评论 1 18
  • 1.JVM 堆内存和非堆内存 堆和非堆内存按照官方的说法:“Java 虚拟机具有一个堆(Heap),堆是运行时数据...
    yanzhu728阅读 906评论 0 0
  • Hibernate 二级缓存 Hibernate 二级缓存 总结整理 可以在进程或集群的级别上为事务之间可重用的数...
    WesleyLien阅读 387评论 0 0
  • 邓茜(泉州九中初二)原创 按兵不动 黯景愁庭月盛情, 冰寒假树翠竹亭。 不征万战东风沁, 冻乱苍茫苦坠盈。 邓茜 14
    慧益大师阅读 109评论 0 1