Hibernate缓存策略

了解缓存

什么是缓存

缓存是指为了降低应用程序对物理数据源访问的频次,从而提高应用程序的运行性能的一种策略。

为什么使用缓存

  1. ORM框架访问数据库的效率直接影响应用程序的运行速度,提升和优化ORM框架的执行效率至关重要
  2. Hibernate的缓存是提升和优化Hibernate执行效率的重要手段,所以学会Hibernate缓存的使用和配置是优化的关键

Hibernate一级缓存

介绍Hibernate一级缓存

  1. Hibernate一级缓存又称为“Session缓存”、“会话级缓存”
  2. 通过Session从数据库查询实体时会把实体在内存中存储起来,下一次查询同一实体时不再从数据库获取,而从内存中获取
  3. 一级缓存的生命周期和Session相同;Session销毁,它也销毁
  4. 一级缓存中的数据可适用范围在当前会话之内

Hibernate一级缓存的API

一级缓存无法取消,用两个方法管理
evict():用于将某个对象从Session的一级缓存中清楚
clear():用于将一级缓存中的所有对象全部清楚

代码测试

同一个Session进行重复查询

@Test
public void oneLevelTest() {
    // 使用同一个Session进行重复查询
    Student student = (Student) session.get(Student.class, 1);
    System.out.println(student.getSname());
    
    Student student2 = (Student) session.get(Student.class, 1);
    System.out.println(student2.getSname());
}

这里我们先观察控制台的输出:

Hibernate: select student0_.SID as SID1_1_0_, student0_.SNAME as SNAME2_1_0_, student0_.sex as sex3_1_0_, student0_.gid as gid4_1_0_ from student student0_ where student0_.SID=?;
Rose
Rose

可以看到,在使用同一个Session进行重复查询的时候,只进行了一次SQL查询,就可以打印出两次。可以说明第二次查询是使用了缓存的

不同Session进行重复查询

@Test
public void oneLevelTest2() {
    // 使用不同Session进行重复查询    
    Session session = HibernateUtil.getSession();
    Student student = (Student) session.get(Student.class, 1);
    System.out.println(student.getSname());
    HibernateUtil.closeSession(session);
    
    Session session2 = HibernateUtil.getSession();
    Student student2 = (Student) session2.get(Student.class, 1);
    System.out.println(student2.getSname());
    HibernateUtil.closeSession(session2);       
}
Hibernate: select student0_.SID as SID1_1_0_, student0_.SNAME as SNAME2_1_0_, student0_.sex as sex3_1_0_, student0_.gid as gid4_1_0_ from student student0_ where student0_.SID=?
Rose
Hibernate: select student0_.SID as SID1_1_0_, student0_.SNAME as SNAME2_1_0_, student0_.sex as sex3_1_0_, student0_.gid as gid4_1_0_ from student student0_ where student0_.SID=?
Rose

这里进行了两次查询,说明第二次的查询没有使用到缓存。这正是因为两次查询使用的不是同一个Session

query.list() 和 query.iterate()

在这里分别使用query.list() 和 query.iterate()进行查询,然后再观察控制台的输出。

单独使用query.list()

@Test
public void oneLevelTest3() {
    // 使用query.list()进行查询   
    Session session = HibernateUtil.getSession();
    Query query = session.createQuery("FROM Student");
    List<Student> list = query.list();
    for (Student student : list) {
        System.out.println(student.getSname());
    }
    HibernateUtil.closeSession(session);    
}
Hibernate: select student0_.SID as SID1_1_, student0_.SNAME as SNAME2_1_, student0_.sex as sex3_1_, student0_.gid as gid4_1_ from student student0_
Rose
Jack

可以看到,这里只进行了一次查询就查询出了所有的Student。

单独使用query.iterate()

接下来看看query.iterate()进行查询

@Test
public void oneLevelTest4() {
    // 使用query.iterate()进行查询    
    Session session = HibernateUtil.getSession();
    Query query = session.createQuery("FROM Student");
    Iterator iterate = query.iterate();
    while(iterate.hasNext()) {
        Student student = (Student) iterate.next();
        System.out.println(student.getSname());
    }
    HibernateUtil.closeSession(session);
}
Hibernate: select student0_.SID as col_0_0_ from student student0_
Hibernate: select student0_.SID as SID1_1_0_, student0_.SNAME as SNAME2_1_0_, student0_.sex as sex3_1_0_, student0_.gid as gid4_1_0_ from student student0_ where student0_.SID=?
Rose
Hibernate: select student0_.SID as SID1_1_0_, student0_.SNAME as SNAME2_1_0_, student0_.sex as sex3_1_0_, student0_.gid as gid4_1_0_ from student student0_ where student0_.SID=?
Jack

这里输出了三条语句。第一条是查询出所有学生的id,然后在遍历输出学生信息的时候再根据id去查询具体的学生信息。这里因为表中只有两个学生,所以这里就只做了两次单独的查询

先使用query.list()再使用query.iterate()

@Test
public void oneLevelTest5() {
    // 先使用query.list()再使用query.iterate()
    Session session = HibernateUtil.getSession();
    Query query = session.createQuery("FROM Student");
    List<Student> list = query.list();
    for (Student student : list) {
        System.out.println(student.getSname());
    }
    System.out.println("-------------");
    Iterator iterate = query.iterate();
    while(iterate.hasNext()) {
        Student student = (Student) iterate.next();
        System.out.println(student.getSname());
    }
    HibernateUtil.closeSession(session);    
}
Hibernate: select student0_.SID as SID1_1_, student0_.SNAME as SNAME2_1_, student0_.sex as sex3_1_, student0_.gid as gid4_1_ from student student0_
Rose
Jack
-------------
Hibernate: select student0_.SID as col_0_0_ from student student0_
Rose
Jack

这里一共输出了两行sql。第一行是采用query.list()进行查询时输出的,它查询了所有的Student信息。 第二条是query.iterate()进行查询时输出的,可以看到这条语句仅查询出了所有Student的id,然后便输出了学生的信息。 因为可以推断出,这是的查询是使用了缓存的。

先使用query.iterate()再使用query.list()

@Test
public void oneLevelTest6() {
    // 先使用query.iterate()再使用query.list()
    Session session = HibernateUtil.getSession();
    Query query = session.createQuery("FROM Student");
    Iterator iterate = query.iterate();
    while(iterate.hasNext()) {
        Student student = (Student) iterate.next();
        System.out.println(student.getSname());
    }
    System.out.println("-------------");
    List<Student> list = query.list();
    for (Student student : list) {
        System.out.println(student.getSname());
    }
    HibernateUtil.closeSession(session);    
}
Hibernate: select student0_.SID as col_0_0_ from student student0_
Hibernate: select student0_.SID as SID1_1_0_, student0_.SNAME as SNAME2_1_0_, student0_.sex as sex3_1_0_, student0_.gid as gid4_1_0_ from student student0_ where student0_.SID=?
Rose
Hibernate: select student0_.SID as SID1_1_0_, student0_.SNAME as SNAME2_1_0_, student0_.sex as sex3_1_0_, student0_.gid as gid4_1_0_ from student student0_ where student0_.SID=?
Jack
-------------
Hibernate: select student0_.SID as SID1_1_, student0_.SNAME as SNAME2_1_, student0_.sex as sex3_1_, student0_.gid as gid4_1_ from student student0_
Rose
Jack

因为是先使用query.iterate()进行的查询,所以依照之前的分析会进行三次sql的输出。随后使用query.list()进行查询,它还是像原来一样输出了一行sql。所以我们可以推测query.list()进行查询的时候是没有使用缓存的。

query.iterate()和query.list()小结

query.list()和query.iterate()区别:

  1. 返回的类型不同:
    list()返回List;iterate()返回Iterate。
  2. 查询策略不同:
    list()直接发送sql语句,查询数据库;
    iterate()发送sql语句,从数据库取出id,然后先从缓存中根据id查找对应信息,
    有就返回结果,没有就根据id发送sql语句,查询数据库。
  3. 返回对象不同:
    list()返回持久化实体类对象;
    iterate()返回代理对象。
  4. 缓存的关系不同:
    list()只缓存,但不使用缓存(查询缓存除外);
    iterate()会使用缓存。

Hibernate二级缓存

环境准备

要使用二级缓存,需要额外的引入几个jar包,和一个配置文件。
我使用的版本是:hibernate-release-4.2.4.Final,可以去官网下载相应的zip文件。里面包含了跟Hibernate相关的所有jar包了。
EHCache相关jar包:hibernate-4.2.4\hibernate-release-4.2.4.Final\lib\optional\ehcache下的所有包
EHCache配置文件:hibernate-4.2.4\hibernate-release-4.2.4.Final\project\etc下的ehcache.xml文件

映射文件修改

除了将相应的jar包和配置文件添加进来,还需要在Java实体类所对应的映射文件中添加如下配置:

<?xml version="1.0"?>
<!DOCTYPE hibernate-mapping PUBLIC 
    "-//Hibernate/Hibernate Mapping DTD 3.0//EN"
    "http://www.hibernate.org/dtd/hibernate-mapping-3.0.dtd">
<hibernate-mapping>
    <class name="com.whyalwaysmea.cache.Grade" table="grade">
        <!-- 配置二级缓存信息 -->
        <cache usage="read-only" region="Grade"/>
        <id name="gid" column="gid" type="java.lang.Integer">
            <generator class="increment"></generator>
        </id>
        <property name="gname" type="java.lang.String">
            <column name="gname" length="20" not-null="true"></column>
        </property>
        <property name="gdesc" type="java.lang.String">
            <column name="gdesc"></column>
        </property>        
        <set name="students" table="student" inverse="true" cascade="all">
            <!-- 指定关联的外键列 -->
            <key column="gid"></key>
            <one-to-many class="com.whyalwaysmea.cache.Student"/>
        </set>
    </class>
</hibernate-mapping>

测试

@Test
public void twoLevelCacheTest() {
    // 使用不同Session重复查询      
    Session session = HibernateUtil.getSession();
    Grade grade = (Grade) session.get(Grade.class, 1);
    System.out.println(grade.getGname());
    HibernateUtil.closeSession(session);
    
    Session session2 = HibernateUtil.getSession();
    Grade grade2 = (Grade) session2.get(Grade.class, 1);
    System.out.println(grade2.getGname());
    HibernateUtil.closeSession(session2);
}
Hibernate: select grade0_.gid as gid1_0_0_, grade0_.gname as gname2_0_0_, grade0_.gdesc as gdesc3_0_0_ from grade grade0_ where grade0_.gid=?
Java大神班
Java大神班

通过控制台的输出可以发现,现在使用不同的Session进行查询,只会输出一次sql查询语句了。

二级缓存相关配置

<cache />标签的详情介绍:

  • usage: 指定缓存策略,可选的策略包括:transactional, read-write, nonstrict-read-write或read-only
  • region: 指定二级缓存区域名(用于配置指定缓存属性)
  • include: 指定是否缓存延迟加载的对象。all,表示缓存所有对象;non-lazy,表示不缓存延迟加载的对象

缓存策略配置

最开始配置环境的时候,将ehcache.xml放入进来了,现在我们来看看这一个配置文件的使用

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

在映射文件中,我们指定了二级缓存区域名为"Grade",在这里我们就是使用该区域名指定它的缓存策略的。
这里对配置属性进行一下简单的介绍:

  • maxElementsInMemory: cache 中最多可以存放的元素的数量
  • eternal : 是否永驻内存。如果值是true,cache中的元素将一直保存在内存中,不会因为时间超时而丢失,所以在这个值为true的时候,timeToIdleSeconds和timeToLiveSeconds两个属性的值就不起作用了。
  • timeToIdleSeconds: 访问这个cache中元素的最大间隔时间。如果超过这个时间没有访问这个cache中的某个元素,那么这个元素将被从cache中清除。
  • timeToLiveSeconds: cache中元素的生存时间。意思是从cache中的某个元素从创建到消亡的时间,从创建开始计时,当超过这个时间,这个元素将被从cache中清除。
  • overflowToDisk: 溢出是否写入磁盘。

一二级缓存的对比

属性 一级缓存 二级缓存
缓存的范围 事务范围、每个事务都拥有单独一级缓存 应用范围,当前应用内所有事务共享
并发访问策略 不会出现并发问题 必须提供适当的并发访问策略
数据过期策略 没有数据过期策略 缓存对象的最大数目、最长时间、最长空闲时间等
缓存的软件实现 框架包含 第三方提供、可插拔集成
物理介质 内存 内存和硬盘
启用方式 默认启用、不可关闭 默认不启用、选择性开启

参考

hibernate-release-4.2.4.Final下载
Hibernate缓存策略
代码下载

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