缓存:缓存是什么,解决什么问题?
位于速度相差较大的两种硬件/软件之间的,用于协调二者数据传输速度差别的结构,都可称之为缓存Cache。缓存目的:让数据更接近于应用程序,协调速度不匹配,使访问速度更快。
java
缓存的范围分为3类:
1.事务范围(单Session即一级缓存)
事务范围的缓存只能被当前事务访问,每一个事务都有各自的缓存,缓存内的数据一般采用相互关联的对象形式.缓存的生命周期依赖于事务的生命周期,只有当事务结束时,缓存的生命周期才会结束.事务范围的缓存使用内存做为存储介质,一级缓存就属于事务范围.
2.应用范围(单SessionFactory即二级缓存)
应用程序的缓存能够被应用范围内的全部事务共享访问.缓存的生命周期依赖于应用的生命周期,只有当应用结束时,缓存的生命周期才会结束.应用范围的缓存可使用内存或硬盘做为存储介质,二级缓存就属于应用范围.
3.集群范围(多SessionFactory)
在集群环境中,缓存被一个机器或多个机器的进程共享,缓存中的数据被复制到集群环境中的每一个进程节点,进程间经过远程通讯来保证缓存中的数据的一致,缓存中的数据一般采用对象的松散数据形式.算法
一级缓存(session):内部缓存sql
事务范围:缓存只能被当前事务访问。缓存的生命周期依赖于事务的生命周期,当事务结束时,缓存也就结束生命周期。数据库
二级缓存(sessionFactory):缓存
缓存被应用范围内的全部事务共享。 这些事务有多是并发访问缓存,所以必须对缓存进行更新。缓存的生命周期依赖于应用的生命周期,应用结束时, 缓存也就结束了生命周期,二级缓存存在于应用范围。集群范围:在集群环境中,缓存被一个机器或者多个机器的进程共享。缓存中的数据被复制到集群环境中的每一个进程节点,进程间经过远程通讯来保证缓存中的数据的一致性, 缓存中的数据一般采用对象的松散数据形式,二级缓存也存在与应用范围。session
注意:对大多数应用来讲,应该慎重地考虑是否须要使用集群范围的缓存,再加上集群范围还有数据同步的问题,因此应当慎用。多种范围的缓存处理过程持久化层能够提供多种范围的缓存。若是在事务范围的缓存中没有查到相应的数据,还能够到应用范围或集群范围的缓存内查询,若是仍是没有查到,那么只有到数据库中查询了。并发
在一般状况下会将具备如下特征的数据放入到二级缓存中:
● 不多被修改的数据。
● 不是很重要的数据,容许出现偶尔并发的数据。
● 不会被并发访问的数据。
● 常量数据。
● 不会被第三方修改的数据
而对于具备如下特征的数据则不适合放在二级缓存中:
● 常常被修改的数据。
● 财务数据,绝对不容许出现并发。
● 与其余应用共享的数据。
在这里特别要注意的是对放入缓存中的数据不能有第三方的应用对数据进行更改(其中也包括在本身程序中使用其余方式进行数据的修改,例如,JDBC),由于那样Hibernate将不会知道数据已经被修改,也就没法保证缓存中的数据与数据库中数据的一致性app
常见的缓存组件
在默认状况下,Hibernate会使用EHCache做为二级缓存组件。可是,能够经过设置hibernate.cache.provider_class属性,指定其余的缓存策略,该缓存策略必须实现org.hibernate.cache.CacheProvider接口。
经过实现org.hibernate.cache.CacheProvider接口能够提供对不一样二级缓存组件的支持,此接口充当缓存插件与Hibernate之间的适配器。 ide
组件 | 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的诸多限制,那么仍是本身在应用程序的层面上作缓存吧! 在越高的层面上作缓存,效果就会越好。就好像尽管磁盘有缓存,数据库仍是要实现本身的缓存,尽管数据库有缓存,我们的应用程序仍是要作缓存。由于底层的缓存它并不知道高层要用这些数据干什么,只能作的比较通用,而高层能够有针对性的实现缓存,因此在更高的级别上作缓存,效果也要好些吧!