位于速度相差较大的两种硬件/软件之间的,用于协调两者数据传输速度差异的结构,均可称之为缓存Cache。缓存目的:让数据更接近于应用程序,协调速度不匹配,使访问速度更快。
缓存的范围分为3类:
1.事务范围(单Session即一级缓存)事务范围的缓存只能被当前事务访问,每个事务都有各自的缓存,缓存内的数据通常采用相互关联的对象形式.缓存的生命周期依赖于事务的生命周期,只有当事务结束时,缓存的生命周期才会结束.事务范围的缓存使用内存作为存储介质,一级缓存就属于事务范围.
2.应用范围(单SessionFactory即二级缓存)
应用程序的缓存可以被应用范围内的所有事务共享访问.缓存的生命周期依赖于应用的生命周期,只有当应用结束时,缓存的生命周期才会结束.应用范围的缓存可以使用内存或硬盘作为存储介质,二级缓存就属于应用范围.
3.集群范围(多SessionFactory)
在集群环境中,缓存被一个机器或多个机器的进程共享,缓存中的数据被复制到集群环境中的每个进程节点,进程间通过远程通信来保证缓存中的数据的一致,缓存中的数据通常采用对象的松散数据形式.
二级缓存如何工作的
Hibernate的二级缓存同一级缓存一样,也是针对对象ID来进行缓存。所以说,二级缓存的作用范围是针对根据ID获得对象的查询。
● 在执行各种条件查询时,如果所获得的结果集为实体对象的集合,那么就会把所有的数据对象根据ID放入到二级缓存中。
● 当Hibernate根据ID访问数据对象的时候,首先会从Session一级缓存中查找,如果查不到并且配置了二级缓存,那么会从二级缓存中查找,如果还查不到,就会查询数据库,把结果按照ID放入到缓存中。
● 删除、更新、增加数据的时候,同时更新缓存。
与Hibernate一级缓存Session范围相对的是SessionFactory范围的二级缓存,SessionFactory也提供了相应的缓存机制。SessionFactory缓存可以依据功能和目的的不同而划分为内置缓存和外置缓存。
SessionFactory的内置缓存中存放了映射元数据和预定义SQL语句,映射元数据是映射文件中数据的副本,而预定义SQL语句是在Hibernate初始化阶段根据映射元数据推导出来的。SessionFactory的内置缓存是只读的,应用程序不能修改缓存中的映射元数据和预定义SQL语句,因此SessionFactory不需要进行内置缓存与映射文件的同步。
SessionFactory的外置缓存是一个可配置的插件。在默认情况下,SessionFactory不会启用这个插件。外置缓存的数据是数据库数据的副本,外置缓存的介质可以是内存或者硬盘。SessionFactory的外置缓存也被称为Hibernate的二级缓存。
Hibernate的二级缓存的实现原理与一级缓存是一样的,也是通过以ID为key的Map来实现对对象的缓存。
二级缓存是缓存实体对象的,由于Hibernate的二级缓存是作用在SessionFactory范围内的,因而它比一级缓存的范围更广,可以被所有的Session对象所共享。
在通常情况下会将具有以下特征的数据放入到二级缓存中:
● 很少被修改的数据。
● 不是很重要的数据,允许出现偶尔并发的数据。
● 不会被并发访问的数据。
● 常量数据。
● 不会被第三方修改的数据
而对于具有以下特征的数据则不适合放在二级缓存中:
● 经常被修改的数据。
● 财务数据,绝对不允许出现并发。
● 与其他应用共享的数据。
在这里特别要注意的是对放入缓存中的数据不能有第三方的应用对数据进行更改(其中也包括在自己程序中使用其他方式进行数据的修改,例如,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)组件的支持了。
集群缓存的概念:
当一台服务器上的执行了update方法修改了一条数据,那么只有这一台服务器上的二级缓存会同步于数据库,其他服务器上的二级缓存里面这条数据就没意义了。这个时候用OSCache缓存机制,只要有一台服务器上有数据修改了,马上会从配置文件中找到配置好的其他服务器IP地址,进行广播,告诉他们我这条数据修改了,你们也更新同步一下。(是不是有点像手机上微博的推送功能)
如何在程序里使用二级缓存:
在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)把最早进入二级缓存的对象替换掉
- <?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 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注解配置缓存实体类
- @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进行缓存的。- 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语句去数据库拿。
一级缓存的管理:
● evit(Object obj)将指定的持久化对象从一级缓存中清除,释放对象所占用的内存资源,指定对象从持久化状态变为脱管状态,从而成为游离对象.
● clear()将一级缓存中的所有持久化对象清除,释放其占用的内存资源
● contains(Object obj)判断指定的对象是否存在于一级缓存中.
● flush()刷新一级缓存区的内容,使之与数据库数据保持同步.
二级缓存的管理:
● evict(Class arg0, Serializable arg1)将某个类的指定ID的持久化对象从二级缓存中清除,释放对象所占用的资源.
- sessionFactory.evict(Customer.class, new Integer(1));
evict(Class arg0)将指定类的所有持久化对象从二级缓存中清除,释放其占用的内存资源
- sessionFactory.evict(Customer.class);
evictCollection(String arg0)将指定类的所有持久化对象的指定集合从二级缓存中清除,释放其占用的内存资源.
- sessionFactory.evictCollection("Customer.orders");
设置一级缓存和二级缓存的交互权限
- session = HibernateUtils.getSession();
- session.beginTransaction();
- //仅向二级缓存读数据,而不向二级缓存写数据,这里load的数据就不会放入二级缓存,下次再查还是会去数据库拿
- session.setCacheMode(CacheMode.GET);
- //只向二级缓存写数据,而不从二级缓存读数据
- //session.setCacheMode(CacheMode.PUT);
- //不与二级缓存交互
- //session.setCacheMode(CacheMode.IGNORE);
- //可以与二级缓存交互
- //session.setCacheMode(CacheMode.NORMAL);
- Student student = (Student)session.load(Student.class, 1);
- session.getTransaction().commit();
设置二级缓存策略
● READ_ONLY:实体只读缓存
只读缓存不允许更新,将报错Can't write to a readonly object。
允许新增,(从2.0以后新增直接添加到二级缓存)
- //确保数据库中有标识符为1的FarmModel
- FarmModel farm = (FarmModel) session.get(FarmModel.class, 1);
- //如果修改将报错,只读缓存不允许修改
- //farm.setName("aaa");
● NONSTRICT_READ_WRITE:实体非严格读/写缓存
允许更新,更新后缓存失效,需再查询一次。
允许新增,新增记录自动加到二级缓存中。
整个过程不加锁。
● READ_WRITE:实体读/写缓存
允许更新,更新后自动同步到缓存。
允许新增,新增记录后自动同步到缓存。
保证read committed隔离级别及可重复读隔离级别(通过时间戳实现)
整个过程加锁,如果当前事务的时间戳早于二级缓存中的条目的时间戳,说明该条目已经被别的
事务修改了,此时重新查询一次数据库,否则才使用缓存数据,因此保证可重复读隔离级别。
读写缓存和不严格读写缓存在实现上的区别在于,读写缓存更新缓存的时候会把缓存里面的数据换成一个锁
● TRANSACTIONAL:实体事务缓存
缓存支持事务,发生异常的时候,缓存也能够回滚,只支持jta环境
● Collection集合缓存
- <hibernate-mapping>
- <class name="cn.java.test.model.UserModel" table="TBL_USER">
- <cache usage="read-write" />
- <set name="farms" cascade="all" inverse="true" lazy="false">
- <cache usage="read-write"/>
- <key column="fk_user_id"/>
- <one-to-many class="cn.java.test.model.FarmModel"/>
- </set>
- </class>
- </hibernate-mapping>
和实体并发策略有相同含义;
但集合缓存只缓存集合元素的标识符,在二级缓存中只存放相应实体的标识符,然后再通过标识符去二级缓存查找相应的实体最后组合为集合返回
Collection的缓存和前面查询缓存的list一样,也是只保持一串id,但它不会因为这个表更新过就失效,一个collection缓存仅在这个collection里面的元素有增删时才失效。
这样有一个问题,如果你的collection是根据某个字段排序的,当其中一个元素更新了该字段时,导致顺序改变时,collection缓存里面的顺序没有做更新
高速缓存区域
Hibernate在不同的高速缓存区域保存不同的类(实体)/集合,如果不配置区域默认都保存到“默认缓存”(defaultCache)中。
●每一个区域可以设置过期策略、缓存条目大小等等。
●对于类缓存,默认区域名是全限定类名,如cn.javass.h3test.model.UserModel。
●对于集合而言,默认区域名是全限定类名+属性名,如cn.javass.….UserModel.farms。
●可通过hibernate.cache.region_prefix指定特定SessionFactory的区域前缀,如前缀是h3test,则如类缓存的区域名就是h3test.cn.javass.h3test.model.UserModel。如果应用程序使用多个SessionFactory这可能是必须的。
可通过<cache usage="read-write" region="区域名"/>自定义区域名,不过默认其实就可以了。
一些对二级缓存的理解
当hibernate更新数据库的时候,它怎么知道更新哪些查询缓存呢?
hibernate在一个地方维护每个表的最后更新时间,其实也就是放在上面net.sf.hibernate.cache.UpdateTimestampsCache所指定的缓存配置里面。
当通过hibernate更新的时候,hibernate会知道这次更新影响了哪些表。然后它更新这些表的最后更新时间。每个缓存都有一个生成时间和这个缓存所查询的表,当hibernate查询一个缓存是否存在的时候,如果缓存存在,它还要取出缓存的生成时间和这个缓存所查询的表,然后去查找这些表的最后更新时间,如果有一个表在生成时间后更新过了,那么这个缓存是无效的。
如果找到的时间戳晚于高速缓存查询结果的时间戳,那么缓存结果将被丢弃,重新执行一次查询。
可以看出,只要更新过一个表,那么凡是涉及到这个表的查询缓存就失效了,因此查询缓存的命中率可能会比较低。
使用二级缓存的前置条件
对于那些查询非常多但插入、删除、更新非常少的应用程序来说,查询缓存可提升性能。但写入多查询少的没有用,总失效。
hibernate程序对数据库有独占的写访问权,其他的进程更新了数据库,hibernate是不可能知道的。
你操作数据库必需直接通过hibernate,如果你调用存储过程,或者自己使用jdbc更新数据库,hibernate也是不知道的。
这个限制相当的棘手,有时候hibernate做批量更新、删除很慢,但是你却不能自己写jdbc来优化。
当然可以用SessionFactory提供的移除缓存的方法(上面的二级缓存的管理里面有介绍)
常见Java缓存框架
§ EHCache
§ OSCache
§ JBossCache
§ SwarmCache
5、通用缓存产品
§ Memcached:在大规模互联网应用下使用,可用于分布式环境,每秒支撑1.5万~2万次请求
§ Tokyo Tyrant:兼容memcached协议,可以持久化存储,支持故障切换,对缓存服务器有高可靠性要求可以使用,每秒支撑0.5万~0.8万次请求
6、基于Web应用的缓存应用场景:
(摘自bluedavy的《大型网站架构演化》)
8、缓存实战:
8.4、ORM缓存
8.4.1、目的:
Hibernate缓存:使当前数据库状态的表示接近应用程序,要么在内存中,要么在应用程序服务器机器的磁盘上。高速缓存是数据的一个本地副本,处于应用程序和数据库之间,可用来避免数据库的命中。
8.4.2、避免数据库命中:
应用程序根据标识符到缓存查,有就返回,没有再去数据库.
8.4.3、ORM缓存分类
一级缓存、二级缓存
8.4.4、缓存范围
1、事务范围高速缓存,对应于一级缓存(单Session)
2、过程(JVM)范围高速缓存,对应于二级缓存(单SessionFactory)
3、集群范围高速缓存,对应于二级缓存(多SessionFactory)
8.4.5、缓存哪些数据
1、很少改变的数据;
2、不重要的数据,如论坛帖子,无需实时的数据;
3、应用程序固有的而非共享的。
4、读大于写有用
8.4.6、Hibernate缓存架构
图摘自《Hibernate in Action》
§ Hibernate中的二级缓存是可插拔的。
§ Hibernate二级缓存支持对象缓存、集合缓存、查询结果集缓存,对于查询结果集缓存可选。
§ 查询缓存:需要两个额外的物理高速缓存区域:一个用于存放查询的结果集;另一个用于存储表上次更新的时间戳
8.4.6.2、高速缓存实战(ehcache)
8.4.6.2.3、实体只读缓存
1、修改FarmModel.hbm.xml,添加如下红色部分配置,表示实体缓存并只读
- <hibernate-mapping>
- <class name="cn.javass.h3test.model.FarmModel" table="TBL_FARM">
- <cache usage="read-only"/>
- ……
- </hibernate-mapping>
2、测试代码
- public static void readonlyTest() {
- SessionFactory sf =
- new Configuration().configure().buildSessionFactory();
- Session session1 = sf.openSession();
- Transaction t1 = session1.beginTransaction();
- //确保数据库中有标识符为1的FarmModel
- FarmModel farm = (FarmModel) session1.get(FarmModel.class, 1);
- //如果修改将报错,只读缓存不允许修改
- //farm.setName("aaa");
- t1.commit();
- session1.close();
- Session session2 = sf.openSession();
- Transaction t2 = session2.beginTransaction();
- farm = (FarmModel) session2.get(FarmModel.class, 1);
- t2.commit();
- session2.close();
- sf.close();
- }
§ 只读缓存不允许更新,将报错Can't write to a readonly object。
§ 允许新增,(现在2。0 新增直接添加到二级缓存)
8.4.6.2.4、实体非严格读/写缓存
1、修改FarmModel.hbm.xml,添加如下红色部分配置,表示实体缓存并非严格读/写
- <hibernate-mapping>
- <class name="cn.javass.h3test.model.FarmModel" table="TBL_FARM">
- <cache usage="nonstrict-read-write"/>
- ……
- </hibernate-mapping>
2、测试代码
- public static void nonstrictReadWriteTest () {
- SessionFactory sf =
- new Configuration().configure().buildSessionFactory();
- Session session1 = sf.openSession();
- Transaction t1 = session1.beginTransaction();
- //确保数据库中有标识符为1的FarmModel
- FarmModel farm = (FarmModel) session1.get(FarmModel.class, 1);
- t1.commit();
- session1.close();
- Session session2 = sf.openSession();
- Transaction t2 = session2.beginTransaction();
- farm = (FarmModel) session2.get(FarmModel.class, 1);
- t2.commit();
- session2.close();
- sf.close();
- }
§ 允许更新,更新后缓存失效,需再查询一次。
§ 允许新增,新增记录自动加到二级缓存中。
§ 整个过程不加锁,不保证。
8.4.6.2.5、实体读/写缓存
1、修改FarmModel.hbm.xml,添加如下红色部分配置,表示实体缓存并读/写
- <hibernate-mapping>
- <class name="cn.javass.h3test.model.FarmModel" table="TBL_FARM">
- <cache usage="read-write"/>
- ……
- </hibernate-mapping>
2、测试代码
- public static void readWriteTest() {
- SessionFactory sf =
- new Configuration().configure().buildSessionFactory();
- Session session1 = sf.openSession();
- Transaction t1 = session1.beginTransaction();
- //确保数据库中有标识符为1的FarmModel
- FarmModel farm = (FarmModel) session1.get(FarmModel.class, 1);
- farm.setName("as");
- t1.commit();
- session1.close();
- Session session2 = sf.openSession();
- Transaction t2 = session2.beginTransaction();
- farm = (FarmModel) session2.get(FarmModel.class, 1);
- t2.commit();
- session2.close();
- sf.close();
- }
§ 允许更新,更新后自动同步到缓存。
§ 允许新增,新增记录后自动同步到缓存。
§ 保证read committed隔离级别及可重复读隔离级别(通过时间戳实现)
§ 整个过程加锁,如果当前事务的时间戳早于二级缓存中的条目的时间戳,说明该条目已经被别的事务修改了,此时重新查询一次数据库,否则才使用缓存数据,因此保证可重复读隔离级别。
8.4.6.2.6、实体事务缓存
需要特定缓存的支持和JTA事务支持,此处不演示。
8.4.6.2.7、集合缓存
此处演示读/写缓存示例,其他自测
1、修改FarmModel.hbm.xml,添加如下红色部分配置,表示实体缓存并读/写
- <hibernate-mapping>
- <class name="cn.javass.h3test.model.UserModel" table="TBL_USER">
- <cache usage="read-write" />
- <set name="farms" cascade="all" inverse="true" lazy="false">
- <cache usage="read-write"/>
- <key column="fk_user_id"/>
- <one-to-many class="cn.javass.h3test.model.FarmModel"/>
- </set>
- </class>
- </hibernate-mapping>
2、测试代码
- public static void collectionReadWriteTest() {
- SessionFactory sf =
- new Configuration().configure().buildSessionFactory();
- Session session1 = sf.openSession();
- Transaction t1 = session1.beginTransaction();
- //确保数据库中有标识符为118的UserModel
- UserModel user = (UserModel) session1.get(UserModel.class, 118);
- user.getFarms();
- t1.commit();
- session1.close();
- Session session2 = sf.openSession();
- Transaction t2 = session2.beginTransaction();
- user = (UserModel) session2.get(UserModel.class, 118);
- user.getFarms();
- t2.commit();
- session2.close();
- sf.close();
- }
§ 和实体并发策略有相同含义;
§ 但集合缓存只缓存集合元素的标识符,在二级缓存中只存放相应实体的标识符,然后再通过标识符去二级缓存查找相应的实体最后组合为集合返回。
8.4.6.2.8、查询缓存
1、保证全局配置中有开启了查询缓存。
2、修改FarmModel.hbm.xml,添加如下红色部分配置,表示实体缓存并读/写
- <hibernate-mapping>
- <class name="cn.javass.h3test.model.FarmModel" table="TBL_FARM">
- <cache usage="read-write"/>
- ……
- </hibernate-mapping>
3、测试代码
- public static void queryCacheTest() {
- SessionFactory sf =
- new Configuration().configure().buildSessionFactory();
- Session session1 = sf.openSession();
- Transaction t1 = session1.beginTransaction();
- Query query = session1.createQuery("from FarmModel");
- //即使全局打开了查询缓存,此处也是必须的
- query.setCacheable(true);
- List<FarmModel> farmList = query.list();
- t1.commit();
- session1.close();
- Session session2 = sf.openSession();
- Transaction t2 = session2.beginTransaction();
- query = session2.createQuery("from FarmModel");
- //即使全局打开了查询缓存,此处也是必须的
- query.setCacheable(true);
- farmList = query.list();
- t2.commit();
- session2.close();
- sf.close();
- }
§ 和实体并发策略有相同含义;
§ 和集合缓存类似,只缓存集合元素的标识符,在二级缓存中只存放相应实体的标识符,然后再通过标识符 去二级缓存查找相应的实体最后组合为集合返回。
8.4.6.2.9、高速缓存区域
Hibernate在不同的高速缓存区域保存不同的类(实体)/集合,如果不配置区域默认都保存到“默认缓存”(defaultCache)中。
每一个区域可以设置过期策略、缓存条目大小等等。
对于类缓存,默认区域名是全限定类名,如cn.javass.h3test.model.UserModel。
对于集合而言,默认区域名是全限定类名+属性名,如cn.javass.….UserModel.farms。
可通过hibernate.cache.region_prefix指定特定SessionFactory的区域前缀,如前缀是h3test,则如类缓存的区域名就是h3test. cn.javass.h3test.model.UserModel。如果应用程序使用多个SessionFactory 这可能是必须的。
可通过<cache usage="read-write" region="区域名"/>自定义区域名,不过默认其实就可以了。
8.4.6.2.10、ehcache配置详解:
1、默认cache:如果没有对应的特定区域的缓存,就使用默认缓存。
- <defaultCache
- maxElementsInMemory="100"
- eternal="false"
- timeToIdleSeconds="1200"
- timeToLiveSeconds="1200"
- overflowToDisk="false">
- </defaultCache>
2、指定区域cache:通过name指定,name对应到Hibernate中的区域名即可。
- <cache name="cn.javass.h3test.model.UserModel"
- maxElementsInMemory="100"
- eternal="false"
- timeToIdleSeconds="1200"
- timeToLiveSeconds="1200"
- overflowToDisk="false">
- </cache>
3、cache参数详解:
name:指定区域名
maxElementsInMemory :缓存在内存中的最大数目
maxElementsOnDisk:缓存在磁盘上的最大数目
eternal :缓存是否持久
overflowToDisk : 硬盘溢出数目
timeToIdleSeconds :当缓存条目闲置n秒后销毁
timeToLiveSeconds :当缓存条目存活n秒后销毁
memoryStoreEvictionPolicy:缓存算法,有LRU(默认)、LFU、FIFO
4、StandardQueryCache
用于查询缓存使用,如果指定了该缓存,那么查询缓存将放在该缓存中。
- <cache
- name="org.hibernate.cache.StandardQueryCache"
- maxElementsInMemory="5"
- eternal="false"
- timeToLiveSeconds="120"
- overflowToDisk="true"/>
如果不给查询设置区域名默认缓存到这,可以通过“query.setCacheRegion("区域名");”来设置查询的区域名。
时间戳缓存,内部使用,用于保存最近更新的表的时间戳,这是非常重要的,无需失效,关闭时间戳缓存区域的过期时间。
- <cache
- name="org.hibernate.cache.UpdateTimestampsCache"
- maxElementsInMemory="5000"
- eternal="true"
- overflowToDisk="true"/>
Hibernate使用时间戳区域来决定被高速缓存的查询结果集是否是失效的。当你重新执行了一个启用了高速缓存的查询时,Hibernate就在时间戳缓存中查找对被查询的(几张)表所做的最近插入、更新或删除的时间戳。如果找到的时间戳晚于高速缓存查询结果的时间戳,那么缓存结果将被丢弃,重新执行一次查询。
8.4.6.2.11、什么时候需要查询缓存
大多数时候无法从结果集高速缓存获益。必须知道:每隔多久重复执行同一查询。
对于那些查询非常多但插入、删除、更新非常少的应用程序来说,查询缓存可提升性能。但写入多查询少的没有用,总失效。
8.4.6.2.12、管理一级缓存
无论何时,当你给save()、update()或 saveOrUpdate()方法传递一个对象时,或使用load()、 get()、list()、iterate() 或scroll()方法获得一个对象时, 该对象都将被加入到Session的内部缓存中。
当随后flush()方法被调用时,对象的状态会和数据库取得同步。 如果你不希望此同步操作发生,或者你正处理大量对象、需要对有效管理内存时,你可以调用evict() 方法,从一级缓存中去掉这些对象及其集合。
ScrollableResult cats = sess.createQuery("from Cat as cat").scroll(); //a huge result set
while ( cats.next() ) {
Cat cat = (Cat) cats.get(0);
doSomethingWithACat(cat);
sess.evict(cat);
}
Session还提供了一个contains()方法,用来判断某个实例是否处于当前session的缓存中。
如若要把所有的对象从session缓存中彻底清除,则需要调用Session.clear()。
CacheMode参数用于控制具体的Session如何与二级缓存进行交互。
CacheMode.NORMAL - 从二级缓存中读、写数据。
CacheMode.GET - 从二级缓存中读取数据,仅在数据更新时对二级缓存写数据。
CacheMode.PUT - 仅向二级缓存写数据,但不从二级缓存中读数据。
CacheMode.REFRESH - 仅向二级缓存写数据,但不从二级缓存中读数据。通过 hibernate.cache.use_minimal_puts的设置,强制二级缓存从数据库中读取数据,刷新缓存内容。
8.4.6.2.12、管理二级缓存
对于二级缓存来说,在SessionFactory中定义了许多方法, 清除缓存中实例、整个类、集合实例或者整个集合。
sessionFactory.evict(Cat.class, catId); //evict a particular Cat
sessionFactory.evict(Cat.class); //evict all Cats
sessionFactory.evictCollection("Cat.kittens", catId); //evict a particular collection of kittens
sessionFactory.evictCollection("Cat.kittens"); //evict all kitten collections
sessionFactory.evictQueries()//evict all queries
8.4.6.2.13、监控二级缓存
如若需要查看二级缓存或查询缓存区域的内容,你可以使用统计(Statistics) API。
通过sessionFactory.getStatistics();获取Hibernate统计信息。
此时,你必须手工打开统计选项。
hibernate.generate_statistics true
hibernate.cache.use_structured_entries true
Hibernate中获取数据常用的方式主要有四种:Session.load、Session.get、Query.list、Query.iterator。
1、Session.load
在执行session.load时,Hibernate首先从当前session的一级缓存中获取id对应的值,在获取不到的情况下,将根据该对象是否配置了二级缓存来做相应的处理,如配置了二级缓存,则从二级缓存中获取id对应的值,如仍然获取不到则还需要根据是否配置了延迟加载来决定如何执行,如未配置延迟加载则从数据库中直接获取,在从数据库获取到数据的情况下,Hibernate会相应的填充一级缓存和二级缓存,如配置了延迟加载则直接返回一个代理类,只有在触发代理类的调用时才进行数据库查询的操作。
在这样的情况下我们就可以看到,在session一直打开的情况下,要注意在适当的时候对一级缓存进行刷新操作,通常是在该对象具有单向关联维护的时候,在Hibernate中可以使用象session.clear、session.evict的方式来强制刷新一级缓存。
二级缓存则在数据发生任何变化(新增、更新、删除)的情况下都会自动的被更新。
2、Session.get
在执行Session.get时,和Session.load不同的就是在当从缓存中获取不到时,直接从数据库中获取id对应的值。
3、Query.list
在执行Query.list时,Hibernate的做法是首先检查是否配置了查询缓存,如配置了则从查询缓存中查找key为查询语句+查询参数+分页条件的值,如获取不到则从数据库中进行获取,从数据库获取到后Hibernate将会相应的填充一级、二级和查询缓存,如获取到的为直接的结果集,则直接返回,如获取到的为一堆id的值,则再根据id获取相应的值(Session.load),最后形成结果集返回,可以看到,在这样的情况下,list也是有可能造成N次的查询的。
查询缓存在数据发生任何变化的情况下都会被自动的清空。
4、Query.iterator
在执行Query.iterator时,和Query.list的不同的在于从数据库获取的处理上,Query.iterator向数据库发起的是select id from这样的语句,也就是它是先获取符合查询条件的id,之后在进行iterator.next调用时才再次发起session.load的调用获取实际的数据。
可见,在拥有二级缓存并且查询参数多变的情况下,Query.iterator会比Query.list更为高效。
这四种获取数据的方式都各有适用的场合,要根据实际情况做相应的决定,^_^,最好的方式无疑就是打开show_sql选项看看执行的情况来做分析,系统结构上只用保证这种调整是容易实现的就好了,在cache这个方面的调整自然是非常的容易,只需要调整配置文件里的设置,而查询的方式则可对外部进行屏蔽,这样要根据实际情况调整也非常容易。