当一个实体被管理后,该对象被添加到当前持久性上下文(EntityManager或Session)的内部缓存中。持久化上下文也称为一级缓存,默认情况下是启用的。
1、一级缓存无需激活
由debug信息可以看到,只有4条查询语句(如果没有缓存,应出现7条语句),也就是说,第二次再查询相同记录时,没有再去数据库查询,而是从一级缓存查询了。
2、二级缓存——实体缓存(Entity cache)
(1)配置步骤:
a.在Hibernate初始化的配置文件,激活二级缓存,并且指定使用EhCache作为缓存实现(Hibernate的二级缓存实现有很多,比较常用的是EhCache和Infinispan)。
b.在src下面增加ehcache.xml配置文件,用于配置EhCache
<?xml version="1.0" encoding="UTF-8"?>
<ehcache>
<!-- 如果缓存里面的数据太多,内存不足的情况下,把文件存在哪个位置 -->
<!-- Linux、Unix、MacOS直接存储在 /tmp -->
<diskStore path="c:/tmp/cache" />
<!-- maxElementsInMemory表示在缓存里面,最多有多少个对象 -->
<!-- timeToIdleSeconds当对象空闲多少秒以后,从缓存里面删除 -->
<!-- overflowToDisk表示超过maxElementsInMemory数量的对象,是否存储在硬盘上 -->
<defaultCache
maxElementsInMemory="1"
eternal="false"
timeToIdleSeconds="120"
timeToLiveSeconds="120"
overflowToDisk="true" />
</ehcache>
c.在要被缓存的对象上面,需要增加注解
@Cacheable是JPA的规范
@Cache是Hibernate的规范
可以一起用,也可以只用一个
@Cache并发缓存策略:
i.read-only(可读,但不修改,仍然可以删除entity)
ii.read-write(需要更新数据)
iii.nonstrict-read-write(类似于read-write,但有时对实体的并发访问可能会读到过时的数据,适合很少同时更新相同数据的情况)
iv.transactional(提供可序列化的事务隔离级别。)
(2)例子:
测试结果:
a.此处出现疑惑:发现testB()中二级缓存没有起作用,有部分数据还是到数据库里查
b.原因:有部分数据没有,所以第一次查询缓存不了,第二次还是会接着查询
将testB改成如下测试:
public void testB() {
EntityManagerFactory factory = Persistence
.createEntityManagerFactory("homework");
// 开启第一个EntityManager
EntityManager manager = factory.createEntityManager();
manager.getTransaction().begin();
for (long i = 800; i < 820; i++) {
Order order = manager.find(Order.class, i);
if (order != null) {
System.out.println(order.getAmount());
} else {
System.out.println("No Data");
}
}
manager.getTransaction().commit();
manager.close();
System.out.println("--------------------------------------");
// 开启第二个EntityManager
EntityManager manager2 = factory.createEntityManager();
manager2.getTransaction().begin();
for (long i = 800; i < 820; i++) {
Order order = manager2.find(Order.class, i);
if (order != null) {
System.out.println(order.getAmount());
} else {
System.out.println("mei shuju");
}
}
manager2.getTransaction().commit();
manager2.close();
factory.close();
}
(3)再次测试结果:
注意:如果想要查看缓存的data文件,不要关闭EntityManagerFactory/SessionFactory。因为二级缓存是在EntityManagerFactory/SessionFactory这个级别,关闭时所有文件都会被删除。
3、集合缓存(Collection cache){需要与二级缓存一起用}
(1)在需要缓存的集合上面加@Cache注解
(2)测试代码
将打印order.getAmount()改为System.out.println(order.getOrderItems());
(3)测试结果
4、查询缓存(Query cache){需要与二级缓存一起用}
适用于绑定固定参数,频繁执行查询语句
在应用程序的正常事务处理方面,缓存查询结果会带来一些开销。例如,如果您缓存针对Person的查询结果,那么Hibernate将需要跟踪这些结果何时失效,因为已经对任何Person实体进行了更改。另外,由于大多数应用程序无法从缓存查询结果中获得好处,因此Hibernate在默认情况下会禁用查询结果的缓存。
查询缓存,缓存的key是hql语句,缓存的value是满足hql语句的记录的主键值。也就是说,查询缓存,只是缓存数据库记录的主键值,并不会缓存记录的所有字段值。
(1)配置:需要激活查询缓存
(2)通过继承另一个类使用Junit实现entityManager和entityManager Factory启动及关闭,此处省略该代码
a.测试代码:(使用对象缓存,不使用查询缓存)
测试结果:可以看到查询指定user的order发出两次SQL语句。由于User使用了Entity cache,所以使用了二级缓存,第二次没有再去查询User
b.测试代码:(使用对象缓存,使用查询缓存)
必须设置org.hibernate.cacheable为true,启用缓存
测试结果:
(3)此处疑惑:
使用对象缓存,不使用查询缓存运行时间: 使用对象缓存,使用查询缓存运行时间:
可以看到使用了查询缓存后运行时间反而比不使用查询缓存的运行时间要多不少。
原因:
ehcache.xml中maxElementsInMemory的设置的值为1,而maxElementsInMemory表示在缓存里面最多可以放置多少个对象。如果该参数设置的值较小,那么执行查询语句时,每执行一次或数量较小的几次,那么就将该对象往磁盘里面存入。第二次查询读出的时候也是从磁盘上的缓存文件中读。这里消耗的代价也是很大。
如果要降低代价,可将maxElementsInMemory设置得足够大,比如这次测试设置成200000。再次运行程序,此时运行时间为:
可以看出比原来运行时间少了很多。因为第一次查询没有往磁盘存数据,第二次也是直接从内存读取。
注意:测试案例都是运行相同的代码,故而命中率为100%。实际需要的查询参数或语句很少会有完全相同的可能,所以,使用缓存需谨慎。