Hibernate缓存一直比较难掌握,下面就分析和总结原因,相信你就会慢慢清楚了原来Hibernate缓存也是可以轻松掌握的,但前提要求大家必须跟着动手去验证一下,再用心体会,光看是没有用的

目录:

一、hibernate一级缓存(Session 级别的缓存)

二、一级缓存特征及其应用

三、管理一级缓存

四、Hibernate二级缓存(sessionFactory级别缓存)

五、总结

一、hibernate一级缓存(Session 级别的缓存)

  hibernate是一个线程对应一个session,一个线程可以看成一个用户。也就是说session级缓存(一级缓存)只能给一个线程用,别的线程用不了,一级缓存就是和线程绑定了。hibernate一级缓存生命周期很短,和session生命周期一样,一级缓存也称session级的缓存或事务级缓存。如果tb事务提交或回滚了,我们称session就关闭了,生命周期结束了。

  实验1:体验一级缓存:(动手做做)

 //同一个session中,发出两次load方法查询

Employee  emp= (Employee )session.load(Employee .class, 1);

System.out.println( emp.getName());

//不会发出查询语句,load使用缓存

emp = (Employee )session.load(Employee .class, 1);

System.out.println(emp.getName());

  第二次查询第一次相同的数据,第二次load方法就是从缓存里取数据,不会发出sql语句到数据库里查询。

  缓存主要是用于查询 ,hibernate的很多方法都是首先从缓存中取数据如果没有在从数据库中获取,以提升查询效率如:get()/load()、iterate(),而且持久态的对象是会存储在缓存中的。例如:先save保存实体对象,再用load方法查询刚刚save的实体对象,则load方法不会发出sql语句到数据库查询的,而是到缓存里取数据,因为save方法也支持缓存.

二、一级缓存特征及其应用:

  1.Session 级别的缓存,它同session邦定。它的生命周期和session相同。Session消毁,它也同时消毁;

  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方法都会使用一级缓存,

  Get与load的区别? (动手做做)

  1. 对于get方法,hibernate会确认一下该id对应的数据是否存在,首先在session缓存中查找,然后在二级缓存中查找,还没有就查询数据库,数据库中没有就返回null。

  2. load方法加载实体对象的时候,根据映射文件上类级别的lazy属性的配置(默认为true),分情况讨论:

    (1)若为true,则首先在Session缓存中查找,看看该id对应的对象是否存在,不存在则使用延迟加载,返回实体的代理类对象(该代理类为实体类的子类,由CGLIB动态生成)。等到具体使用该对象(除获取OID以外)的时候,再查询二级缓存和数据库,若仍没发现符合条件的记录,则会抛出一个ObjectNotFoundException。

    (2)若为false,就跟get方法查找顺序一样,只是最终若没发现符合条件的记录,则会抛出一个ObjectNotFoundException。

  小结:

1、get方法首先查询session缓存,没有的话查询二级缓存,最后查询数据库;而load方法首先查询session缓存,没有就创建代理,实际使用数据时才查询二级缓存和数据库

2、如果未能发现符合条件的记录,get方法返回null,而load方法会抛出一个ObjectNotFoundException。

3、load使用代理延迟加载数据,而get方法往往返回有实体数据的对象

使用:

1、如果想对一个对象进行增删改查之类,该使用load方法,性能提高,可以使用代理对象,省去了一次和数据库交互的机会,当真正用到该对象的属性时,才跟数据库交互

2、如果你想加载一个对象使用它的属性,该使用get方法

  7.hiberate3 session 存储过程如下:

  例如 object 对象Session.save(object);

  这时候不会把数据放到数据库,会先放到session缓存中去,数据库中没有相应记录,session.flush();才发SQL和HQL语句,数据库中有了相应记录,

  但是数据库用select查不到,这是跟数据库事物级别有关系。

  Session.beginTrransaction()。commit();

  事物提交后可以查询到了。

  Session.flush()语句但是为什么不写呢,因为commit()会默认调用flush();

三、管理一级缓存

  无论何时,当你给save()、update()或 saveOrUpdate()方法传递一个对象时,或使用load()、 get()、list()、iterate() 或scroll()方法获得一个对象时, 该对象都将被加入到Session的内部缓存中。当随后flush()方法被调用时,对象的状态会和数据库取得同步。 如果你不希望此同步操作发生,或者你正处理大量对象、需要对有效管理内存时,你可以调用evict() 方法,从一级缓存中去掉这些对象及其集合。

ScrollableResult cats = sess.createQuery("from Cat as cat").scroll();
while ( cats.next() ) {
Cat cat = (Cat) cats.get(0);
doSomethingWithACat(cat);
sess.evict(cat);
}

Session还提供了一个contains()方法,用来判断某个实例是否处于当前session的缓存中。如若要把所有的对象从session缓存中彻底清除,则需要调用Session.clear()。

四、Hibernate二级缓存(sessionFactory级别缓存)

  二级缓存需要sessionFactory来管理,它是进初级的缓存,所有人都可以使用,它是共享的。Hibernate二级缓存支持对象缓存、集合缓存、查询结果集缓存,对于查询结果集缓存可选。

  二级缓存比较复杂,一般用第三方产品。hibernate提供了一个简单实现,用Hashtable做的,只能作为我们的测试使用,商用还是需要第三方产品。

  几种优秀缓存方案:

  1、Memcached 分布式缓存系统 2、JBOSS CACHE   3 、EhCache   Ehcache 2.1起提供了针对Hibernate的JTA支持。 4、Infinispan 开源数据网格平台

  使用缓存,肯定是长时间不改变的数据,如果经常变化的数据放到缓存里就没有太大意义了。因为经常变化,还是需要经常到数据库里查询,那就没有必要用缓存了。hibernate做了一些优化,和一些第三方的缓存产品做了集成。这里采用EHCache缓存产品。

  和EHCache二级缓存产品集成:EHCache的jar文件在hibernate的lib里,我们还需要设置一系列的缓存使用策略,需要一个配置文件ehcache.xml来配置。

  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.region_prefix">h3test</property>-->
<!-- 高速缓存提供程序 第三方产品-->
<property name="hibernate.cache.region.factory_class">
net.sf.ehcache.hibernate.EhCacheRegionFactory
</property>
<!-- 指定缓存配置文件位置 -->
<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>  

  ehcache配置(ehcache.xml)(动手做做)

<?xml version="1.0" encoding="UTF-8"?>
<ehcache name="h3test"> <!--指定区域名-->
<defaultCache
maxElementsInMemory="100" <!--缓存在内存中的最大数目-->
eternal="false" <!--缓存是否持久-->
timeToIdleSeconds="1200" <!--当缓存条目闲置n秒后销毁-->
timeToLiveSeconds="1200" <!--当缓存条目存活n秒后销毁-->
overflowToDisk="false"> <!--硬盘溢出-->
</defaultCache>
</ehcache>

  实体只读缓存

只读缓存 read only,不须要锁与事务,因为缓存自数据从数据库加载后就不会改变。如果数据是只读的,例如引用数据,那么总是使用“read-only”策略,因为它是最简单、最高效的策略,也是集群安全的策略。是性能第一的策略

<hibernate-mapping>
<class name="com.ljb.entity.Voucher" table="Voucher">
<cache usage="read-only"/>
……
</hibernate-mapping>

  二级缓存测试代码(动手做做)

Session session1 = sf.openSession();
Transaction t1 = session1.beginTransaction();
//确保数据库中有标识符为1的Voucher
Voucher voucher = (Vocher) session1.get(Vocher.class, 1);
//如果修改将报错,只读缓存不允许修改
//voucher.setName("aaa");
t1.commit();
session1.close();
Session session2 = sf.openSession();
Transaction t2 = session2.beginTransaction(); voucher = (Vocher) session2.get(Vocher.class, 1); //在二级缓存中查找结果,不会产出sql语句,不操作数据库
t2.commit();
session2.close();
sf.close();

  只读缓存不允许更新,将报错Can't write to a readonly object。允许新增,( 新增直接添加到二级缓存)

  读写缓存 read write

  对缓存的更新发生在数据库事务完成后。缓存需要支持锁。在一个事务中更新数据库,在这个事务成功完成后更新缓存,并释放锁。锁只是一种特定的缓存值失效表述方式,在它获得新数据库值前阻止其他事务读写缓存。那些事务会转而直接读取数据库

     实体读/写缓存

<hibernate-mapping>
<class name="com.ljb.entity.Voucher" table="Voucher">
<cache usage="read-write"/>
……
</hibernate-mapping>

  二级缓存测试代码

Session session1 = sf.openSession();
Transaction t1 = session1.beginTransaction();
//确保数据库中有标识符为1的Voucher
Voucher voucher = (Vocher) session1.get(Vocher.class, 1);
//如果修改将报错,只读缓存不允许修改
voucher.setName("aaa");
t1.commit();
session1.close();
Session session2 = sf.openSession();
Transaction t2 = session2.beginTransaction();
voucher = (Vocher) session2.get(Vocher.class, 1); //该条目已经被别的事务修改了,此时重新查询一次数据库
t2.commit();
session2.close();
sf.close();

  允许更新,更新后自动同步到缓存。允许新增,新增记录后自动同步到缓存。保证read committed隔离级别及可重复读隔离级别(通过时间戳实现)整个过程加锁,如果当前事务的时间戳早于二级缓存中的条目的时间戳,说明该条目已经被别的事务修改了,此时重新查询一次数据库,否则才使用缓存数据,因此保证可重复读隔离级别

  非严格读写缓存 nonstrict read write

  在一个事务中更新数据库,在这个事务完成前就清除缓存,为了安全起见,无论事务成功与否,在事务完成后再次清除缓存。既不需要支持缓存锁,也不需要支持事务。如果是缓存集群,“清除缓存”调用会让所有副本都失效,这通常被称为“拉(pull)”更新策略。如果你的数据读很多或者很少有并发缓存访问和更新,那么可以使用“nonstrict-read-write”策略。感谢它的轻量级“拉”更新策略,它通常是性能第二好的策略。

  实体非严格读/写缓存

<hibernate-mapping>
<class name="com.ljb.entity.Voucher" table="Voucher">
<cache usage="nonstrict-read-write"/>
……
</hibernate-mapping>

  测试代码 略(我想大家会验证了)

  验证结果

  允许更新,更新后缓存失效,需再查询一次。 允许新增,新增记录自动加到二级缓存中。整个过程不加锁,不保证。

  事务缓存 transactional (一定要在JTA环境中)

  对缓存和数据库的更新被包装在同一个JTA事务中,这样缓存与数据库总是保持同步的。数据库和缓存都必须支持JTA。除非你真的想将缓存更新和数据库更新放在一个JTA事务里,否则不要使用“transactional”策略,因为JTA需要漫长的两阶段提交处理,这导致它基本是性能最差的策略。

  需要特定缓存的支持和JTA事务支持,此处不演示。 

  集合缓存

  演示读/写缓存示例,和之前实体缓存测试差不多,其他自测

<hibernate-mapping>
  <class name="cn.javass.h3test.model.UserModel" table="TBL_USER">
    <cache usage="read-write" />
    <set name="vouchers" cascade="all" inverse="true" lazy="false">
      <cache usage="read-write"/>
      <key column="fk_user_id"/>
      <one-to-many class="cn.ljb.entity.Voucher"/>
    </set>
  </class>
</hibernate-mapping>
SessionFactory sf =
new Configuration().configure().buildSessionFactory();
Session session1 = sf.openSession();
Transaction t1 = session1.beginTransaction();
//确保数据库中有标识符为1的UserModel
UserModel user = (UserModel) session1.get(UserModel.class, 1);
user.getVouchers();
t1.commit();
session1.close(); Session session2 = sf.openSession();
Transaction t2 = session2.beginTransaction();
user = (UserModel) session2.get(UserModel.class, 1);
user.getVouchers();
t2.commit();
session2.close();
sf.close();

  测试结论:   

  和实体并发策略有相同含义; 但集合缓存只缓存集合元素的标识符,在二级缓存中只存放相应实体的标识符,然后再通过标识符去二级缓存查找相应的实体最后组合为集合返回。

  查询缓存 (动手做做)

  1、保证全局配置中有开启了查询缓存。

  2、修改FarmModel.hbm.xml,添加如下红色部分配置,表示实体缓存并读/写

<hibernate-mapping>
<class name="com.lijb.entity.Voucher" table="voucher">
<cache usage="read-write"/>
……
</hibernate-mapping>

  3、测试代码

SessionFactory sf =   .new Configuration().configure().buildSessionFactory();
Session session1 = sf.openSession();
Transaction t1 = session1.beginTransaction();
Query query = session1.createQuery("fromVoucher");
//即使全局打开了查询缓存,此处也是必须的
query.setCacheable(true);
List<Voucher> voucherList = query.list();
t1.commit();
session1.close();
Session session2 = sf.openSession();
Transaction t2 = session2.beginTransaction();
query = session2.createQuery("from Voucher");
//即使全局打开了查询缓存,此处也是必须的
query.setCacheable(true);
voucherList = query.list();
t2.commit();
session2.close();
sf.close();

  结论:

  和实体并发策略有相同含义; 和集合缓存类似,只缓存集合元素的标识符,在二级缓存中只存放相应实体的标识符,然后再通过标识符 去二级缓存查找相应的实体最后组合为集合返回。

什么时候需要查询缓存?

大多数时候无法从结果集高速缓存获益。必须知道:每隔多久重复执行同一查询。对于那些查询非常多但插入、删除、更新非常少的应用程序来说,查询缓存可提升性能。但写入多查询少的没有用,总失效。

  管理二级缓存

  对于二级缓存来说,在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 //CacheMode参数用于控制具体的Session如何与二级缓存进行交互。
//CacheMode.NORMAL - 从二级缓存中读、写数据。
//CacheMode.GET - 从二级缓存中读取数据,仅在数据更新时对二级缓存写数据。
//CacheMode.PUT - 仅向二级缓存写数据,但不从二级缓存中读数据。
//CacheMode.REFRESH - 仅向二级缓存写数据,但不从二级缓存中读数据。通过 hibernate.cache.use_minimal_puts的设置,强制二级缓存从数据库中读取数据,刷新缓存内容

  监控二级缓存

  如若需要查看二级缓存或查询缓存区域的内容,你可以使用统计(Statistics) API。通过sessionFactory.getStatistics();获取Hibernate统计信息。此时,你必须手工打开统计选项。

 hibernate.generate_statistics true
hibernate.cache.use_structured_entries true

 五、总结

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

  如果受不了hibernate的诸多限制,那么还是自己在应用程序的层面上做缓存吧。

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

作者:杰瑞教育
出处:http://www.cnblogs.com/jerehedu/ 
本文版权归烟台杰瑞教育科技有限公司和博客园共有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文连接,否则保留追究法律责任的权利。
 

Hibernate缓存应用的积累与总结的更多相关文章

  1. 【转】hibernate缓存:一级缓存和二级缓存

    什么是缓存? 缓存是介于物理数据源与应用程序之间,是对数据库中的数据复制一份临时放在内存中的容器,其作用是为了减少应用程序对物理数据源访问的次数,从而提高了应用程序的运行性能.Hibernate在进行 ...

  2. hibernate缓存机制(转载)

    缓存是介于应用程序和物理数据源之间,其作用是为了降低应用程序对物理数据源访问的频次,从而提高了应用的运行性能.缓存内的数据是对物理数据源中的数据的复制,应用程序在运行时从缓存读写数据,在特定的时刻或事 ...

  3. Hibernate 缓存机制浅析

    1. 为什么要用 Hibernate 缓存? Hibernate是一个持久层框架,经常访问物理数据库. 为了降低应用程序对物理数据源访问的频次,从而提高应用程序的运行性能. 缓存内的数据是对物理数据源 ...

  4. hibernate缓存机制(转)

    原文出处:http://www.cnblogs.com/wean/archive/2012/05/16/2502724.html 一.why(为什么要用Hibernate缓存?) Hibernate是 ...

  5. Hibernate缓存(转)

    来自:http://www.cnblogs.com/wean/archive/2012/05/16/2502724.html 一.why(为什么要用Hibernate缓存?) Hibernate是一个 ...

  6. 初识Hibernate 缓存

    生活就像一杯咖啡,让你我慢慢的品尝,品尝它的苦涩和甘甜...... 一.什么是Hibernate缓存. 解析:白话来说就是缓存数据的容器 官方标准点缓存:是计算机领域的概念,它介于应用程序和永久性数据 ...

  7. Hibernate缓存原理与策略

    Hibernate缓存原理: 对于Hibernate这类ORM而言,缓存显的尤为重要,它是持久层性能提升的关键.简单来讲Hibernate就是对JDBC进行封装,以实现内部状态的管理,OR关系的映射等 ...

  8. [原创]java WEB学习笔记93:Hibernate学习之路---Hibernate 缓存介绍,缓存级别,使用二级缓存的情况,二级缓存的架构集合缓存,二级缓存的并发策略,实现步骤,集合缓存,查询缓存,时间戳缓存

    本博客的目的:①总结自己的学习过程,相当于学习笔记 ②将自己的经验分享给大家,相互学习,互相交流,不可商用 内容难免出现问题,欢迎指正,交流,探讨,可以留言,也可以通过以下方式联系. 本人互联网技术爱 ...

  9. Hibernate缓存原理与策略 Hibernate缓存原理:

    Hibernate缓存原理: 对于Hibernate这类ORM而言,缓存显的尤为重要,它是持久层性能提升的关键.简单来讲Hibernate就是对JDBC进行封装,以实现内部状态的管理,OR关系的映射等 ...

随机推荐

  1. BZOJ.2125.最短路(仙人掌 最短路Dijkstra)

    题目链接 多次询问求仙人掌上两点间的最短路径. 如果是在树上,那么求LCA就可以了. 先做着,看看能不能把它弄成树. 把仙人掌看作一个图(实际上就是),求一遍根节点到每个点的最短路dis[i]. 对于 ...

  2. [USACO06JAN]Redundant Paths

    OJ题号:洛谷2860.POJ3177 题目大意: 给定一个无向图,试添加最少的边使得原图中没有桥. 思路: Tarjan缩点,然后统计度为$1$的连通分量的个数(找出原图中所有的桥). 考虑给它们每 ...

  3. 深入理解指针—>指针函数与函数指针的区别

    一. 在学习过程中发现这"指针函数"与"函数指针"容易搞错,所以今天,我自己想一次把它搞清楚,找了一些资料,首先它们之间的定义: 1.指针函数是指带指针的函数, ...

  4. hdu 1073 字符串处理

    题意:给一系列的输出和标准答案,比较二者是AC,PE或WA 字符串处理还是比较薄弱,目前没什么时间搞字符串专题,所以遇到一题就努力搞懂 #include<cstdio> #include& ...

  5. zoj 3460 二分+二分图匹配

    不错的思想 /* 大致题意: 用n个导弹发射塔攻击m个目标.每个发射架在某个时刻只能为 一颗导弹服务,发射一颗导弹需要准备t1的时间,一颗导弹从发 射到击中目标的时间与目标到发射架的距离有关.每颗导弹 ...

  6. Markdown基础用法

    1. 标题 文字前加#,共6级标题,# 一级标题,## 二级标题,...,###### 六级标题 2. 列表 文字前加-或* 即可变无序列表,文字前加 数字. 即可变有序列表 3. 引用 在引用文本前 ...

  7. Linux学习笔记04—IP配置

    一.自动获取IP只有一种情况可以自动获取IP地址,那就是你的Linux所在的网络环境中有DHCP服务.只要你的真机可以自动获取IP,那么安装在虚拟机的Linux同样也可以自动获取IP. 方法很简单,只 ...

  8. Wingdings 2 符号编码对照表

     http://blog.csdn.net/linux7985/article/details/5030754 符号  编码 HTML 代码  符号  编码 HTML 代码 ! ! " &q ...

  9. WinForm MDIParent如何防止重复打开

    DI,全称是多文档界面(Multiple Document Interface),主要应用于基于图形用户界面的系统中.其目的是同时打开和显示多个文档,便于参考和编辑资料. 下面是一个WinForm M ...

  10. java之 ------ 文件拷贝

    import java.io.FileInputStream; import java.io.FileNotFoundException; import java.io.FileOutputStrea ...