1. 一级缓存:Session中共享
测试:可以通过查看输出的 select 语句的数目来测试Hibernate中的Session级的缓存
使用缓存的三种操作:放,取,删
会从缓存中拿数据的方法:get,load,iterate [id为native(自增长的形式的话,save方法是不会放入到一级缓存或者二级缓存中的)]
会向缓存中放入数据的方法:save,update,saveOrUpdate,get,load,iterate,lock(还没有使用过,lock是把一个没有更改过的脱管状态的对象变成持久状态)
删除缓存内容的方法:evict(清除某个数据),clear(清除所有 s.clear() 清除一级缓存中的内容)
缺点:Hibernate的缓存内容默认是不会清除的,所以,如果数据内容太大时会造成缓存溢出!
还有一个缺点是:作用范围比较小,只在session关闭之前有效
模拟缓存实现:Map
package cn.itcast.hibernate; import java.util.HashMap; import java.util.Map; import cn.itcast.hibernate.domain.User; public class CacheDemo { static Map cache = new HashMap(); public static void main(String[] args) { User u = getUser(1);//第一次是从数据库中取出来 User u1 = getUser(1);//第二次是直接从缓存中取数据 } public static void update(User user){ updateDB(user); String key = User.class.getName() + user.getId(); cache.remove(key); } public static User getUser(int id) { String key = User.class.getName() + id; User user = (User) cache.get(key); if (user != null) return user; user = getFromDB(); cache.put(key, user); return user; } private static void updateDB(User user) { // TODO Auto-generated method stub } private static User getFromDB() { // TODO Auto-generated method stub return null; } }
2. 二级缓存:SessionFactory级共享
实现为可插拔,通过修改cache.provider_class参数来改变;
hibernate内置了对EhCache,OSCache,TreeCache,SwarmCache的支持,可以通过实现CacheProvider和Cache接口来加入Hibernate不支持的缓存实现。
Hibernate默认的二级缓存支持类:hibernate.cache.provider_class org.hibernate.cache.HashtableCacheProvider 采用的是Hashtable,但是性能并不是很好
如果要设置某个类的对象存入缓存中有两种方法:
①在hibernate.cfg.xml中加入:
<class-cache class="className" usage="read-only"/>
②在映射文件的class元素加入子元素:
<cache usage="read-write"/>
其中usage:read-only,read-write,nonstrict-read-write,transactional
配置:
1.开启二级缓存
2.设置二级缓存的第三方类
3.导入相应的包并配置
4.设置允许放入缓存的类
例如:
hibernate.cfg.xml
<!-- 开启二级缓存 --> <property name="hibernate.cache.use_second_level_cache">true</property> <!-- 开启查询语句可以使用二级缓存 --> <property name="hibernate.cache.use_query_cache">true</property> <!-- 设置二级缓存的支持类 --> <property name="hibernate.cache.provider_class">org.hibernate.cache.OSCacheProvider</property> <!-- 设置可以放入缓存中的类 --> <class-cache usage="read-only" class="com.yinger.domain.User"/>
添加 文件 oscache.properties (一般是在Hibernate下载到的etc文件夹中),可以自行设置里面的属性值
统计信息:可以得到很多的关于二级缓存的信息(例如:hit,put,miss次数)
用sessionFactory.getSatistics()获取统计信息
示例代码:
Statistics st = HibernateUtils.getSessionFactory().getStatistics(); System.out.println(st.getSecondLevelCachePutCount()); System.out.println(st.getSecondLevelCacheHitCount()); System.out.println(st.getSecondLevelCacheMissCount());
从二级缓存取数据的方法:get,load,iterator,而存数据的方法很多
Session的save(这个方法不适合native生成方式的主键),update,saveOrUpdate,list,iterator,get,load,以及Query,Criteria都会填充二级缓存,
但(在没打开查询缓存时)只有Session的iterator,get,load会从二级缓存中取数据(iterator可能存在N+1次查询,如果没有取到数据的话)。
Query,Criteria(查询缓存)由于命中率较低,所以hibernate缺省是关闭;修改cache.use_query_cache为true打开对查询的缓存,
并且调用query.setCacheable(true)或criteria.setCacheable(true)。
测试代码: N+1次查询的问题的代码 /** * @Author:胡家威 * @CreateTime:2011-8-16 上午12:52:21 * @Description: */ package com.yinger.main; import java.util.Date; import java.util.Iterator; import java.util.List; import org.hibernate.Hibernate; import org.hibernate.Query; import org.hibernate.Session; import org.hibernate.Transaction; import org.hibernate.stat.Statistics; import com.yinger.domain.IdCard; import com.yinger.domain.Person; import com.yinger.util.HibernateUtils; public class One2One { public static void main(String[] args) { add(); add(); System.out.println("-------------"); ncpp(); System.out.println("-------------"); iterate(); // Statistics st = HibernateUtils.getSessionFactory().getStatistics(); // System.out.println(st.getSecondLevelCachePutCount()); // System.out.println(st.getSecondLevelCacheHitCount()); // System.out.println(st.getSecondLevelCacheMissCount()); // IdCard card = queryIdCardById(1); // System.out.println(card.getPerson().getName()); } // 添加信息到数据库中 private static void add() { Person p = new Person(); p.setName("person name"); IdCard card = new IdCard(); card.setLife(new Date()); // p.setIdCard(card); // 外键一对一映射的这种情况下 id_card表中的person_id字段没有值 card.setPerson(p); Session s = null; Transaction tr = null; try { s = HibernateUtils.getSession(); tr = s.beginTransaction(); s.save(p); s.save(card); tr.commit(); } catch (Exception e) { if (tr != null) tr.rollback(); } finally { if (s != null) s.close(); } } // 根据id查询IdCard public static IdCard queryIdCardById(int id) { IdCard card = null; Session s = null; try { s = HibernateUtils.getSession(); card = (IdCard) s.get(IdCard.class, id); Hibernate.initialize(card.getPerson()); // 可以使用这个方法来初始化代理对象 // System.out.println(card.getPerson().getName()); return card; } catch (Exception e) { e.printStackTrace(); } finally { if (s != null) { s.close(); } } return card; } // 测试 N+1 次查询 @SuppressWarnings("unchecked") private static void ncpp() { Session s = HibernateUtils.getSession(); Query q = s.createQuery("from IdCard"); q.setCacheable(true); List<IdCard> ics = q.list(); for (IdCard ic : ics) { System.out.println(ic.getPerson().getName()); } s.close(); } // 测试 N+1 次查询 @SuppressWarnings("unchecked") private static void iterate() { Session s = HibernateUtils.getSession(); Query q = s.createQuery("from IdCard"); q.setCacheable(true); Iterator<IdCard> it = q.iterate(); while(it.hasNext()) { System.out.println(it.next().getPerson().getName()); } s.close(); } }
如果在hibernate.cfg.xml中配置了
<!-- 设置可以放入缓存中的类 --> <class-cache usage="read-only" class="com.yinger.domain.Person"/> <class-cache usage="read-only" class="com.yinger.domain.IdCard"/>
测试结果:
log4j:WARN No appenders could be found for logger (org.hibernate.cfg.Environment). log4j:WARN Please initialize the log4j system properly. Hibernate: insert into Person (name) values (?) Hibernate: insert into id_card (life) values (?) Hibernate: insert into Person (name) values (?) Hibernate: insert into id_card (life) values (?) ------------- Hibernate: select idcard0_.id as id, idcard0_.life as life4_ from id_card idcard0_ Hibernate: select person0_.id as id0_, person0_.name as name3_0_ from Person person0_ where person0_.id=? person name Hibernate: select person0_.id as id0_, person0_.name as name3_0_ from Person person0_ where person0_.id=? person name ------------- Hibernate: select idcard0_.id as col_0_0_ from id_card idcard0_ person name person name
如果调换一下 ncpp 和 iterate 方法的调用顺序,结果是:
log4j:WARN No appenders could be found for logger (org.hibernate.cfg.Environment). log4j:WARN Please initialize the log4j system properly. Hibernate: insert into Person (name) values (?) Hibernate: insert into id_card (life) values (?) Hibernate: insert into Person (name) values (?) Hibernate: insert into id_card (life) values (?) ------------- Hibernate: select idcard0_.id as col_0_0_ from id_card idcard0_ Hibernate: select idcard0_.id as id0_, idcard0_.life as life4_0_ from id_card idcard0_ where idcard0_.id=? Hibernate: select person0_.id as id0_, person0_.name as name3_0_ from Person person0_ where person0_.id=? person name Hibernate: select idcard0_.id as id0_, idcard0_.life as life4_0_ from id_card idcard0_ where idcard0_.id=? Hibernate: select person0_.id as id0_, person0_.name as name3_0_ from Person person0_ where person0_.id=? person name ------------- Hibernate: select idcard0_.id as id, idcard0_.life as life4_ from id_card idcard0_ person name person name
很清楚的发现两次查询实现了SessionFactory级别(也就是二级缓存)的共享,iterate方法共需要执行5个select,ncpp需要调用3个select
如果没有在hibernate配置文件中加上那两句,是不行的,默认这两个类是不会放入二级缓存的,结果总是3+5=8条select语句
[不过奇怪的是我删掉了q.setCacheable(true);语句之后结果不变,按理应该不会存入到二级缓存中的啊]
3.其他的缓存
分布式缓存(每台服务器保存自己的缓存):如果数据更新了,并且需要更新的其他服务器很多,那么广播出去进行更新的话就很耗时 [读取次数大于更新次数]
中央缓存(利用一台服务器来进行缓存):读取数据时要从另一台服务器得到,这个比较耗时
使用缓存的条件:
1.读取大于修改。
2.数据量不能超过内存容量。
3.对数据要有独享的控制。
4.可以容忍出现无效数据。
优秀的文章推荐:
Hiberante3 一级缓存总结
1. Session 级别的缓存,它同session邦定。它的生命周期和session相同。Session消毁,它也同时消毁;管理一级缓存,一级缓存无法取消,用两个方法管理,clear(),evict()
2. 两个session 不能共享一级缓存,因它会伴随session的生命周期的创建和消毁;
3. Session缓存是实体级别的缓存,就是只有在查询对象级别的时候才使用,如果
使用HQL和SQL是查询属性级别的,是不使用一级缓存的!
4 . iterate 查询使用缓存,会发出查询Id的SQL和HQL语句,但不会发出查实体的,
它查询完会把相应的实体放到缓存里边,一些实体查询如果缓存里边有,就从缓存中查询,但还是会发出查询id的SQL和HQL语句。如果缓存中没有它会数据库中查询,然后将查询到的实体一个一个放到缓存中去,所以会有N+1问题出现。
5 . List()和iterate 查询区别:
使用iterate,list查询实体对象
*N+1问题,在默认情况下,使用query.iterate查询,有可以能出现N+1问题
所谓的N+1是在查询的时候发出了N+1条sql语句1:首先发出一条查询对象id列表的sqlN:
根据id列表到缓存中查询,如果缓存中不存在与之匹配的数据,那么会根据id发出相应的sql语句
list和iterate的区别?
list每次都会发出sql语句,list会向缓存中放入数据,而不利用缓存中的数据
iterate:在默认情况下iterate利用缓存数据,但如果缓存中不存在数据有可以能出现N+1问题
6.get()和load(),iterate方法都会使用一级缓存
7.hiberate3 session 存储对象的过程如下:
例如 object 对象
Session.save(object);
这时候不会把数据放到数据库,会先放到session缓存中去,数据库中没有相应记录,session.flush();才发SQL和HQL语句,数据库中有了相应记录,
但是数据库用select查不到,这是跟数据库事务级别有关系
Hiberante3 二级缓存总结
1.Hibernate3的(sessionFactory)二级缓存和session级别的缓存一样都只对实体对象做缓存,不对属性级别的查询做缓存;二级缓存的生命周期和sessionFactory的生命周期是一样的,sessionFactory可以管理二级缓存;
2.sessionFactory级别的缓存,需要手动配置;所有的session可以共享sessionFactory 级别的缓存;(一般把一些不经常变化的实体对象放到sessionFactory级别的缓存中)
3.Hiberante3二级缓存的配置和使用方法如下:
1. 必须把ehcache.jar包导入,然后到Hibernate3.2的etc文件下把ehcache.xml复制到工程src目录下(ehcache.xml里边的参数里边有详细英文说明);
(说明:ehcache.jar是第三方法的缓存产品,hiberante只是把它做了集成,还有好多第三方hibernate集成的缓存产品,相关说明请查阅hiberante3开发手册;ehcache支持分布应用的(这个和Hibernate3.2开发手册有出入,经过官网查证确实支持了),如果有分布式需求,请换成支持分布式的二级缓存产品,hiberate3开发手册都有相头说明。配置方法都类似);
4.Hibernate3的二级缓存默认是开起的,也可以指定开起。在hibernate.cfg.xml 文件下配置如下:
*修改hibernate.cfg.xml文件,开启二级缓存;
<property name=”hibernate.cache.use_second_level_cache”>true</property>
*指定二级缓存产品的提供商;
<property name=”hibernate.cache.provider_class”> org.hibernate.cache.EhCacheProvider
</property>
要让那些实体使用二级缓存,在hibernate.cfg.xml配置文件中加入:
<!—
让这个实体用二级缓存 也可以在实体中映射文件去配置即:
<cache usage="read-only"/>
-->
<class-cache class=”com.zzz.hibernate.ClassT” usage=”read-only”/>
Read-only一般使用这个策略,其它的hibernate3开发手册中也有详细介绍;
CacheMode去hibernate3开发手册中搜索这个关键字,可以找到一级缓存和二级缓存交互使用的问题;