hibernate 缓存机制

一个人想着一个人 提交于 2019-12-01 20:34:06

缓存:缓存是什么,解决什么问题? 
位于速度相差较大的两种硬件/软件之间的,用于协调两者数据传输速度差异的结构,均可称之为缓存Cache。缓存目的:让数据更接近于应用程序,协调速度不匹配,使访问速度更快。 
 

缓存的范围分为3类: 
1.事务范围(单Session即一级缓存) 
   事务范围的缓存只能被当前事务访问,每个事务都有各自的缓存,缓存内的数据通常采用相互关联的对象形式.缓存的生命周期依赖于事务的生命周期,只有当事务结束时,缓存的生命周期才会结束.事务范围的缓存使用内存作为存储介质,一级缓存就属于事务范围. 
2.应用范围(单SessionFactory即二级缓存) 
   应用程序的缓存可以被应用范围内的所有事务共享访问.缓存的生命周期依赖于应用的生命周期,只有当应用结束时,缓存的生命周期才会结束.应用范围的缓存可以使用内存或硬盘作为存储介质,二级缓存就属于应用范围. 
3.集群范围(多SessionFactory) 
   在集群环境中,缓存被一个机器或多个机器的进程共享,缓存中的数据被复制到集群环境中的每个进程节点,进程间通过远程通信来保证缓存中的数据的一致,缓存中的数据通常采用对象的松散数据形式.

 

hibernate 的缓存介绍

一级缓存(session):内部缓存

事务范围:缓存只能被当前事务访问。缓存的生命周期依赖于事务的生命周期,当事务结束时,缓存也就结束生命周期。

 

二级缓存(sessionFactory):

缓存被应用范围内的所有事务共享。 这些事务有可能是并发访问缓存,因此必须对缓存进行更新。缓存的生命周期依赖于应用的生命周期,应用结束时, 缓存也就结束了生命周期,二级缓存存在于应用范围。集群范围:在集群环境中,缓存被一个机器或者多个机器的进程共享。缓存中的数据被复制到集群环境中的每个进程节点,进程间通过远程通信来保证缓存中的数据的一致性, 缓存中的数据通常采用对象的松散数据形式,二级缓存也存在与应用范围。

 

注意:对大多数应用来说,应该慎重地考虑是否需要使用集群范围的缓存,再加上集群范围还有数据同步的问题,所以应当慎用。多种范围的缓存处理过程持久化层可以提供多种范围的缓存。如果在事务范围的缓存中没有查到相应的数据,还可以到应用范围或集群范围的缓存内查询,如果还是没有查到,那么只有到数据库中查询了。

 

在通常情况下会将具有以下特征的数据放入到二级缓存中: 
● 很少被修改的数据。 
● 不是很重要的数据,允许出现偶尔并发的数据。 
● 不会被并发访问的数据。 
● 常量数据。 
● 不会被第三方修改的数据 

而对于具有以下特征的数据则不适合放在二级缓存中: 
● 经常被修改的数据。 
● 财务数据,绝对不允许出现并发。 
● 与其他应用共享的数据。 

在这里特别要注意的是对放入缓存中的数据不能有第三方的应用对数据进行更改(其中也包括在自己程序中使用其他方式进行数据的修改,例如,JDBC),因为那样Hibernate将不会知道数据已经被修改,也就无法保证缓存中的数据与数据库中数据的一致性

 

常见的缓存组件 
在默认情况下,Hibernate会使用EHCache作为二级缓存组件。但是,可以通过设置hibernate.cache.provider_class属性,指定其他的缓存策略,该缓存策略必须实现org.hibernate.cache.CacheProvider接口。 
通过实现org.hibernate.cache.CacheProvider接口可以提供对不同二级缓存组件的支持,此接口充当缓存插件与Hibernate之间的适配器。 

组件 Provider类 类型 集群 查询缓存
Hashtable org.hibernate.cache.HashtableCacheProvider 内存 不支持 支持
EHCache org.hibernate.cache.EhCacheProvider 内存,硬盘 不支持 支持
OSCache org.hibernate.cache.OSCacheProvider 内存,硬盘 支持 支持
SwarmCache org.hibernate.cache.SwarmCacheProvider 集群 支持 不支持
JBoss TreeCache org.hibernate.cache.TreeCacheProvider 集群 支持 支持


Hibernate已经不再提供对JCS(Java Caching System)组件的支持了。

 

缓存配置

如何在程序里使用二级缓存: 
首先在hibernate.cfg.xml开启二级缓存

<hibernate-configuration>  
   <session-factory>  
  
      ......  
  
      <!-- 开启二级缓存 -->  
      <property name="hibernate.cache.use_second_level_cache">true</property>  
      <!-- 启动"查询缓存"如果想缓存使用findall()、list()、Iterator()、createCriteria()、createQuery()等方法获得的数据结果集,必须配置此项-->  
      <property name="hibernate.cache.use_query_cache">true</property>  
      <!-- 设置二级缓存插件EHCache的Provider类-->  
      <!-- <property name="hibernate.cache.provider_class">  
         org.hibernate.cache.EhCacheProvider  
      </property> -->  
      <!-- 二级缓存区域名的前缀 -->  
      <!--<property name="hibernate.cache.region_prefix">test</property>-->  
      <!-- 高速缓存提供程序 -->  
      <property name="hibernate.cache.region.factory_class">  
         net.sf.ehcache.hibernate.EhCacheRegionFactory  
      </property>  
      <!-- Hibernate4以后都封装到org.hibernate.cache.ehcache.EhCacheRegionFactory -->  
      <!-- 指定缓存配置文件位置 -->  
      <!-- <property name="hibernate.cache.provider_configuration_file_resource_path">  
         ehcache.xml  
      </property> -->  
      <!-- 强制Hibernate以更人性化的格式将数据存入二级缓存 -->  
      <property name="hibernate.cache.use_structured_entries">true</property>  
  
      <!-- Hibernate将收集有助于性能调节的统计数据 -->  
      <property name="hibernate.generate_statistics">true</property>  
  
      ......  
  
   </session-factory>  
</hibernate-configuration>

 

然后是ehcache配置(ehcache.xml) 
cache参数详解: 
● name:指定区域名 
● maxElementsInMemory :缓存在内存中的最大数目 
● maxElementsOnDisk:缓存在磁盘上的最大数目 
● eternal :设置是否永远不过期 
● overflowToDisk : 硬盘溢出数目 
● timeToIdleSeconds :对象处于空闲状态的最多秒数后销毁 
● timeToLiveSeconds :对象处于缓存状态的最多秒数后销毁 
● memoryStoreEvictionPolicy:缓存算法,有LRU(默认)、LFU、LFU 

关于缓存算法,常见有三种: 
● LRU:(Least Rencently Used)新来的对象替换掉使用时间算最近很少使用的对象 
● LFU:(Least Frequently Used)替换掉按命中率高低算比较低的对象 
● LFU:(First In First Out)把最早进入二级缓存的对象替换掉 

ehcache.xml配置实例

<?xml version="1.0" encoding="UTF-8"?>  
<ehcache>  
  <!--如果缓存中的对象存储超过指定的缓存数量的对象存储的磁盘地址-->  
  <diskStore path="D:/ehcache"/>  
  
  <!-- 默认cache:如果没有对应的特定区域的缓存,就使用默认缓存 -->  
  <defaultCache maxElementsInMemory="10000"  
                eternal="false"  
                timeToIdleSeconds="300"   
                timeToLiveSeconds="600"  
                overflowToDisk="false"/>  
  <!-- 指定区域cache:通过name指定,name对应到Hibernate中的区域名即可-->  
  <cache name="cn.javass.h3test.model.UserModel"  
                eternal="false"  
                maxElementsInMemory="100"  
                timeToIdleSeconds="1200"  
                timeToLiveSeconds="1200"  
                overflowToDisk="false">  
  </cache>  
  
</ehcache>  

在每个实体的hbm文件中配置cache元素,usage可以是read-only或者是read-write等4种。 

Xml代码  

<?xml version="1.0" encoding='UTF-8'?>  
<!DOCTYPE hibernate-mapping PUBLIC  
                            "-//Hibernate/Hibernate Mapping DTD 3.0//EN"  
                            "http://hibernate.sourceforge.net/hibernate-mapping-3.0.dtd" >  
<hibernate-mapping>      
   <class>  
       <!-- 设置该持久化类的二级缓存并发访问策略 read-only read-write nonstrict-read-write transactional-->  
       <class name="cn.java.test.model.User" table="TBL_USER">  
              <cache usage="read-write"/>  
       ......    
   </class>  
</hibernate-mapping>  


也可以用Hibernate注解配置缓存实体类 

Java代码  

@Entity    
@Table    
@Cache(usage = CacheConcurrencyStrategy.READ_WRITE)  
public class User implements Serializable {    
    private static final long serialVersionUID = -5121812640999313420L;  
    private Integer id;  
    private String name;  
  
    ......  
}  


Query或Criteria接口查询时设置其setCacheable(true): 
默认的如果不在程序中显示的执行查询缓存声明操作,Hibernate是不会对查询的list进行缓存的。 

Java代码   

Session s1= HibernateSessionFactory.getCurrentSession();  
s1.beginTransaction();  
System.out.println("第一次查询User");  
Query q = s1.createQuery("from User");  
q.setCacheable(true);  
q.list();  
System.out.println("放进二级缓存");  
s1.getTransaction().commit();  
  
Session s2= HibernateSessionFactory.getCurrentSession();  
s2.beginTransaction();  
System.out.println("第二次查询User,将不会发出sql");  
Query q = s2.createQuery("from User");  
q.setCacheable(true);  
q.list();  
s2.getTransaction().commit();  
  
//如果配置文件打开了generate_statistics性能调解,可以得到二级缓存命中次数等数据  
Statistics s = HibernateSessionFactoryUtil.getSessionFactory().getStatistics();  
System.out.println(s);  
System.out.println("put:"+s.getSecondLevelCachePutCount());  
System.out.println("hit:"+s.getSecondLevelCacheHitCount());  
System.out.println("miss:"+s.getSecondLevelCacheMissCount()); 


如果开启了二级缓存,由于session是共享二级缓存的,只要缓存里面有要查询的对象,就不会向数据库发出sql,如果在二级缓存里没有找到需要的数据就会发出sql语句去数据库拿。 

一些对二级缓存的理解 
当hibernate更新数据库的时候,它怎么知道更新哪些查询缓存呢? 
hibernate在一个地方维护每个表的最后更新时间,其实也就是放在上面net.sf.hibernate.cache.UpdateTimestampsCache所指定的缓存配置里面。 
当通过hibernate更新的时候,hibernate会知道这次更新影响了哪些表。然后它更新这些表的最后更新时间。每个缓存都有一个生成时间和这个缓存所查询的表,当hibernate查询一个缓存是否存在的时候,如果缓存存在,它还要取出缓存的生成时间和这个缓存所查询的表,然后去查找这些表的最后更新时间,如果有一个表在生成时间后更新过了,那么这个缓存是无效的。 
如果找到的时间戳晚于高速缓存查询结果的时间戳,那么缓存结果将被丢弃,重新执行一次查询。 
可以看出,只要更新过一个表,那么凡是涉及到这个表的查询缓存就失效了,因此查询缓存的命中率可能会比较低。 

使用二级缓存的前置条件 
对于那些查询非常多但插入、删除、更新非常少的应用程序来说,查询缓存可提升性能。但写入多查询少的没有用,总失效。 
hibernate程序对数据库有独占的写访问权,其他的进程更新了数据库,hibernate是不可能知道的。 
你操作数据库必需直接通过hibernate,如果你调用存储过程,或者自己使用jdbc更新数据库,hibernate也是不知道的。 
这个限制相当的棘手,有时候hibernate做批量更新、删除很慢,但是你却不能自己写jdbc来优化。 
当然可以用SessionFactory提供的移除缓存的方法(上面的二级缓存的管理里面有介绍) 

总结 
不要想当然的以为缓存一定能提高性能,仅仅在你能够驾驭它并且条件合适的情况下才是这样的。hibernate的二级缓存限制还是比较多的,不方便用jdbc可能会大大的降低更新性能。在不了解原理的情况下乱用,可能会有1+N的问题。不当的使用还可能导致读出脏数据。 

如果受不了Hibernate的诸多限制,那么还是自己在应用程序的层面上做缓存吧! 
在越高的层面上做缓存,效果就会越好。就好像尽管磁盘有缓存,数据库还是要实现自己的缓存,尽管数据库有缓存,咱们的应用程序还是要做缓存。因为底层的缓存它并不知道高层要用这些数据干什么,只能做的比较通用,而高层可以有针对性的实现缓存,所以在更高的级别上做缓存,效果也要好些吧! 

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!