章节索引 :

Hibernate 性能之缓存

1. 前言

本节课程和大家一起聊聊性能优化方案之:缓存。通过本节课程学习,你将了解到:

  • 什么是缓存,缓存的作用;
  • HIbernate 中的缓存级别;
  • 如何使用缓存。

2. 缓存

2.1 缓存是什么

现实世界里,缓存是一个无处不在的概念。

家里的米桶中都会储存大米,需要下锅时,直接从米桶里拿出来,而不是等米下锅时去商店采购。只有等到米桶中没有米时,才会去商店。

米桶就是一个类似于缓存的存储体,它的作用是用来缓存大米。

程序中,通俗讲,缓存就是一个用来临时存储数据的地方,便于需要时伸手便可拿到。

更专业上讲,缓存可以在两个速度不匹配的设备之间建立一个缓冲带,适配两者速度。

2.2 Hibernate 中的为什么需要缓存

要搞清楚 Hibernate 为什么需要缓存,那就要了解 Hibernate 使用缓存做什么?

Hibernate 的任务是帮助开发者发送 SQL 语句,从数据库中获取数据。

这个过程并不轻松。从微观角度上讲,Hibernate 要背上行李,通过纵横交织的网络交通,到达数据库服务器,获取数据。然后背起数据,继续行走在四通八达的网络交通,回到程序中。

运气不好时,碰到网络拥堵,就会产生延迟,遇到网络断线,则会丢失数据。

理论上讲,对于每次的数据请求,这个过程都是必须的。

但是,如果多次的请求是同样数据的时候,也就是用户的请求 SQL 是一样的时候,有必要这么不停地来往于数据库服务器吗?

面对这种情况,Hibernate 提供的缓存就起作用了,可以缓存曾经从数据库中获取过的数据。如果下次再需要时,只需要从缓存中获取,而无需翻山涉水,通过网络获取。

图片描述

Hibernate 的缓存主要是存储曾经操作过的数据,程序逻辑向 Hibernate 发送数据请求操作时,Hibernate 会先查询缓存中有没有,如果存在,则直接从缓存中获取,没有时,才会行走于网络通道,从数据库中获取。

3. Session 缓存

Hibernate 提供有一级和二级缓存,一级缓存也叫 Session 缓存,二级缓存也叫 SessionFactory 缓存。

前面课程中和大家聊过,Session 的使用原则是,需要时创建,用完后关闭,其作用域一般为方法级别。

一级缓存的生命周期和 Session 是一致的,所以,一级缓存中所存储的数据其生命周期也不长,其实际意义就论情况来看了。

SessionFactory 在前面也讨论过,SessionFactory 是应用程序级别的生命周期,所以与其关联的缓存中所保存的数据也可以长时间存在。

默认情况下,Hibernate 的一级缓存是可以直接使用的,二级缓存是没有打开的。需要根据实际情况进行选择。

验证一级缓存

需求:在 Session 关闭之前,连续查询相同的学生两次。

Session session = sessionFactory.openSession();
Transaction transaction = null;
Student stu = null;
try {
	transaction = session.beginTransaction();
	stu = (Student) session.get(Student.class, new Integer(1));
	System.out.println(stu.getStuName());
	// 查询前面查询过的学生
	System.out.println("--------------第二次查询------------------");
	stu = (Student) session.get(Student.class, new Integer(1));
	System.out.println(stu.getStuName());
	transaction.commit();
} catch (Exception e) {
	transaction.rollback();
} finally {
	session.close();
}

运行结果如下:

Hibernate: 
    select
        student0_.stuId as stuId1_3_0_,
        student0_.classRoomId as classRoo6_3_0_,
        student0_.stuName as stuName2_3_0_,
        student0_.stuPassword as stuPassw3_3_0_,
        student0_.stuPic as stuPic4_3_0_,
        student0_.stuSex as stuSex5_3_0_ 
    from
        Student student0_ 
    where
        student0_.stuId=?
Hibernate
--------------第二次查询------------------
Hibernate

从输出结果中能得到什么结论?

只有在第一次查询的时候,Hibernate 才会向数据库发送 SQL 语句请求,第二查询时,不需要再发送 SQL 请求,因为缓存中已经存在。

稍微改动一下上述实例,创建两个 Session 对象,用来查询同一个学生:

Session session = sessionFactory.openSession();
Transaction transaction = null;
Student stu = null;
try {
	transaction = session.beginTransaction();
	System.out.println("--------------第一次查询------------------");
	stu = (Student) session.get(Student.class, new Integer(1));
	System.out.println(stu.getStuName());
} catch (Exception e) {
	transaction.rollback();
} finally {
	session.close();
}

session = sessionFactory.openSession();
try {
	transaction = session.beginTransaction();
	// 查询前面查询过的学生
	System.out.println("--------------第二次查询------------------");
	stu = (Student) session.get(Student.class, new Integer(1));
	System.out.println(stu.getStuName());
	transaction.commit();
} catch (Exception e) {
	transaction.rollback();
} finally {
	session.close();
}

查看控制台上的输出结果:

Hibernate: 
    select
        student0_.stuId as stuId1_3_0_,
        student0_.classRoomId as classRoo6_3_0_,
        student0_.stuName as stuName2_3_0_,
        student0_.stuPassword as stuPassw3_3_0_,
        student0_.stuPic as stuPic4_3_0_,
        student0_.stuSex as stuSex5_3_0_ 
    from
        Student student0_ 
    where
        student0_.stuId=?
Hibernate
--------------第二次查询------------------
Hibernate: 
    select
        student0_.stuId as stuId1_3_0_,
        student0_.classRoomId as classRoo6_3_0_,
        student0_.stuName as stuName2_3_0_,
        student0_.stuPassword as stuPassw3_3_0_,
        student0_.stuPic as stuPic4_3_0_,
        student0_.stuSex as stuSex5_3_0_ 
    from
        Student student0_ 
    where
        student0_.stuId=?
Hibernate

每次查询都会发送 SQL 请求,这是因为 Session 缓存中的数据只能提供给本 Session 对象使用。不能跨 Session 使用。

  • 当调用 save ()、update () 或 saveOrUpdate () 方法传递一个对象时,或使用 load ()、 get ()、list ()、iterate () 方法获得一个对象时,该对象都将被加入到 Session 的内部缓存中;
  • 可以通过调用 close()、clear()、evict() 方法手工清空缓存中的数据。

前面说过的,Session 生命周期很短,与 Session 关联的一级缓存的生命周期也很短,所以缓存的命中率是很低的。其对系统性能的改善也有限得很。Session 内部缓存的主要作用是保持 Session 内部数据状态同步。

4. SessionFactory 缓存

SessionFactory 缓存也称其为二级缓存,是应用程序级别的缓存。二级缓存在默认情况下是没有启动的,如果开发者想使用二级缓存所提供的功能,则需要通过一系列的操作流程方能让其现身。

Hibernate 本身也提供有二级缓存的功能模块,但只建议用于测试或学习过程。对于生产环境,Hibernae 建议使用专业的第三方缓存框架,如 EhCache 缓存框架。

常用缓存框架:

  • EhCache;
  • OSCache;
  • SwarmCache;
  • JBossCache。

启动二级缓存

  1. Hibernate 的主配置文件中启动并指定二级缓存的实现者;
<property name="cache.use_structured_entries">true</property>
<property name="cache.region.factory_class">org.hibernate.cache.ehcache.EhCacheRegionFactory</property>
  1. 添加 EhCache 相关的 JAR 包。这些 JAR 包都可以在下载的 Hibernate 框架包的 lib 文件夹中找到;
  • ehcache-core-2.4.3.jar;
  • hibernate-ehcache-4.2.0.Final.jar。
  1. 在项目的 src 中添加 EhCache 缓存框架的配置文件 ehcache.xml

这个配置文件可以在下载的 Hibernate 框架包中的 project 目录下的 etc 中找到。此配置文件中的内容用来配置缓存管理相关信息。

<ehcache>  
<diskStore path="java.io.tmpdir"/>  
<defaultCache  
        maxElementsInMemory="10000"  
        eternal="false"  
        timeToIdleSeconds="120"  
        timeToLiveSeconds="120"  
        overflowToDisk="true"  
        />  
</ehcache>  

配置说明:

  • maxElementsInMemory: 缓存最大数目;
  • eternal : 缓存是否持久;
  • overflowToDisk : 是否保存到磁盘,当系统当机时;
  • timeToIdleSeconds : 当缓存闲置 n 秒后销毁;
  • timeToLiveSeconds : 当缓存存活 n 秒后销毁。
  1. 在需要缓存的实体类上添加 @cache 注解
@Cache(usage=CacheConcurrencyStrategy.TRANSACTIONAL,include="all",region="student")  

只有被 @Cache 注解的实体才会被存储进二级缓存中,此注解有一个 usage 属性,用来配置缓存的策略,是一个枚举类型,有如下几种选择:

  • CacheConcurrencyStrategy.NONE
  • CacheConcurrencyStrategy.NONSTRICT_READ_WRITE: 非严格读写缓存;
  • CacheConcurrencyStrategy.READ_ONLY: 只读缓存;
  • CacheConcurrencyStrategy.READ_WRITE: 读写缓存;
  • CacheConcurrencyStrategy.TRANSACTIONAL: 事务缓存。

Region 指定二级缓存中的区域名,默认为类或者集合的名字。
include 有几个选项,non-lazy 当属性延迟抓取打开时,标记为 lazy=“true” 的实体的属性可能无法被缓存。

做完上面的事情后,再执行前面的两个 Session 对象查询同一个学生的代码,再查看控制台上的信息:

Hibernate: 
    select
        student0_.stuId as stuId1_1_0_,
        student0_.classRoomId as classRoo5_1_0_,
        student0_.stuName as stuName2_1_0_,
        student0_.stuPassword as stuPassw3_1_0_,
        student0_.stuSex as stuSex4_1_0_ 
    from
        Student student0_ 
    where
        student0_.stuId=?
学生姓名:Hibernate
--------------第二次查询------------------
学生姓名:Hibernate

第一次查询时,需要发送 SQL 请求,第二查询时,不再发送 SQL 请求,因为查询过的信息已经被存储在了二级缓存中,Hibernate 会直接从缓存查询。

二级缓存并不支持缓存 Blob 类型的数据。

5. 小结

本节课和大家一起了解了 Hibernate 提供的缓存机制,Hibernate 提供了一级缓存和二级缓存。一级缓存因生命周期较短,主要用于内部服务。二级缓存因生命周期较长,命中率会较高,缓存中一般存放经常被访问、改动不频繁、数量有限的数据。

有了缓存机制的加持,HIbernate 在响应开发者的请求时,又会少了许多延迟。速度对于程序来讲,是一个重要的性能指标。