首页 > 代码库 > Hibernate一级缓存、二级缓存以及查询缓存的关系

Hibernate一级缓存、二级缓存以及查询缓存的关系

转载自http://blog.csdn.net/maoyeqiu/article/details/50209893

 

前两天总结了一下二级缓存和查询缓存的关系,但是又有一个新的问题,就是查询缓存缓存到二级缓存的数据,在第三次(第一次缓存中没有数据,查询数据库将对应的ID值存入到二级缓存中去,第二次如果是同一个Session那么将会把数据一级缓存中的数据返回,如果不是同一个Session而是同一个sessionfactory,那么将会把二级缓存中的数据返回,同时将数据放入到一级缓存中去)获取的时候,不使用查询缓存的方法,而是直接使用一级缓存的方法,能不能将缓存的数据获取到呢,我们现在验证一下。

首先开启一级缓存(默认)、二级缓存和查询缓存

 

[html] view plain copy
 
  1. <?xml version="1.0" encoding="utf-8"?>  
  2. <!DOCTYPE hibernate-configuration PUBLIC  
  3. "-//Hibernate/Hibernate Configuration DTD 3.0//EN"  
  4. "http://hibernate.sourceforge.net/hibernate-configuration-3.0.dtd">  
  5. <hibernate-configuration>  
  6.     <session-factory>  
  7.         <property name="hibernate.connection.driver_class">com.mysql.jdbc.Driver</property>  
  8.         <property name="hibernate.connection.url">jdbc:mysql://localhost:3306/hibernatedemo</property>  
  9.         <property name="hibernate.connection.password">root</property>  
  10.         <property name="hibernate.connection.username">root</property>  
  11.         <property name="hibernate.dialect">org.hibernate.dialect.MySQLDialect</property>  
  12.         <property name="show_sql">true</property>  
  13.         <property name="hbm2ddl.auto">create</property>  
  14.         <property name="hibernate.cache.provider_class">org.hibernate.cache.EhCacheProvider</property>  
  15.         <property name="hibernate.cache.use_query_cache">true</property>  
  16.         <!-- 开启二级缓存,其实hibernate默认就是开启的,这里显示的指定一下 -->   
  17.                 <property name="hibernate.cache.use_second_level_cache">true</property>   
  18.         <property name="hibernate.generate_statistics">true</property>  
  19.         <mapping class="hibernate.test.dto.DepartmentEntity"></mapping>  
  20.     </session-factory>  
  21. </hibernate-configuration>  


用到的缓存类

 

 

[java] view plain copy
 
  1. package hibernate.test.dto;  
  2.   
  3. @Entity  
  4. @Table(name = "DEPARTMENT", uniqueConstraints = {  
  5.         @UniqueConstraint(columnNames = "ID"),  
  6.         @UniqueConstraint(columnNames = "NAME") })  
  7. @Cache(usage=CacheConcurrencyStrategy.READ_ONLY, region="department")  
  8. public class DepartmentEntity implements Serializable {  
  9.       
  10.     private static final long serialVersionUID = 1L;  
  11.   
  12.     @Id  
  13.     @GeneratedValue(strategy = GenerationType.IDENTITY)  
  14.     @Column(name = "ID", unique = true, nullable = false)  
  15.     private Integer id;  
  16.       
  17.     @Column(name = "NAME", unique = true, nullable = false, length = 100)  
  18.     private String name;  
  19.   
  20.     public Integer getId() {  
  21.         return id;  
  22.     }  
  23.   
  24.     public void setId(Integer id) {  
  25.         this.id = id;  
  26.     }  
  27.   
  28.     public String getName() {  
  29.         return name;  
  30.     }  
  31.   
  32.     public void setName(String name) {  
  33.         this.name = name;  
  34.     }  
  35. }  


启动查询缓存将返回的实体存入到二级缓存中去

 

 

[java] view plain copy
 
  1. //此方法向数据库中存入三条数据  
  2. storeData();  
  3. Session session = HibernateUtil.getSessionFactory().openSession();  
  4. session.beginTransaction();  
  5. //开启查询缓存, query.setCacheable(true);开启查询缓存  
  6. Query query = session.createQuery("select s from DepartmentEntity s");     
  7. query.setCacheable(true);   
  8. List<DepartmentEntity> names = query.list();   
  9. for (Iterator<DepartmentEntity> it = names.iterator(); it.hasNext();) {   
  10.     String name = it.next().getName();   
  11.     System.out.println(name);   
  12. }   
  13. System.out.println(HibernateUtil.getSessionFactory().getStatistics().getSecondLevelCachePutCount());  


执行的结果:

 

hibernate: select department0_.ID as ID0_, department0_.NAME as NAME0_ from DEPARTMENT department0_
Human Resource
Humanne
Jhon
3

我们可以看到,产生了一条查询语句,将三个实体的名字输出,放入二级缓存的数量是3,都是没有问题的。这里比较容易犯的一个错误是查询的结果并不是实体,而是名字或者是其他属性,比如sql我们这么写:select s.name from DepartmentEntity s。存入二级缓存的数据是0,也就是没有存入到二级缓存中去。

再次调用上述方法:

 

[java] view plain copy
 
  1. //开启查询缓存, query.setCacheable(true);开启查询缓存  
  2. Query query = session.createQuery("select s from DepartmentEntity s");     
  3. query.setCacheable(true);   
  4. List<DepartmentEntity> names = query.list();   
  5. for (Iterator<DepartmentEntity> it = names.iterator(); it.hasNext();) {   
  6.     String name = it.next().getName();   
  7.     System.out.println(name);   
  8. }   
  9. System.out.println(HibernateUtil.getSessionFactory().getStatistics().getSecondLevelCachePutCount());  
  10.   
  11. //同一个Session中再次查询,不会发出SQL语句到数据库  
  12. query = session.createQuery("select s from DepartmentEntity s");   
  13. query.setCacheable(true);   
  14. names = query.list();   
  15. for (Iterator<DepartmentEntity> it = names.iterator(); it.hasNext();) {   
  16.     String name = it.next().getName();   
  17.     System.out.println(name);   
  18. }   
  19. System.out.println(HibernateUtil.getSessionFactory().getStatistics().getSecondLevelCachePutCount());  
  20. System.out.println(HibernateUtil.getSessionFactory().getStatistics().getSecondLevelCacheHitCount());  



执行的结果:

 

Hibernate: select department0_.ID as ID0_, department0_.NAME as NAME0_ from DEPARTMENT department0_
Human Resource
Humanne
Jhon
3
Human Resource
Humanne
Jhon
3
0

我们可以看到只产生了一条数据库查询语句,第二次直接从缓存中获取(注意,第二次调用的时候依然要写quey.setCacheable(true); ,我们模拟的是同一个方法的多次调用,不然的会产生数据库查询),那么问题来了,这个缓存指的是一级缓存还是二级缓存呢,我们看执行的结果二级缓存的命中是0,也就是说这里并没有用到二级缓存而是直接使用了一级缓存,前提是在同一个Session的情况下,在同一个session下执行的结果都会首先缓存到一级缓存中去,那么我们开一个新的Session会有什么样的不同结果呢

 

[java] view plain copy
 
  1. //开启查询缓存, query.setCacheable(true);开启查询缓存  
  2. Query query = session.createQuery("select s from DepartmentEntity s");     
  3. query.setCacheable(true);   
  4. List<DepartmentEntity> names = query.list();   
  5. for (Iterator<DepartmentEntity> it = names.iterator(); it.hasNext();) {   
  6.     String name = it.next().getName();   
  7.     System.out.println(name);   
  8. }   
  9. System.out.println(HibernateUtil.getSessionFactory().getStatistics().getSecondLevelCachePutCount());  
  10.   
  11. //同一个Session中再次查询,不会发出SQL语句到数据库  
  12. query = session.createQuery("select s from DepartmentEntity s");   
  13. quey.setCacheable(true);   
  14. names = query.list();   
  15. for (Iterator<DepartmentEntity> it = names.iterator(); it.hasNext();) {   
  16.     String name = it.next().getName();   
  17.     System.out.println(name);   
  18. }   
  19. System.out.println(HibernateUtil.getSessionFactory().getStatistics().getSecondLevelCachePutCount());  
  20. System.out.println(HibernateUtil.getSessionFactory().getStatistics().getSecondLevelCacheHitCount());  
  21.   
  22. Session anotherSession = HibernateUtil.getSessionFactory().openSession();  
  23. anotherSession.beginTransaction();  
  24. query = anotherSession.createQuery("select s from DepartmentEntity s");   
  25. query.setCacheable(true);   
  26. names = query.list();   
  27. for (Iterator<DepartmentEntity> it = names.iterator(); it.hasNext();) {   
  28.     String name = it.next().getName();   
  29.     System.out.println(name);   
  30. }   
  31. System.out.println(HibernateUtil.getSessionFactory().getStatistics().getSecondLevelCachePutCount());  
  32. System.out.println(HibernateUtil.getSessionFactory().getStatistics().getSecondLevelCacheHitCount());  


执行结果:

 

 

[java] view plain copy
 
  1. Hibernate: select department0_.ID as ID0_, department0_.NAME as NAME0_ from DEPARTMENT department0_  
  2. Human Resource  
  3. Humanne  
  4. Jhon  
  5. 3  
  6. Human Resource  
  7. Humanne  
  8. Jhon  
  9. 3  
  10. 0  
  11. Human Resource  
  12. Humanne  
  13. Jhon  
  14. 3  
  15. 3  


我们看到运行的结果,三次执行都只是放入二级缓存的实例3个,也就是说二级缓存中只有三个实例。由于二级缓存是sessionfactory级别的当开启查询缓存将数据放入的二级缓存的时候是不受开了几个session影响的,所以尽管我们上边开启了2个session但是依旧是在二级缓存中有3个实体。这里还有一个问题是查询缓存的key值是如何定义的呢,导致了开启了3次查询缓存而只存入3条数据,如果key值不同的话,那么肯定是会存入9条数据,关于这个问题大家可以参考,这里。由于二级缓存是sessionfactory级别的,因此会直接将二级缓存中的数据取出,并存入到一级缓存中去。

 

我们在sql中只是查询实体的名字,我们来看一下查询缓存是如何缓存的

 

[java] view plain copy
 
  1.               //开启查询缓存, query.setCacheable(true);开启查询缓存  
  2. ,Query query = session.createQuery("select s.name from DepartmentEntity s");     
  3. query.setCacheable(true);   
  4. System.out.println(HibernateUtil.getSessionFactory().getStatistics().getSecondLevelCachePutCount());  
  5. System.out.println(HibernateUtil.getSessionFactory().getStatistics().getSecondLevelCacheHitCount());  
  6.   
  7. //同一个Session中再次查询,不会发出SQL语句到数据库  
  8. query = session.createQuery("select s.name from DepartmentEntity s");   
  9. query.setCacheable(true);   
  10. System.out.println(HibernateUtil.getSessionFactory().getStatistics().getSecondLevelCachePutCount());  
  11. System.out.println(HibernateUtil.getSessionFactory().getStatistics().getSecondLevelCacheHitCount());  
  12.   
  13. Session anotherSession = HibernateUtil.getSessionFactory().openSession();  
  14. anotherSession.beginTransaction();  
  15. query = anotherSession.createQuery("select s.name from DepartmentEntity s");   
  16. query.setCacheable(true);   
  17. System.out.println(HibernateUtil.getSessionFactory().getStatistics().getSecondLevelCachePutCount());  
  18. System.out.println(HibernateUtil.getSessionFactory().getStatistics().getSecondLevelCacheHitCount());  


执行结果:

 

Hibernate: select department0_.NAME as col_0_0_ from DEPARTMENT department0_
0
0
0
0
0
0

从结果上我们可以看到只有一个数据库查询,然后是六个0,前两个0是没有将数据存入到二级缓存中去,中间两个0并且没有sql数据库查询说明,数据从缓存中获取,而且是一级缓存中的数据,后两个0我们重新开启了一个session,同样没有数据库查询,也就说是调用了二级缓存中的数据,也就说明了查询缓存也是sessionfactory级别的。

我们现在来验证一下我们刚才提出的问题,直接用load获取数据

 

[java] view plain copy
 
  1. DepartmentEntity department = (DepartmentEntity) session.load(DepartmentEntity.class, new Integer(1));  
  2. System.out.println(HibernateUtil.getSessionFactory().getStatistics().getSecondLevelCacheHitCount());  

 

执行结果:

0

从结果来看,没有产生数据库查询二级缓存的命中是0,也就是说数据是从一级缓存中获取的,这就验证了我们一开始提到的答案。

总结:

1、一级缓存是session级别的,二级缓存和查询缓存都是sessionfactory级别的,查询缓存和二级缓存是一起来使用的

2、任何sql执行都会存入到同一个session的一级缓存中去

3、同时开启查询缓存和二级缓存,可以在不同session间共享缓存的结果

4、二级缓存缓存的是实体,不是属性

5、查询缓存的结果如果只是属性,那么查询缓存中存储的是id和属性的值,如果是实体的集合,那么查询缓存存储的只是实体的id,对应的实体会存储到二级缓存中去。

6、不同session间返回数据的顺序是,二级缓存先将数据返回,然后将数据存入本session的一级缓存中去,以便下次调用时的使用

Hibernate一级缓存、二级缓存以及查询缓存的关系