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: selectstudent0_.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_ fromStudent student0_ wherestudent0_.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: selectstudent0_.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_ fromStudent student0_ wherestudent0_.stuId=?
Hibernate
--------------第二次查询------------------
Hibernate: selectstudent0_.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_ fromStudent student0_ wherestudent0_.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>
2. 添加 EhCache 相关的 JAR 包。这些 JAR 包都可以在下载的 Hibernate 框架包的 lib 文件夹中找到;
- ehcache-core-2.4.3.jar;
- hibernate-ehcache-4.2.0.Final.jar。
3. 在项目的 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 秒后销毁。
- 在需要缓存的实体类上添加 @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: selectstudent0_.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_ fromStudent student0_ wherestudent0_.stuId=?
学生姓名:Hibernate
--------------第二次查询------------------
学生姓名:Hibernate
第一次查询时,需要发送 SQL 请求,第二查询时,不再发送 SQL 请求,因为查询过的信息已经被存储在了二级缓存中,Hibernate 会直接从缓存查询。
二级缓存并不支持缓存 Blob 类型的数据。
5. 小结
本节和大家一起了解了 Hibernate 提供的缓存机制,Hibernate 提供了一级缓存和二级缓存。一级缓存因生命周期较短,主要用于内部服务。二级缓存因生命周期较长,命中率会较高,缓存中一般存放经常被访问、改动不频繁、数量有限的数据。
有了缓存机制的加持,HIbernate 在响应开发者的请求时,又会少了许多延迟。速度对于程序来讲,是一个重要的性能指标。