Hibernate 性能之缓存与缓存算法
1. 前言
本节课和大家一起聊聊查询缓存和缓存算法。
对于缓存的使用要有针对性,不能滥用缓存,因为缓存本身是需要占用系统资源的,缓存的维护也需要消耗系统性能。
所以,这个世界是平衡的!如何掌握平衡,多用心感悟!
通过本节课程的学习,你将了解到:
- 什么是查询缓存,如何使用查询缓存;
- 常用的缓存算法有哪些。
2. list()和 iterate()
在前面的课程里,咱们一起讲解过 Query 对象,它提供了 list() 方法,此方法能接受 HQL 语句,查询出开发者所需要的数据。
那么 list() 方法支持缓存吗?也就是说 list() 方法查询出来的数据会存储到缓存中吗?
本节课程中的缓存都是指二级缓存。
问题出来了,要找到答案很简单,编写一个实例,测试一下便知道结果 。创建 2 个 Session 对象,分别对同一个 HQL 语句进行查询:
Session session = sessionFactory.openSession();
Transaction transaction = null;
try {
transaction = session.beginTransaction();
String hql = "from Student s";
Query query = session.createQuery(hql);
System.out.println("------------------第一次查询-------------------");
List<Student> stus = query.list();
System.out.println(stus.size());
transaction.commit();
} catch (Exception e) {
transaction.rollback();
} finally {
session.close();
}
session = sessionFactory.openSession();
transaction = null;
try {
transaction = session.beginTransaction();
String hql = "from Student s";
Query query = session.createQuery(hql);
System.out.println("-----------------第二次查询--------------------");
List<Student> stus = query.list();
System.out.println(stus.size());
transaction.commit();
} catch (Exception e) {
transaction.rollback();
} finally {
session.close();
}
查看控制台上的输出结果:
Hibernate:
select
student0_.stuId as stuId1_1_,
student0_.classRoomId as classRoo5_1_,
student0_.stuName as stuName2_1_,
student0_.stuPassword as stuPassw3_1_,
student0_.stuSex as stuSex4_1_
from
Student student0_
4
-----------------第二次查询--------------------
Hibernate:
select
student0_.stuId as stuId1_1_,
student0_.classRoomId as classRoo5_1_,
student0_.stuName as stuName2_1_,
student0_.stuPassword as stuPassw3_1_,
student0_.stuSex as stuSex4_1_
from
Student student0_
4
从结果上可以看出,两次查询的 HQL 请求是相同的,但每一次都会重新发送 SQL 语句,是不是就得出结论, list() 方法与缓存无缘分呢?
结论可不要提出来的太早。
Query 还提供了一个方法 iterate() ,从功能上做比较,和 list() 没有多大区别,只是一个返回的是集合对象,一个返回的是迭代器对象,作用是一样的。
但是不是就没有其它的区别了?
不急,先了解一下 iterate() 方法的特点,用实例来说话:
Session session = sessionFactory.openSession();
Transaction transaction = null;
try {
transaction = session.beginTransaction();
String hql = "from Student s";
Query query = session.createQuery(hql);
System.out.println("------------------迭代查询-------------------");
Iterator<Student> stus = query.iterate();
while(stus.hasNext()) {
Student stu= stus.next();
System.out.println("-------------------输出结果------------------");
System.out.println("学生姓名:"+stu.getStuName());
}
transaction.commit();
} catch (Exception e) {
transaction.rollback();
} finally {
session.close();
}
截取运行后的一部分控制台上的内容展示如下:
------------------迭代查询-------------------
Hibernate:
select
student0_.stuId as col_0_0_
from
Student student0_
-------------------输出结果------------------
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
当我们执行 iterate() 方法时, Hibernate 只是把所有的学生编号(主键)返回给应用程序。也就是说并没有返回完整的学生信息。
它为什么要这么做了?
首先有一点是可以得出结论的,仅仅得到学生编号肯定比获取全部学生信息是要快很多的。
当程序中需要学生其它数据的时候,这时 Hibernate 又会跑一次数据库,根据前面获取到的学生编号构建新的条件查询,从数据库中再次获取数据。
天呀,真不闲累的慌。
为什么要这么做了?
这有点类似于延迟加载,很多时候,程序中并不急着使用数据,可能需要等某些依赖的逻辑成立后再使用。如此, iterate() 方法可快速获取主键值,并安慰开发者,你看,我是有能力帮你获取数据的。等需要更多时,我也是有能力拿到的。
Query 既提供 list() 方法,又提供 iterate() 方法不是没有出发点的。这两个方法很多时候结合起来使用,可以达到一种神奇的效果。
什么效果呢?
看一段实例:
Session session = sessionFactory.openSession();
Transaction transaction = null;
try {
transaction = session.beginTransaction();
String hql = "from Student s";
Query query = session.createQuery(hql);
System.out.println("------------------第一次使用 list()方法查询-------------------");
List<Student> stus = query.list();
System.out.println(stus.size());
transaction.commit();
} catch (Exception e) {
transaction.rollback();
} finally {
session.close();
}
session = sessionFactory.openSession();
transaction = null;
try {
transaction = session.beginTransaction();
String hql = "from Student s";
Query query = session.createQuery(hql);
System.out.println("-----------------第二次使用iterate()方法查询--------------------");
Iterator<Student> stus = query.iterate();
while (stus.hasNext()) {
Student stu = stus.next();
System.out.println("-------------------输出结果------------------");
System.out.println("学生姓名:" + stu.getStuName());
}
transaction.commit();
} catch (Exception e) {
transaction.rollback();
} finally {
session.close();
}
两者结合,交织中所碰触出的火花,你 get 到了吗?
先使用 list() 方法查询出所有学生信息, hibernate 会把 list() 方法查询出来的数据全部存储到缓存中。但是,它自己不使用缓存中自己缓存的数据,它是勤劳的小蜜蜂,无私的奉献。
谁会使用 list() 缓存的数据了?
输出结果已经告诉了我们答案, iterate() 方法会使用 list() 方法缓存的数据。
对于一条查询语句, Iterator 会先从数据库中找到所有符合条件的记录的主键 ID,再通过主键 ID 去缓存找,对于缓存中没有的记录,再构造语句从数据中查出,在缓存中没有命中的话,效率很低。
那么,怎么联合使用了?
建议在应用程序启动或修改时使用 list ,通过 list 缓存数据。需要更多数据时再使用 iterator 。
好兄弟,一辈子,江湖上,有你也有我。
3. 查询缓存
是不是 list() 方法真的就不能使用缓存,而只是作为 iterator() 身后的兄弟。
Hibernate 中提供的有查询缓存的概念。查询缓存只对 query.list() 方法起作用。查询缓存依赖于二级缓存,因此一定要打开二级缓存。而且,在默认情况下,查询缓存也是关闭的。
启动查询缓存
-
在 Hibernate 的主配置文件中添加如下配置信息:
true
切记,使用查询缓存是一定要加入下面的代码:
query.setCacheable(true);
好吧,来一个实例,看看查询缓存的威力。
Session session = sessionFactory.openSession();
Transaction transaction = null;
try {
transaction = session.beginTransaction();
String hql = "from Student s";
Query query = session.createQuery(hql);
query.setCacheable(true);
System.out.println("------------------第一次查询-------------------");
List<Student> stus = query.list();
System.out.println(stus.size());
transaction.commit();
} catch (Exception e) {
transaction.rollback();
} finally {
session.close();
}
session = sessionFactory.openSession();
transaction = null;
try {
transaction = session.beginTransaction();
String hql = "from Student s";
Query query = session.createQuery(hql);
query.setCacheable(true);
System.out.println("-----------------第二次查询--------------------");
List<Student> stus = query.list();
System.out.println(stus.size());
transaction.commit();
} catch (Exception e) {
transaction.rollback();
} finally {
session.close();
}
查看一下控制台上的输出结果:
------------------第一次查询-------------------
Hibernate:
select
student0_.stuId as stuId1_1_,
student0_.classRoomId as classRoo5_1_,
student0_.stuName as stuName2_1_,
student0_.stuPassword as stuPassw3_1_,
student0_.stuSex as stuSex4_1_
from
Student student0_
4
-----------------第二次查询--------------------
4
结论很明显,第一次使用 list() 方法时,需要发送 SQL 语句,第二次时,就不再需要了,也就是说 list() 也是可以享受自己缓存的数据。但是必须启动查询缓存,且在代码中明明确确指示出来。
4. 缓存算法
什么是缓存算法?
缓存是一个临时存储数据的地方,但是,这个地方可金贵的很,咱们可不能让那些不经常使用的、过期的数据长时间待在里面。所以,必须有一种机制能随时检查一下缓存中的数据,哪些数据是可以继续待在里面的,哪些数据需要移出去,给新来者挪出空间的,这就是所谓的缓存算法。
常用的缓存算法:
- LRU : Least Recently Used ,最近最少被使用的,每个缓存对象都记录一个最后使用时间;
- LFU : Least Frequently Used ,最近使用频率最少;
- FIFO: First in First Out ,这个简单,定时清理时,先来的,先离开。
Session 和 SessionFactory 对象也提供的有与缓存管理有关的方法,方便开发者可以随时按需清除缓存。如 evict() 等方法。
上一节课介绍 EHCache 缓存框架时,就要使用它的配置文件,其配置内容就是设置如何管理缓存。
5. 小结
好了!又到了说再见的时候了,本节课继续上一节的内容,向大家介绍了查询缓存,主要介绍了 Query 对象的 list 和 iterate 两个方法,它们有各自的特点,也有各自调用的时机点。
联合使用两者,能更充分的发挥缓存的功效。
后面也给大家介绍了缓存算法,大家需要把此内容当成常识性知识。
访问者可将本网站提供的内容或服务用于个人学习、研究或欣赏,以及其他非商业性或非盈利性用途,但同时应遵守著作权法及其他相关法律的规定,不得侵犯本网站及相关权利人的合法权利。
本网站内容原作者如不愿意在本网站刊登内容,请及时通知本站,邮箱:80764001@qq.com,予以删除。