我这里使用的是Hibernate5.2.0版本

  Hibernate缓存分为一级缓存(有的也叫Session缓存)和二级缓存。

 

一级缓存(Session)

  一级缓存的生命周期和session的生命周期一致,当前sessioin一旦关闭,一级缓存就消失,因此一级缓存也叫session级的缓存或事务级缓存。一级缓存只存实体对象的 ,它不会缓存一般的对象属性(查询缓存可以),即当获得对象后,就将该对象的缓存起来,如果在同一session中如果再去获取这个对象 时,它会先判断缓存中有没有该对象的ID,如果有就直接从缓存中取出,反之则去数据库中取,取的同时将该对象的缓存起来,有以下方法可以 支持一级缓存:

      get() 
      load() 
      iterate(查询实体对象) 
其 中 Query 和Criteria的list() 只会缓存,但不会使用缓存(除非结合查询缓存)。 

  下面我们进行代码测试:

 

测试1:

 1 Session session=factory.openSession();
 2 Transaction tran=session.beginTransaction();
 3 
 4 User user=session.get(User.class, 2);
 5 System.out.println(user);
 6 
 7 User user2=session.get(User.class, 2);
 8 System.out.println(user2);
 9 
10 tran.commit();
11 session.close();

  输出结果:

1 Hibernate: select user0_.Id as Id1_1_0_, user0_.name as name2_1_0_, user0_.deptid as deptid3_1_0_ from user user0_ where user0_.Id=?
2 User [id=2, name=姓名2]
3 User [id=2, name=姓名2]

  可以很清楚的看到,这里只执行了一条SQL语句,当使用get方法时,获取到一个对象,并将这个对象缓存到session中,当下次再从这个session中获取Id=2的User对象,那么session首先从缓存中查询是否有一个id=2的User对象,如果就返回,这里刚好有一个,所有就不会执行第二条SQL语句。

 

测试2:

  

 1 Session session=factory.openSession();
 2 Transaction tran=session.beginTransaction();
 3 
 4 User user=session.get(User.class, 200);
 5 System.out.println(user);
 6 
 7 User user2=session.get(User.class, 200);
 8 System.out.println(user2);
 9 
10 tran.commit();
11 session.close();

  输出结果

1 Hibernate: select user0_.Id as Id1_1_0_, user0_.name as name2_1_0_, user0_.deptid as deptid3_1_0_ from user user0_ where user0_.Id=?
2 null
3 Hibernate: select user0_.Id as Id1_1_0_, user0_.name as name2_1_0_, user0_.deptid as deptid3_1_0_ from user user0_ where user0_.Id=?
4 null

  因为ID=200的User对象在数据库中并不存在,所有第一条语句执行后,输出该对象为null,当再次从使用get方法时,Hibernate先判断session是否有id=200的User对象,这里很明显并没有。所有再次从数据库中查找,执行了第二条语句

 

测试3

  

 1 Session session=factory.openSession();
 2 Transaction tran=session.beginTransaction();
 3 
 4 User user=session.get(User.class, 2);
 5 System.out.println(user);
 6 
 7 tran.commit();
 8 session.close();
 9         
10 System.out.println("---------------------");
11 
12 session=factory.openSession();
13 tran=session.beginTransaction();
14 
15 user=session.get(User.class, 2);
16 System.out.println(user);
17 
18 tran.commit();
19 session.close();

  输出结果:

1 Hibernate: select user0_.Id as Id1_1_0_, user0_.name as name2_1_0_, user0_.deptid as deptid3_1_0_ from user user0_ where user0_.Id=?
2 User [id=2, name=姓名2]
3 ---------------------
4 Hibernate: select user0_.Id as Id1_1_0_, user0_.name as name2_1_0_, user0_.deptid as deptid3_1_0_ from user user0_ where user0_.Id=?
5 User [id=2, name=姓名2]

  这里我们可以看出,执行了两条语句。我们知道一级缓存是基于Session,每个Session缓存的数据并不能共享,当关闭Session时,这个Session中的缓存也会被清楚。上面我们可以看到两个Session并不是同一个Session对象,所有会读取两条操作。那么怎么才可以夸Session读取缓存信息了。这里就要用到我们下面要讲解的二级缓存。

 

二级缓存  

  二级缓存也称进程级的缓存或SessionFactory级的缓存,二级缓存可以被所有的session共享,二级缓存的生命周二级缓存的生命周期和 SessionFactory的生命周期一致。 

  二级缓存的工具包也很多,我们这里讲解EhCache

  hibernate.cfg.xml配置

<!-- 启用二级缓存 -->
<property name="cache.use_second_level_cache">true</property>
<!-- 选择二级缓存的工具类 -->
<property name="cache.region.factory_class">org.hibernate.cache.ehcache.EhCacheRegionFactory</property>

  第一句代码,是启用二级缓存,要想使用二级缓存,我们必须先启用二级缓存。第二句代码是选择第三方缓存提供类,我们这里使用EhCache。以前版本的Hibernate可能会用cache.provider_class。

  并不是所有的类都需要使用缓存机制,比如财务上的数据,变更比较大,就不能使用缓存。而基本上没有变更或变更比较少的话就可以使用缓存。要想使某个类型的对象使用使用缓存,我们也必须给这个类定义缓存声明

1 <class-cache usage="read-write" class="com.myproc.domain.User"/>

  属性:class用来指定对那个类使用缓存,这里必须是全限命名(包括包名)

  usage:指定缓存策略

☆实体缓存

测试代码:

 1 Session session=factory.openSession();
 2 Transaction tran=session.beginTransaction();
 3 
 4 User user=session.get(User.class, 2);
 5 System.out.println(user);
 6 
 7 tran.commit();
 8 session.close();
 9         
10 System.out.println("---------------------");
11 
12 session=factory.openSession();
13 tran=session.beginTransaction();
14 
15 user=session.get(User.class, 2);
16 System.out.println(user);
17 
18 tran.commit();
19 session.close();

 

  输出结果

1 Hibernate: select user0_.Id as Id1_1_0_, user0_.name as name2_1_0_, user0_.deptid as deptid3_1_0_ from user user0_ where user0_.Id=?
2 User [id=2, name=姓名2]
3 ---------------------
4 User [id=2, name=姓名2]

  可以看到,虽然是两个不同的Session,但是还是只执行了一条SQL语句,说明二级缓存起了作用。

   【总结】:通过Session.get()方法和Session.load()方法都可以将一个实体对象缓存到一级缓存中。如果开启了二级缓存,那么数据也会缓存到二级缓存中

 

☆集合属性的缓存

  代码测试:

 1 Dept dept=session.get(Dept.class, 5);
 2 System.out.println(dept);
 3 System.out.println(dept.getUsers())
 4 tran.commit();
 5 session.close();
 6         
 7 System.out.println("---------------------");
 8 
 9 session=factory.openSession();
10 tran=session.beginTransaction();
11 
12 dept=session.get(Dept.class, 2);
13 System.out.println(dept);
14 System.out.println(dept.getUsers());
15 tran.commit();
16 session.close();

  输出结果:

1 Hibernate: select dept0_.Id as Id1_0_0_, dept0_.name as name2_0_0_ from dept dept0_ where dept0_.Id=?
2 Dept [id=5, name=部门5]
3 Hibernate: select users0_.deptid as deptid3_1_0_, users0_.Id as Id1_1_0_, users0_.Id as Id1_1_1_, users0_.name as name2_1_1_, users0_.deptid as deptid3_1_1_ from user users0_ where users0_.deptid=?
4 [User [id=49, name=姓名49], User [id=26, name=姓名26], User [id=35, name=姓名35], User [id=14, name=姓名14]]
5 ---------------------
6 Dept [id=5, name=部门5]
7 Hibernate: select users0_.deptid as deptid3_1_0_, users0_.Id as Id1_1_0_, users0_.Id as Id1_1_1_, users0_.name as name2_1_1_, users0_.deptid as deptid3_1_1_ from user users0_ where users0_.deptid=?
8 [User [id=26, name=姓名26], User [id=49, name=姓名49], User [id=14, name=姓名14], User [id=35, name=姓名35]]

  这里我们可以看到select .... from dept 语句只有一句,说明对应Dept对象的二级缓存是成功了的。但是当我们查看该对象下users集合中的值时,在第一次查询的时候从数据库中读取了一次,第二次查询的时候又读取了一次,显然,对应对象的集合属性并没有缓存到二级缓存中,那么怎么才能够缓存集合属性了?

  方法:在hibernate中添加collection-cache

 

<collection-cache usage="read-only" collection="com.myproc.domain.Dept.users"/> 

 

  collection属性为类的全限命名+集合属性名称

  输出结果

1 Hibernate: select dept0_.Id as Id1_0_0_, dept0_.name as name2_0_0_ from dept dept0_ where dept0_.Id=?
2 Dept [id=5, name=部门5]
3 Hibernate: select users0_.deptid as deptid3_1_0_, users0_.Id as Id1_1_0_, users0_.Id as Id1_1_1_, users0_.name as name2_1_1_, users0_.deptid as deptid3_1_1_ from user users0_ where users0_.deptid=?
4 [User [id=35, name=姓名35], User [id=26, name=姓名26], User [id=49, name=姓名49], User [id=14, name=姓名14]]
5 ---------------------
6 Dept [id=5, name=部门5]
7 [User [id=35, name=姓名35], User [id=14, name=姓名14], User [id=26, name=姓名26], User [id=49, name=姓名49]]

 

 ☆查询缓存

  首先看一下下面的代码:

 1 Session session=factory.openSession();
 2 Transaction tran=session.beginTransaction();
 3 
 4 List list= session.createQuery("FROM User WHERE id<5").list();
 5 System.out.println(list);
 6 
 7 tran.commit();
 8 session.close();
 9         
10 System.out.println("---------------------");
11 
12 session=factory.openSession();
13 tran=session.beginTransaction();
14 
15 User user=session.get(User.class, 1);
16 System.out.println(user);
17 
18 List list2= session.createQuery("FROM User WHERE id<5").list();
19 System.out.println(list2);
20 
21 tran.commit();
22 session.close();
23 
24 tran.commit();
25 session.close();

  输出结果

1 Hibernate: select user0_.Id as Id1_1_, user0_.name as name2_1_, user0_.deptid as deptid3_1_ from user user0_ where user0_.Id<5
2 [User [id=1, name=姓名1], User [id=2, name=姓名2], User [id=3, name=姓名3], User [id=4, name=姓名4]]
3 ---------------------
4 User [id=1, name=姓名1]
5 Hibernate: select user0_.Id as Id1_1_, user0_.name as name2_1_, user0_.deptid as deptid3_1_ from user user0_ where user0_.Id<5
6 [User [id=1, name=姓名1], User [id=2, name=姓名2], User [id=3, name=姓名3], User [id=4, name=姓名4]]

  当我们第一次使用HQL查询出一个id<5的集合。在第一次使用HQL同样查询id<5的集合,还是在数据库中再查询了异常,结论查询语句不会使用缓存。

  当在第二次中我们使用session.get()方式,我们查询id=1的user时,结果没有想数据库中查询,说明缓存中存在对应的数据。

  【综上所诉】:查询语句不会使用缓存,但是会将查询的结果放在缓存中

 

Question:对于查询语句难道我们就没有办法进行缓存了吗?

  Answer:答案是我们是有办法的。看看下面的代码

 1 Session session=factory.openSession();
 2 Transaction tran=session.beginTransaction();
 3 
 4 Iterator iterator1= session.createQuery("FROM User WHERE id<5").iterate();
 5 while(iterator1.hasNext()){
 6     System.out.println(iterator1.next());
 7 }
 8 
 9 tran.commit();
10 session.close();
11         
12 System.out.println("---------------------");
13 
14 session=factory.openSession();
15 tran=session.beginTransaction();
16 
17 User user=session.get(User.class, 1);
18 System.out.println(user);
19 
20 Iterator iterator2= session.createQuery("FROM User WHERE id<5").iterate();
21 while(iterator2.hasNext()){
22     System.out.println(iterator2.next());
23 }
24 
25 tran.commit();
26 session.close();

  输出结果

 1 Hibernate: select user0_.Id as col_0_0_ from user user0_ where user0_.Id<5
 2 Hibernate: select user0_.Id as Id1_1_0_, user0_.name as name2_1_0_, user0_.deptid as deptid3_1_0_ from user user0_ where user0_.Id=?
 3 User [id=1, name=姓名1]
 4 Hibernate: select user0_.Id as Id1_1_0_, user0_.name as name2_1_0_, user0_.deptid as deptid3_1_0_ from user user0_ where user0_.Id=?
 5 User [id=2, name=姓名2]
 6 Hibernate: select user0_.Id as Id1_1_0_, user0_.name as name2_1_0_, user0_.deptid as deptid3_1_0_ from user user0_ where user0_.Id=?
 7 User [id=3, name=姓名3]
 8 Hibernate: select user0_.Id as Id1_1_0_, user0_.name as name2_1_0_, user0_.deptid as deptid3_1_0_ from user user0_ where user0_.Id=?
 9 User [id=4, name=姓名4]
10 ---------------------
11 User [id=1, name=姓名1]
12 Hibernate: select user0_.Id as col_0_0_ from user user0_ where user0_.Id<5
13 User [id=1, name=姓名1]
14 User [id=2, name=姓名2]
15 User [id=3, name=姓名3]
16 User [id=4, name=姓名4]

  结果说明:在第一次查询的时候,首先查询了满足条件对象的所有id值,然后在通过迭代方式,没迭代一次就查询一次对应id的对象,并将该对象放入缓存中。这种就是N+1的查询。N表示有多少个对象

       在第二次中,使用session.get能够读取到之前缓存的数据,如果在使用HQL语句查询,那么还是先从数据库中查询出满足条件的所有ID,如果对应ID在缓存中存在,则直接从缓存中读取,否则需要再次从数据库中查询出来

  

  上面的例子,刚好第二次查询所有数据都在缓存中存在,如果我们第二次查询id<8,那么我们看到结果将会是怎样

 1 Hibernate: select user0_.Id as col_0_0_ from user user0_ where user0_.Id<8
 2 User [id=1, name=姓名1]
 3 User [id=2, name=姓名2]
 4 User [id=3, name=姓名3]
 5 User [id=4, name=姓名4]
 6 Hibernate: select user0_.Id as Id1_1_0_, user0_.name as name2_1_0_, user0_.deptid as deptid3_1_0_ from user user0_ where user0_.Id=?
 7 User [id=5, name=姓名5]
 8 Hibernate: select user0_.Id as Id1_1_0_, user0_.name as name2_1_0_, user0_.deptid as deptid3_1_0_ from user user0_ where user0_.Id=?
 9 User [id=6, name=姓名6]
10 Hibernate: select user0_.Id as Id1_1_0_, user0_.name as name2_1_0_, user0_.deptid as deptid3_1_0_ from user user0_ where user0_.Id=?
11 User [id=7, name=姓名7]

  

  虽然使用迭代的方式可以HQL语句缓存问题,但是对于N+1条查询,的确非常影响性能,如果数据非常多,反而结果并不理想,所有我们推荐使用下面一种方式:

 1 List list1=session.createQuery("FROM User WHERE id<5")
 2                     .setCacheable(true)
 3                     .list();
 4 System.out.println(list1);
 5 tran.commit();
 6 session.close();
 7         
 8 System.out.println("---------------------");
 9 
10 session=factory.openSession();
11 tran=session.beginTransaction();
12 
13 List list2=session.createQuery("FROM User WHERE id<5")
14 .setCacheable(true)
15 .list();
16 System.out.println(list2);

  对Query设置能够使用缓存setCacheable(true),同时还需要启用查询缓存

<!-- 启用查询缓存 -->
<property name="cache.use_query_cache">true</property>

  输出结果

1 Hibernate: select user0_.Id as Id1_1_, user0_.name as name2_1_, user0_.deptid as deptid3_1_ from user user0_ where user0_.Id<5
2 [User [id=1, name=姓名1], User [id=2, name=姓名2], User [id=3, name=姓名3], User [id=4, name=姓名4]]
3 ---------------------
4 [User [id=1, name=姓名1], User [id=2, name=姓名2], User [id=3, name=姓名3], User [id=4, name=姓名4]]

  【注意】:使用这种方式,要求条件不行完全一致,不存在包含关系

☆缓存策略

  a、只读缓存(read-only):只读策略,表示数据不能够被更改

  b、不严格的读/写缓存(nonstrict-read-write):需要更新数据,但是两个事务更新同一条记录的可能性很小,性能比读写缓存好 

  c、读/写缓存(read-write):允许少量的修改数据

  d、事务缓存(transactional):缓存支持事务,发生异常的时候,缓存也能够回滚,只支持jta环境

  读写缓存和不严格读写缓存在实现上的区别在于,读写缓存更新缓存的时候会把缓存里面的数据换成一个锁,其他事务如果去取相应的缓存数据,发现被锁住了,然后就直接取数据库查询。 

  各策略的性能从下往上越来越好

 

☆使用ehcache.xml配置文件

  

 1 <!--
 2   ~ Hibernate, Relational Persistence for Idiomatic Java
 3   ~
 4   ~ License: GNU Lesser General Public License (LGPL), version 2.1 or later.
 5   ~ See the lgpl.txt file in the root directory or <http://www.gnu.org/licenses/lgpl-2.1.html>.
 6   -->
 7 <ehcache>
 8 
 9     <!-- Sets the path to the directory where cache .data files are created.
10 
11          If the path is a Java System Property it is replaced by
12          its value in the running VM.
13 
14          The following properties are translated:
15          user.home - User's home directory
16          user.dir - User's current working directory
17          java.io.tmpdir - Default temp file path -->
18     <diskStore path="D:/cache/"/>
19 
20 
21     <!--Default Cache configuration. These will applied to caches programmatically created through
22         the CacheManager.
23 
24         The following attributes are required for defaultCache:
25 
26         maxInMemory       - Sets the maximum number of objects that will be created in memory
27         eternal           - Sets whether elements are eternal. If eternal,  timeouts are ignored and the element
28                             is never expired.
29         timeToIdleSeconds - Sets the time to idle for an element beforeQuery it expires. Is only used
30                             if the element is not eternal. Idle time is now - last accessed time
31         timeToLiveSeconds - Sets the time to live for an element beforeQuery it expires. Is only used
32                             if the element is not eternal. TTL is now - creation time
33         overflowToDisk    - Sets whether elements can overflow to disk when the in-memory cache
34                             has reached the maxInMemory limit.
35 
36         -->
37     <defaultCache
38         maxElementsInMemory="10"
39         eternal="false"
40         timeToIdleSeconds="120"
41         timeToLiveSeconds="120"
42         overflowToDisk="true"
43         />
44 </ehcache>

 

  <diskStore path="D:/cache/"/>:表示当缓存超出了maxElementsInMemory是,存到到硬盘的什么位置

 

  在hibernate.cfg.xml文件中我们还需要指定ehcache的位置

1 <!-- 设置ehcache配置文件 -->
2 <property name="net.sf.ehcache.configurationResourceName">ehcache.xml</property>

  

  下面我看一看在硬盘中缓存的文件

 

【最后提醒】

  如果对于指定了可读性的策略是,通过HQL语句update或delete操作修改了对象后,hibernate会通知二级缓存,删除对于该对象的缓存信息。当下一次访问该对象时,首先访问二级缓存,此时二级缓存中该对象根本就不存在了,所有会查询数据库,然后将查询来的对象再次保存到二级缓存中。所有我们直观的看到,二级缓存中的信息是被更改了的。

  而对于一级缓存,对于update的操作,不会通知session,所有对象属性还是在没有保存之前的数据,如果需要查看对象的新状态,则使用session.refresh(object);

 posted on 2016-06-23 08:59  Just_Do  阅读(346)  评论(0)    收藏  举报