文章目录
Hibernate 缓存的引出
Hibernate是一个持久化框架,经常需要访问数据库。如果我们能够降低应用程序对物理数据库访问的频次,那会提供应用程序的运行性能。缓存内的数据是对物理数据源中的数据的复制,应用程序运行时先从缓存中读写数据。
Hibernate 缓存的一级缓存
Hibernate 如何缓存的效果
Hibernate一级缓存又被成为“Session的缓存”。Session缓存是内置的,不能被卸载,是事务范围的缓存。在一级缓存中,持久化类的每个实例都具有唯一的OID。
Configuration con = new Configuration().configure();
SessionFactory sf = con.buildSessionFactory();
Session ss = sf.openSession();
Transaction tt = ss.beginTransaction();
News n1 = ss.load(News.class, 1);
News n2 = ss.load(News.class, 2);
tt.commit();
ss.close();
sf.close();
在这段代码中,我们对我们调用两次load 方法,可以看后台打印的SQL语句,可以发现,就是select 语句 只有一句,这实际上就使用了缓存。实际上,第一次Session 加载对象的时候,它就存放被当前工作单元中加载的对象,也就是News这个对象对应表的数据。(这里涉及数据库的快照,这里我们就简单理解)(注意这个只是针对于查询,如果是插入、删除还是需要执行相应的SQL语句的)。
你需要记住的事,平常我们save、load的操作都是在session中,并不是真正的操作数据库。
Hibernate 何时清除缓存
- commit() 方法被调用时 。
- 显示的调用session 的 flush方法。
只有清除缓存,才是将数据真正的写入到数据库中。
Session 加载对象后会为对象值类型的属性复制一份快照。当Session 清理缓存时,比较当前对象和它的快照就可以知道那些属性发生了变化,进行相应的修改。
Hibernate 二级缓存
之前已经说过Session 级别的一级缓存,二级缓存是基于SessionFactory。
SessionFactory级别的二级缓存是全局性的(一般在整个应用中,我们SessionFactory 对象只会有一个),应用的所有Session都共享这个二级缓存。不过SessionFactory级别的缓存默认是关闭的,必须由程序显式开启。一旦在应用中开启了二级缓存,当Session需要抓取数据时, Session将会优先从二级缓存抓取。
开启二级缓存
在Hibernate 配置文件:
<property name="hibernate.cache.use_second_level_cache" >true</property>
指定二级缓存的实现类
这里实现类可以是多种,下面以EhCache 为例。
<property name="hibernate.cache.region.factory_class">org.hibernate.cache.ehcache.EhCacheRegionFactory</property>
Ehache 配置
Ehache 导包
导入EhCache的Jar包,可以在hibernate 下载的文件中找到
D:\hibernate\hibernate-release-5.3.0.Beta1\lib\optional\ehcache //对应自己的路径去查找,我用5.3 里面有2个,都拷到项目下。
ehcache 配置文件路径
需要添加Ehcache 的配置文件到项目,最好是和Hibernate 配置放在一个目录,以免有问题。配置文件可以直接从下面路径找到,直接修改相应的属性即可。
D:\hibernate\hibernate-release-5.3.0.Beta1\project\etc
配置文件属性介绍
<defaultCache
maxElementsInMemory="10000"
eternal="false"
timeToIdleSeconds="120"
timeToLiveSeconds="120"
overflowToDisk="false"
/>
- maxElementsInMemory 设置缓存中最多放多少对象
- eternal 设置缓存是否永久有效
- timeToIdleSeconds 设置对象多少秒没有被使用就clean
- timeToLiveSeconds 设置对象缓存长时间
- overflowToDisk 是否将缓存对象放到硬盘上,保存路径由diskStore 决定
对实体、集合启用二级缓存
两种方式:
- 修改要使用缓存的实体的映射文件。在持久化映射文件的<clas…>元素、或<set…/>、<ist…>等集合元素内使用<cache…/>元素指定缓存策略。
- 在hibernate.cfg.xml文件中使用<class-cache…/>或<collection-cache…/>元素对指定持久化类、集合属性启用二级缓存。
二级缓存策略
- 只读策略(read-only) :如果应用程序只需读取持久化实体的对象,无须对其进行修改,那么就可以对其设置“只读”缓存策略。这是最简单、也最实用的缓存策略。
- 读/写缓存(read/write) :如果应用程序需要更新数据,那就需要使用“读/写”缓存策略了。如果应用程序要求使用“序列化事务(serializable transaction)”的隔离级别,那就绝不能使用这种缓存策略。
- 非严格读/写(nonstrict read/write) :如果应用程序只需要偶尔更新数据(也就是说,两个事务同时更新同一记录的情况很少见),也不需要十分严格的事务隔离, 那么比较适合使用非严格读/写缓存策略。
- 事务缓存( transactional) :Hibernate的事务缓存策略提供了全事务的缓存支持。
二级缓存测试
java.util.List<Object> list = ss.createQuery("from Person p1").list();
Session ss1 = sf.openSession();
Person p1 = ss1.load(Person.class,4);
System.out.println(p1.getName());
Person p2 = ss.load(Person.class,3);
System.out.println(p2.getName());
打印的数据,查询语句只执行一次。
当Hibernate 首先查询出所有的数据,它会将这些实体存放在二级缓存中,当你下次查询某一个实体的时候,只要缓存还在,Hibernate会优先进入缓存中去拿。
在使用Ehcache 的时候,当你在发布或者重新部署的时候,一定要重启tomcat,否则Ehcache manager 会报什么已经create错误。
缓存查找的方式,先到一级缓存中查找,如果开启二级缓存,在二级缓存中查找,最后到数据库中查找。
Session级别的一级缓存是局部缓存,它只对当前Session有效; SessionFactory 级别的二-级缓存 是全局缓存,它对所有的Session都有效。
管理缓存和统计缓存
管理缓存
一级缓存管理
对于hibernate 持久化操作,执行的对象都将被放入Session级别的一级缓存中,在 Session调用flush(方法(该方法把所有缓存数据一次性 flush到数据库)或close(方法之前,这些对象将一直缓存在一级缓存中。
在某些特殊的情况下,例如正在处理一个大对象 (它占用的内存开销非常大), 可能需要从一级缓存中去掉这个大对象或集合属性,可以调用Session的evict(Object object)方法,将该对象从一级缓存中剔除出去。
为了判断某个对象是否处于Session缓存中,可以借助于Session提供的contains(Object object)方法,该方法返回一个boolean值, 用于标识某个实例是否处于当前Session的缓存中。
Person p2 = ss.load(Person.class,3);
System.out.println(ss.contains(p2));//true
ss.evict(p2);
System.out.println(ss.contains(p2));//false
Person p3 = ss.load(Person.class,3);//光load 是不会触发sql 执行的,必须使用该对象
System.out.println(p3.getName());
可以看出,在第一次查询出Person 对象,由于剔除缓存,第二次查询相同的对象,需要再进行一次查询。
如果想把所有的对象都从Session缓存中彻底清除,调用Session的clear方法即可。
二级缓存管理
类似地, Hibermate同样提供了方法来操作SessionFactory的二级缓存所缓存的实体。SessionFactory提供了一个getCache方法,该方法的返回值是Cache对象,通过该对象来进行缓存管理。
前提:开启二级缓存
Cache cache = sf.getCache();
Person p2 = ss.load(Person.class,3);
cache.evict(Person.class);//从二级缓存中剔除Person 所有实例
//cache.evict(Person.class,3);//从二级缓存剔除指定实例
//cache.evictAll();
System.out.println(cache.contains(Person.class,3));//查询某个实例是否在二级缓存中
ss.close();
Session ss1 = sf.openSession();
Person p3 = ss1.load(Person.class,3);
System.out.println(p3.getName());
二级缓存统计
开启二级缓存统计配置
<property name="hibernate.generate_statistics">true</property>
<property name="hibernate.cache.use_structured_entries">true</property>
获取二级缓存的数据
Map map = sf.getStatistics().getSecondLevelCacheStatistics("com.example.test.bean.Person").getEntries();//这里必须全限名,否则会报空指针异常
System.out.println(map.toString());
实际上,缓存也不是多牛逼的东西,也就是在某个地方(内存、磁盘)开辟空间,用来存放某些常用的数据。我们这里通过统计,缓存中该Person的二级缓存对象。
查询缓存
一级、二级缓存都是对整个实体进行缓存,它不会缓存普通属性,如果想对普通属性进行缓存,则可以考患使用查询缓存。
对于查询缓存来说,它缓存的key就是查询所用的HQL或SQL语句,需要指出的是: 查询缓存不仅要求所使用的HQL语句、SQL 语句相同,甚至要求所传入的参数也相同,Hibermate 才能直接从查询缓存中取得数据。
开启查询缓存
<property name="hibernate.cache.use_query_cache">true</property>
查询缓存的例子
java.util.List<Person> list = ss.createQuery("select p.name from Person p").setCacheable(true).list();
System.out.println(list.size());//setCacheable,开启查询缓存
java.util.List<Person> list1 = ss.createQuery("select p.name from Person p").setCacheable(true).list();
System.out.println(list1.size());//sql、hql获取的属性和参数都没有变,但是还是需要setCacheable,否则不会从查询缓存中拿
java.util.List<Person> list2 = ss.createQuery("select p1.id from Person p1").list();
System.out.println(list2.size());//更换sql 的属性,无法从查询缓存中获取
Iterator it = ss.createQuery("select p.name from Person p").setCacheable(true).iterate();
//该例子给查询对象设置iterator方法,尽管sql没变,依然无法查询缓存
while(it.hasNext()){
System.out.println(it.next());
}