Hibernate缓存详解

Hibernate缓存介绍

<strong>1、缓存介绍
</strong>Hibernate中提供了两级Cache,第一级别的缓存是Session级别的缓存,它是属于事务范围的缓存。这一级别的缓存由hibernate管理的,一般情况下无需进行干预;第二级别的缓存是SessionFactory级别的缓存,它是属于进程范围或群集范围的缓存。这一级别的缓存可以进行配置和更改,并且可以动态加载和卸载。 Hibernate还为查询结果提供了一个查询缓存,它依赖于第二级缓存。

<ul class="con" style="padding: 10px 24px 0px; margin: 0px; list-style: none; color: rgb(44, 44, 44); font-family: 宋体, 'Arial Narrow', arial, serif; font-size: 14px; border: 0px; line-height: 26px;"><div style="padding: 0px; margin: 10px 0px; text-indent: 28px; font-size: 14px;">Hibernate中提供了两级Cache,第一级别的缓存是<span style="padding: 0px; margin: 0px;">Session级别的缓存,它是属于事务范围的缓存</span>。这一级别的缓存<span style="padding: 0px; margin: 0px;">由hibernate管理的</span>,<span style="padding: 0px; margin: 0px;">一般情况下无需进行干预</span>;第二级别的缓存是SessionFactory级别的缓存,它是属于<span style="padding: 0px; margin: 0px;">进程范围或群集范围的缓存</span>。这一级别的缓存可以进行配置和更改,并且可以动态加载和卸载。 Hibernate还为查询结果提供了一个<span style="padding: 0px; margin: 0px;">查询缓存,它依赖于第二级缓存</span>。</div><div style="padding: 0px; margin: 10px 0px; text-indent: 28px;"><ul class="con" style="padding: 10px 24px 0px; margin: 0px; list-style: none; border: 0px;"><div style="padding: 0px; margin: 10px 0px; font-size: 14px;"><span style="padding: 0px; margin: 0px;">一级缓存的管理:</span></div><div style="padding: 0px; margin: 10px 0px; font-size: 14px;">当应用程序调用Session的save()、update()、savaeOrUpdate()、get()或load(),以及调用查询接口的 list()、iterate()或filter()方法时,如果在Session缓存中还不存在相应的对象,Hibernate就会把该对象加入到第一级缓存中。当清理缓存时,Hibernate会根据缓存中对象的状态变化来同步更新数据库。 Session为应用程序提供了两个管理缓存的方法: evict(Object obj):从缓存中清除参数指定的持久化对象。 clear():清空缓存中所有持久化对象。</div><div style="padding: 0px; margin: 10px 0px;"><span style="padding: 0px; margin: 0px;"><span style="padding: 0px; margin: 0px;font-size:18px;"> Hibernate二级缓存的管理:</span></span></div><div style="padding: 0px; margin: 10px 0px; font-size: 14px;"><span style="padding: 0px; margin: 0px;">1. Hibernate二级缓存策略的一般过程如下</span>:<br style="padding: 0px; margin: 0px;" />1) 条件查询的时候,总是发出一条select * from table_name where …. (选择所有字段)这样的SQL语句查询数据库,一次获得所有的数据对象。<br style="padding: 0px; margin: 0px;" />2) 把获得的所有数据对象根据ID放入到第二级缓存中。<br style="padding: 0px; margin: 0px;" />3) 当Hibernate根据ID访问数据对象的时候,首先从Session一级缓存中查;查不到,如果配置了二级缓存,那么从二级缓存中查;查不到,再查询数据库,把结果按照ID放入到缓存。<br style="padding: 0px; margin: 0px;" />4) 删除、更新、增加数据的时候,同时更新缓存。<br style="padding: 0px; margin: 0px;" />Hibernate二级缓存策略,是针对于ID查询的缓存策略,对于条件查询则毫无作用。为此,Hibernate提供了针对条件查询的Query Cache。</div><div style="padding: 0px; margin: 10px 0px; font-size: 14px;"><span style="padding: 0px; margin: 0px;">2. 什么样的数据适合存放到第二级缓存中?</span> <br style="padding: 0px; margin: 0px;" />1) 很少被修改的数据 <br style="padding: 0px; margin: 0px;" />2) 不是很重要的数据,允许出现偶尔并发的数据 <br style="padding: 0px; margin: 0px;" />3) 不会被并发访问的数据 <br style="padding: 0px; margin: 0px;" />4) 参考数据,指的是供应用参考的常量数据,它的实例数目有限,它的实例会被许多其他类的实例引用,实例极少或者从来不会被修改。</div><div style="padding: 0px; margin: 10px 0px; font-size: 14px;"><span style="padding: 0px; margin: 0px;">3. 不适合存放到第二级缓存的数据?</span> <br style="padding: 0px; margin: 0px;" />1) 经常被修改的数据 <br style="padding: 0px; margin: 0px;" />2) 财务数据,绝对不允许出现并发 <br style="padding: 0px; margin: 0px;" />3) 与其他应用共享的数据。</div><div style="padding: 0px; margin: 10px 0px; font-size: 14px;"><span style="padding: 0px; margin: 0px;">4. 常用的缓存插件</span> </div><div style="padding: 0px; margin: 10px 0px; font-size: 14px;">Hibernater二级缓存是一个插件,下面是几种常用的缓存插件:<br style="padding: 0px; margin: 0px;" />◆EhCache:可作为进程范围的缓存,存放数据的物理介质可以是内存或硬盘,对Hibernate的查询缓存提供了支持。<br style="padding: 0px; margin: 0px;" />◆OSCache:可作为进程范围的缓存,存放数据的物理介质可以是内存或硬盘,提供了丰富的缓存数据过期策略,对Hibernate的查询缓存提供了支持。<br style="padding: 0px; margin: 0px;" />◆SwarmCache:可作为群集范围内的缓存,但不支持Hibernate的查询缓存。<br style="padding: 0px; margin: 0px;" />◆JBossCache:可作为群集范围内的缓存,支持事务型并发访问策略,对Hibernate的查询缓存提供了支持。</div><div style="padding: 0px; margin: 10px 0px; font-size: 14px;"><span style="padding: 0px; margin: 0px;">5. 配置Hibernate二级缓存的主要步骤</span>:<br style="padding: 0px; margin: 0px;" />1) 选择需要使用二级缓存的持久化类,设置它的命名缓存的并发访问策略。这是最值得认真考虑的步骤。<br style="padding: 0px; margin: 0px;" />2) 选择合适的缓存插件,然后编辑该插件的配置文件。</div></ul></div></ul>


一. <a target=_blank target="_blank" class="inner-link decor-none" href="http://zhidao.baidu.com/search?word=%E4%B8%80%E7%BA%A7%E7%BC%93%E5%AD%98&fr=qb_search_exp&ie=utf8" rel="nofollow" style="color: rgb(51, 102, 153); text-decoration: none;">一级缓存</a>和二级缓存的比较:



第<a target=_blank target="_blank" class="inner-link decor-none" href="http://zhidao.baidu.com/search?word=%E4%B8%80%E7%BA%A7%E7%BC%93%E5%AD%98&fr=qb_search_exp&ie=utf8" rel="nofollow" style="color: rgb(51, 102, 153); text-decoration: none;">一级缓存</a>和第二级缓是存放数据的形式是以相互关联的<a target=_blank target="_blank" class="inner-link decor-none" href="http://zhidao.baidu.com/search?word=%E6%8C%81%E4%B9%85%E5%8C%96&fr=qb_search_exp&ie=utf8" rel="nofollow" style="color: rgb(51, 102, 153); text-decoration: none;">持久化</a>对象,对象的散装数据等方式存储的。每个事务都有单独的第一级缓存,缓存被同一个进程或集群范围内的所有事务共享,由于每个事务都拥有单独的第一级缓存,不会出现并发问题,无需提供并发访问策略,由于多个事务会同时访问第二级缓存中相同数据,因此必须提供适当的并发访问策略,来保证特定的<a target=_blank target="_blank" class="inner-link decor-none" href="http://zhidao.baidu.com/search?word=%E4%BA%8B%E5%8A%A1%E9%9A%94%E7%A6%BB%E7%BA%A7%E5%88%AB&fr=qb_search_exp&ie=utf8" rel="nofollow" style="color: rgb(51, 102, 153); text-decoration: none;">事务隔离级别</a>,没有提供数据过期策略。处于一级缓存中的对象永远不会过期,除非应用程序显式清空缓存或者清除特定的对象,所以必须提供数据过期策略,数据过期策略如:基于内存的缓存中的对象的最大数目,允许对象处于缓存中的最长时间,以及允许对象处于缓存中的最长空闲时间,物理<a target=_blank target="_blank" class="inner-link decor-none" href="http://zhidao.baidu.com/search?word=%E5%AD%98%E5%82%A8%E4%BB%8B%E8%B4%A8&fr=qb_search_exp&ie=utf8" rel="nofollow" style="color: rgb(51, 102, 153); text-decoration: none;">存储介质</a>内存,内存和硬盘。对象的散装数据首先存放在基于内存的缓存中,当内存中对象的数目达到数据过期策略中指定上限时,就会把其余的对象写入基于硬盘的缓存中。缓存的软件实现在Hibernate的Session的实现中包含了缓存的实现由第三方提供,Hibernate仅提供了缓存适配器(CacheProvider)。用于把特定的缓存插件集成到Hibernate中。启用缓存的方式只要应用程序通过Session接口来执行保存、更新、删除、加载和查询数据库数据的操作,Hibernate就会启用第一级缓存,把数据库中的数据以对象的形式拷贝到缓存中,对于批量更新和批量删除操作,如果不希望启用第一级缓存,可以绕过Hibernate API,直接通过JDBC API来执行指操作。用户可以在单个类或类的单个集合的<a target=_blank target="_blank" class="inner-link decor-none" href="http://zhidao.baidu.com/search?word=%E7%B2%92%E5%BA%A6&fr=qb_search_exp&ie=utf8" rel="nofollow" style="color: rgb(51, 102, 153); text-decoration: none;">粒度</a>上配置第二级缓存。如果类的实例被经常读但很少被修改,就可以考虑使用第二级缓存。只有为某个类或集合配置了第二级缓存,Hibernate在运行时才会把它的实例加入到第二级缓存中。用户管理缓存的方式:第一级缓存的物理介质为内存,由于内存容量有限,必须通过恰当的检索策略和检索方式来限制加载对象的数目。Session的 evit()方法可以显式清空缓存中特定对象,但这种方法不值得推荐。第二级缓存的物理介质可以是内存和硬盘,因此第二级缓存可以存放大量的数据,数据过期策略的maxElementsInMemory属性值可以控制内存中的对象数目。管理第二级缓存主要包括两个方面:选择需要使用第二级缓存的持久类,设置合适的并发访问策略:选择缓存适配器,设置合适的数据过期策略。



二. 一级缓存的管理:



当应用程序调用Session的save()、update()、savaeOrUpdate()、get()或load(),以及调用查询接口的 list()、iterate()或filter()方法时,如果在Session缓存中还不存在相应的对象,Hibernate就会把该对象加入到第一级缓存中。当清理缓存时,Hibernate会根据缓存中对象的状态变化来同步更新数据库。 Session为应用程序提供了两个管理缓存的方法: evict(Object obj):从缓存中清除参数指定的<a target=_blank target="_blank" class="inner-link decor-none" href="http://zhidao.baidu.com/search?word=%E6%8C%81%E4%B9%85%E5%8C%96&fr=qb_search_exp&ie=utf8" rel="nofollow" style="color: rgb(51, 102, 153); text-decoration: none;">持久化</a>对象。 clear():清空缓存中所有<a target=_blank target="_blank" class="inner-link decor-none" href="http://zhidao.baidu.com/search?word=%E6%8C%81%E4%B9%85%E5%8C%96&fr=qb_search_exp&ie=utf8" rel="nofollow" style="color: rgb(51, 102, 153); text-decoration: none;">持久化</a>对象。



三. Hibernate二级缓存的管理:



1. Hibernate二级缓存策略的一般过程如下:

1) 条件查询的时候,总是发出一条select * from table_name where …. (选择所有字段)这样的SQL语句查询数据库,一次获得所有的<a target=_blank target="_blank" class="inner-link decor-none" href="http://zhidao.baidu.com/search?word=%E6%95%B0%E6%8D%AE%E5%AF%B9%E8%B1%A1&fr=qb_search_exp&ie=utf8" rel="nofollow" style="color: rgb(51, 102, 153); text-decoration: none;">数据对象</a>。

2) 把获得的所有<a target=_blank target="_blank" class="inner-link decor-none" href="http://zhidao.baidu.com/search?word=%E6%95%B0%E6%8D%AE%E5%AF%B9%E8%B1%A1&fr=qb_search_exp&ie=utf8" rel="nofollow" style="color: rgb(51, 102, 153); text-decoration: none;">数据对象</a>根据ID放入到第二级缓存中。

3) 当Hibernate根据ID访问<a target=_blank target="_blank" class="inner-link decor-none" href="http://zhidao.baidu.com/search?word=%E6%95%B0%E6%8D%AE%E5%AF%B9%E8%B1%A1&fr=qb_search_exp&ie=utf8" rel="nofollow" style="color: rgb(51, 102, 153); text-decoration: none;">数据对象</a>的时候,首先从Session一级缓存中查;查不到,如果配置了二级缓存,那么从二级缓存中查;查不到,再查询数据库,把结果按照ID放入到缓存。

4) 删除、更新、增加数据的时候,同时更新缓存。

Hibernate二级缓存策略,是针对于ID查询的缓存策略,对于条件查询则毫无作用。为此,Hibernate提供了针对条件查询的Query Cache。



2. 什么样的数据适合存放到第二级缓存中? 

1) 很少被修改的数据 

2) 不是很重要的数据,允许出现偶尔并发的数据 

3) 不会被并发访问的数据 

4) 参考数据,指的是供应用参考的<a target=_blank target="_blank" class="inner-link decor-none" href="http://zhidao.baidu.com/search?word=%E5%B8%B8%E9%87%8F&fr=qb_search_exp&ie=utf8" rel="nofollow" style="color: rgb(51, 102, 153); text-decoration: none;">常量</a>数据,它的实例数目有限,它的实例会被许多其他类的实例引用,实例极少或者从来不会被修改。



3. 不适合存放到第二级缓存的数据? 

1) 经常被修改的数据 

2) 财务数据,绝对不允许出现并发 

3) 与其他应用共享的数据。



4. 常用的缓存插件 Hibernater二级缓存是一个插件,下面是几种常用的缓存插件:

◆EhCache:可作为进程范围的缓存,存放数据的物理介质可以是内存或硬盘,对Hibernate的查询缓存提供了支持。

◆OSCache:可作为进程范围的缓存,存放数据的物理介质可以是内存或硬盘,提供了丰富的缓存数据过期策略,对Hibernate的查询缓存提供了支持。

◆SwarmCache:可作为群集范围内的缓存,但不支持Hibernate的查询缓存。

◆JBossCache:可作为群集范围内的缓存,支持事务型并发访问策略,对Hibernate的查询缓存提供了支持。



5. 配置Hibernate二级缓存的主要步骤:

1) 选择需要使用二级缓存的持久化类,设置它的命名缓存的并发访问策略。这是最值得认真考虑的步骤。

2) 选择合适的缓存插件,然后编辑该插件的配置文件。

二、缓存应用的场景:
1、对于新闻,论坛,博客等互联网应用适合在前端做缓存,比如url做为key来缓存整个页面的内容。一条新闻a被如前所述的缓存起来了,在网站并发访问量大时,会大大提高网站的吞吐能力。好了现再须要编辑这条新闻,如何同步更新缓存呢?须要立即同步更新缓存吗?不须要,互联网应用允许用户在5-10分钟之后再看到更新之后的新闻,这是可以接受的。没有较高的时效性,允许延迟。这样我们设定缓存对象的最大生命时间为10分钟,一个被缓存的对象存活时间超过10分钟就被清理,当新的访问请求到来时,再从数据库中加载他,再次被缓存10分钟。Hibernate二级缓存不适合这个场景,这个场景对缓存的锁、事务没有要求,对高并发,高数据量有要求。
    总结一下:被缓存的对象没有较高的时效性,允许对象更新后延迟(10分钟内)展示,允许(10分钟内)的数据不一致。

2、对于企业应用,要保证数据的一致性是第一位的,即使数据被修改,最终用户看到的数据与数据库中的数据要时时一致。适合在应用程序持久层上做缓存,Hibernate二级缓存就适合这个场景。
    总结一下:这个场景对缓存的锁,事务要求是第一的。对高并发高数据量的要求是第二的,通过锁保证数据的一致性。Hibernate对数据库是独占的,修改给数据库的操作都通过他.

 

三、频繁更新的数据要不要被缓存:
网上有人说频繁更新的数据不适合使用缓存。这样说是不全面的,因为他少说了前提条件。

 

数据一致性:本文章的数据一致性是指缓存中的数据与数据库中的数据就保持一致,严格的一致。决对没有脏数据。

 

当你须要数据一致性,而又不能保存数据一致性时,频繁更新的数据就不可以被缓存。不缓存直接操作数据库,就一致了,没有不一致的问题了。

      你的Hibernate对数据库不是独占的,有其它程序来修改数据库中的记录,这时Hibernate是不知道的,也会发生数据不一致.

      当使用url或sql语句做为KEY来缓存时,一句select 语句查出n个对象,无法在缓存中精准的找到被修改的某一个对象,当修改一个对象时就不能在缓存中精准的找到他,为了保证数据一致性,就要清除缓存中所有的同类对象,使下次查询时无法命中缓存。而不清除,很有可能发生数据不一致。这相当于Hibernate的查询缓存。这时个不要使用缓存。

      
当你须要数据一致性,而又能保存数据一致性时,频繁更新的数据是可以被缓存的。这里我们使用缓存的“锁”机制来保证,你使用的Hibernate第三方缓存要支持“锁”,就是read-write模式。这是重点啊。第三方缓存锁的实现方法不同性能也不同,锁是缓存性能下降第一原因,一定要使用高性能的锁,这就要了解多款Hibernate第三方缓存.

      因为经常被更新修改的对象,一定也更加经常的被查询,需要缓存他来提高应用程序的性能。如果执行修改sql时,同时锁住缓存中的这个对象并更新他,之后解锁是最理想的。Hibernate的二级缓存策略,是针对于ID查询的缓存策略,所以可以做到精准的找到缓存中的目标,加之“锁”的帮助,可实现数据一致性。

但限制也有比如使用HQL时就不能精准的找到缓存中的目标,只好清除缓存中所有的同类对象来保证数据的一致性(缓存中没数据就没一致性问题了)。


四、Hibernate二级缓存中的对象什么时候会被清理:
在read-write模式下: 
我们有一个Order对象,是一个实体对象,对应数据库中order表中的一条记录,经过查询已有n个Order对象被放入二级缓存中。现在我们要修改order表中任意任x条记录,执行以下HQL:

template.bulkUpdate("update Order set owner = ? where id in (?,?,?)");

 

 

 这时Hibernate会直接将二级缓存中的n个Order对象清除掉。 天啊,居然不是你想像的修改谁就同步更新二级缓存中的谁,而是清除了二级缓存中全部的Order类型的对象。为什么?这一切是为了保证“数据一致性”。你执行了HQL修改了order表中的x条记录,这x条是哪几条?如果sql是子查询:update Order set owner =? where id in(select id from *** ),谁知道你修改了order表中的哪几条记录,你自己都不知道,Hibernate更不知道了。所以为了保证二级缓存中的数据与order表中的数据一致,只能清除了二级缓存中全部的Order类型的对象。二级缓存频繁的载入与清除,这样缓存命中率就会下降。

试验:看到这里后,我很担心,这样命中率下降后,没有起到缓存的作用。今天特意做一个实验,看看被缓存的对象在被修改后会怎样。

环境:Hibernate3.4 , OsCache(usage="read-write"),JUnit 缓存状态:Hibernate二级缓存中已缓存了5个Order对象。 测试结果: 1 使用saveOrUpdate()方法更新一个实体对象a时,新的a对象被put到二级缓存中,同时写入数据库,二级缓存中的其它4个Order对象没有变化。 这时再查询这5个Order对象中的任意,是可以命中二级缓存的。 2 使用HQL "update Order set name = ? where id =?" 方法更新一个实体对象a时,所有Order对象被从二级缓存中清除,同时a对象被写入数据库。 这时再查询这5个Order对象中的任意,无法命中二级缓存,会去查数据库,查出来的对象又put进二级缓存。

Hibernate的二级缓存策略,是以ID做为key 的缓存策略,在删除、更新、增加数据的时候,同时更新缓存。 对于条件查询,条件修改,条件删除(一般是执行HQL)则起不到缓存的作用。条件修改,条件删除时(一般是执行HQL)会清空所有在缓存中的同类对象。 为此,Hibernate提供了针对条件查询的Query Cache,其实它并不好用。

关于是否命中,是使用Statistics类监测的(通过SessionFactory的getStatistics()方法得到)。

总结一下:如果你打算开启hibernate的二级缓存,在修改与删除时,就要使用session.update(),session.delete()方法按ID一条一条的操作,这样对二级缓存是最优的。    但循环中使用sesion.update(),session.delete()方法,会产生多条sql语句,原本使用一条HQL完成的工作,现在要执行多条,你担心Hibernate与数据库服务器的网络通信次数吗?其实这多条sql是使用JDBC的批处理一次发送到数据库服务器的,所以你不用担心。现在到了数据库服务器端,我们以oracle为例,oracle要执行多条sql,就要进行多次的“分析sql语句的正确性,并解析成oracle的原子操作,并制定执行计划”,你担心这“多次”分析会给oracle带来性能的影响吗?不用担心,请使用oracle的绑定参数,就是Hibernate中的?代替参数。

五、Hibernate二级缓存的并发策略你了解吗:

1 只读缓存 read only 不须要锁与事务,因为缓存自数据从数据库加载后就不会改变。

    如果数据是只读的,例如引用数据,那么总是使用“read-only”策略,因为它是最简单、最高效的策略,也是集群安全的策略。是性能第一的策略 。

2 读写缓存 read write 对缓存的更新发生在数据库事务完成后。缓存需要支持锁。在一个事务中更新数据库,在这个事务成功完成后更新缓存,并释放锁。 锁只是一种特定的缓存值失效表述方式,在它获得新数据库值前阻止其他事务读写缓存。那些事务会转而直接读取数据库。缓存必须支持锁,事务支持则不是必须的。如果缓存是一个集群,“更新缓存”的调用会将新值推送给所有副本,这通常被称为“推(push)”更新策略。

    如果你的数据是又读又写的,那么使用“read-write”策略。这通常是性能第三的策略,因为它要求有缓存锁,缓存集群中使用重量级的“推”更新策略。

3 非严格读写缓存 nonstrict read write 在一个事务中更新数据库,在这个事务完成前就清除缓存,为了安全起见,无论事务成功与否,在事务完成后再次清除缓存。既不需要支持缓存锁,也不需要支持事务。如果是缓存集群,“清除缓存”调用会让所有副本都失效,这通常被称为“拉(pull)”更新策略。

    如果你的数据读很多或者很少有并发缓存访问和更新,那么可以使用“nonstrict-read-write”策略。感谢它的轻量级“拉”更新策略,它通常是性能第二好的策略。

4 事务缓存 transactional (一定要在JTA环境中)对缓存和数据库的更新被包装在同一个JTA事务中,这样缓存与数据库总是保持同步的。数据库和缓存都必须支持JTA。

    除非你真的想将缓存更新和数据库更新放在一个JTA事务里,否则不要使用“transactional”策略,因为JTA需要漫长的两阶段提交处理,这导致它基本是性能最差的策略。

六、缓存锁的性能也要了解,知道加了锁后性能会下降:     为了保证数据的安全性,不发生脏数据,各个缓存通常使用锁来保证 在本地方式运行时,缓存最大的开销就是使用锁来在保证共享数据完整性。 在集群环境中,RPC调用,锁,是性能上大开销。

 

下面以JBoss Cache为例说一说锁:JBoss Cache1.* 和 2.* 时代,提供乐观锁,悲观锁,但是性能不高。 JBoss Cache3.0 MVCC锁方案性能很高。

悲观锁:这些锁的隔离级别和数据库实施的隔离级别相同,这种方案简单而且健壮,允许多用户同时读取数据。读操作阻塞写操作,悲观锁的读写是互斥的,无法同时进行的,写的性能不好。

乐观锁:这个方式则牵涉到数据版本,可以获得高度并发性。那些请求读取数据的用户不会因为并发数据库写入操作而受到阻塞。而且,乐观锁定方式还可以避免悲观锁定中有可能发生的死锁。但它仍然有两个主要的缺点:一是性能问题。因为不断的将结点的状态拷贝到每个并发线程所造成的内存和 CPU 开销是不容忽略的。二是尽管并发时允许了写操作,但是一旦发现数据的版本不对,事务提交时不可避免的还是会失败。也就是说,此时写事务虽然可以不受限制的进行大量处理和写操作,但是这样在事务结束的时候容易出现提交失败。

多版本并发控制(MVCC):在数据访问速度上较之前者也胜出百倍。MVCC 提供了非阻塞 (non-blocking) 读操作 ( 它并不会去阻塞 wirter threads) ,在避免死锁的同时也提供了更高级的并发机制。更棒的是,我们的 MVCC 实现甚至可以对 reader threads 完全不采用任何锁 ( 对于像缓存这样频繁读取的系统来说,意义太大了 ) ,

 

七、批量处理时请不要使用二级缓存当你执行大量的 添加与修改时,并且这个实体对象被配置为启用二级缓存,你考虑过二级缓存会怎么样吗?请看下面代码:

Session session = sessionFactory.openSession(); Transaction tx = session.beginTransaction(); for ( int i=0; i<100000; i++ ) { Customer customer = new Customer(.....); //如果你的 hibernate.cache.use_second_level_cache 是 true, 请在会话级别上关闭他 //向(任何一级)缓存中加载大量数据通常也意味着它们很快会被清除出去,这会增加GC开销。 session.setCacheMode(CacheMode.IGNORE); session.save(customer); if ( i % 50 == 0 ) { //将本批插入的对象立即写入数据库并释放内存 session.flush(); session.clear(); } } tx.commit(); session.close();

 

 批处理通常不需要数据缓存,否则你会将内存耗尽并大量增加GC开销。如果内存有限,那这种情况会很明显。


八、了解几种优秀缓存方案:
1、Memcached 
分布式缓存系统,memcached 要求set的对象必须是可序列化对象,jboss cache等java obect cache是没有这个说法的,这是本质的不同的,但是他可以在网络上用,所以必须序列化也可理解。 
独立服务器+java 客户端。
Memcached java 客户端有:
memcache-client-forjava,
XMemcached,
spymemcached,
memcache-client-forjava


Hibernate缓存管理

1.    Cache简介:
缓存(Cache )是计算机领域非常通用的概念。它介于应用程序和永久性数据存储源(如硬盘上的文件或者数据库)之间,其作用是降低应用程序直接读写永久性数据存储源的频率,从而提高应用的运行性能。缓存中的数据是数据存储源中数据的拷贝,应用程序在运行时直接读写缓存中的数据,只在某些特定时刻按照缓存中的数据来同步更新数据存储源。
缓存的物理介质通常是内存,而永久性数据存储源的物理介质通常是硬盘或磁盘,应用程序读写内在的速度显然比读写硬盘的速度快,如果缓存中存放的数据量非常大,也会用硬盘作为缓存的物理介质。
缓存的实现不仅需要作为物理介质的硬件,同时还需要用于管理缓存的并发访问和过期等策略的软件。因此,缓存是通过软件和硬件共同实现的。
1.1.    持久化层的缓存的范围
缓存的范围决定了缓存的生命周期以及可以被谁访问。缓存的范围分为三类。
1) 事务范围:缓存只能被当前事务访问。缓存的生命周期依赖于事务的生命周期,当事务结束时,缓存也就结束生命周期。在此范围下,缓存的介质是内存。事务可以是数据库事务或者应用事务,每个事务都有独自的缓存,缓存内的数据通常采用相互关联的对象形式。
2) 进程范围:缓存被进程内的所有事务共享。这些事务有可能是并发访问缓存,因此必须对缓存采取必要的事务隔离机制。缓存的生命周期依赖于进程的生命周期,进程结束时,缓存也就结束了生命周期。进程范围的缓存可能会存放大量的数据,所以存放的介质可以是内存或硬盘。缓存内的数据既可以是相互关联的对象形式也可以是对象的松散数据形式。松散的对象数据形式有点类似于对象的序列化数据,但是对象分解为松散的算法比对象序列化的算法要求更快。
3) 集群范围:在集群环境中,缓存被一个机器或者多个机器的进程共享。缓存中的数据被复制到集群环境中的每个进程节点,进程间通过远程通信来保证缓存中的数据的一致性,缓存中的数据通常采用对象的松散数据形式。
对大多数应用来说,应该慎重地考虑是否需要使用集群范围的缓存,因为访问的速度不一定会比直接访问数据库数据的速度快多少。
 
持久化层可以提供多种范围的缓存。如果在事务范围的缓存中没有查到相应的数据,还可以到进程范围或集群范围的缓存内查询,如果还是没有查到,那么只有到数据库中查询。事务范围的缓存是持久化层的第一级缓存,通常它是必需的;进程范围或集群范围的缓存是持久化层的第二级缓存,通常是可选的。
1.2.    持久化层的缓存的并发访问策略
当多个并发的事务同时访问持久化层的缓存的相同数据时,会引起并发问题,必须采用必要的事务隔离措施。
在进程范围或集群范围的缓存,即第二级缓存,会出现并发问题。因此可以设定以下四种类型的并发访问策略,每一种策略对应一种事务隔离级别。
1) 事务型(Transactional)策略:仅仅在受管理环境中适用。它提供了Repeatable Read事务隔离级别。对于经常被读但很少修改的数据,可以采用这种隔离类型,因为它可以防止脏读和不可重复读这类的并发问题。
2) 读写型(read-write)策略:提供了Read Committed事务隔离级别。仅仅在非集群的环境中适用。对于经常被读但很少修改的数据,可以采用这种隔离类型,因为它可以防止脏读这类的并发问题。
3) 非严格读写型(nonstrict-read-write)策略:不保证缓存与数据库中数据的一致性。如果存在两个事务同时访问缓存中相同数据的可能,必须为该数据配置一个很短的数据过期时间,从而尽量避免脏读。对于极少被修改,并且允许偶尔脏读的数据,可以采用这种并发访问策略。
4) 只读型策略(read-only):对于从来不会修改的数据,如参考数据,可以使用这种并发访问策略。
事务型并发访问策略是事务隔离级别最高,只读型的隔离级别最低。事务隔离级别越高,并发性能就越低。
2.    Hibernate中的缓存:
Hibernate中提供了两级Cache,第一级别的缓存是Session级别的缓存,它是属于事务范围的缓存。这一级别的缓存由hibernate管理的,一般情况下无需进行干预;第二级别的缓存是SessionFactory级别的缓存,它是属于进程范围或群集范围的缓存。这一级别的缓存可以进行配置和更改,并且可以动态加载和卸载。
Hibernate还为查询结果提供了一个查询缓存,它依赖于第二级缓存。
2.1.    一级缓存和二级缓存的比较:
  第一级缓存 第二级缓存
存放数据的形式 相互关联的持久化对象 对象的散装数据
缓存的范围 事务范围,每个事务都有单独的第一级缓存 进程范围或集群范围,缓存被同一个进程或集群范围内的所有事务共享
并发访问策略 由于每个事务都拥有单独的第一级缓存,不会出现并发问题,无需提供并发访问策略 由于多个事务会同时访问第二级缓存中相同数据,因此必须提供适当的并发访问策略,来保证特定的事务隔离级别
数据过期策略 没有提供数据过期策略。处于一级缓存中的对象永远不会过期,除非应用程序显式清空缓存或者清除特定的对象 必须提供数据过期策略,如基于内存的缓存中的对象的最大数目,允许对象处于缓存中的最长时间,以及允许对象处于缓存中的最长空闲时间
物理存储介质 内存 内存和硬盘。对象的散装数据首先存放在基于内在的缓存中,当内存中对象的数目达到数据过期策略中指定上限时,就会把其余的对象写入基于硬盘的缓存中。
缓存的软件实现 在Hibernate的Session的实现中包含了缓存的实现 由第三方提供,Hibernate仅提供了缓存适配器(CacheProvider)。用于把特定的缓存插件集成到Hibernate中。
启用缓存的方式 只要应用程序通过Session接口来执行保存、更新、删除、加载和查询数据库数据的操作,Hibernate就会启用第一级缓存,把数据库中的数据以对象的形式拷贝到缓存中,对于批量更新和批量删除操作,如果不希望启用第一级缓存,可以绕过Hibernate API,直接通过JDBC API来执行指操作。 用户可以在单个类或类的单个集合的粒度上配置第二级缓存。如果类的实例被经常读但很少被修改,就可以考虑使用第二级缓存。只有为某个类或集合配置了第二级缓存,Hibernate在运行时才会把它的实例加入到第二级缓存中。
用户管理缓存的方式 第一级缓存的物理介质为内存,由于内存容量有限,必须通过恰当的检索策略和检索方式来限制加载对象的数目。Session的evit()方法可以显式清空缓存中特定对象,但这种方法不值得推荐。 第二级缓存的物理介质可以是内存和硬盘,因此第二级缓存可以存放大量的数据,数据过期策略的maxElementsInMemory属性值可以控制内存中的对象数目。管理第二级缓存主要包括两个方面:选择需要使用第二级缓存的持久类,设置合适的并发访问策略:选择缓存适配器,设置合适的数据过期策略。
 
2.2.    一级缓存的管理:
当应用程序调用Session的save()、update()、savaeOrUpdate()、get()或load(),以及调用查询接口的list()、iterate()或filter()方法时,如果在Session缓存中还不存在相应的对象,Hibernate就会把该对象加入到第一级缓存中。当清理缓存时,Hibernate会根据缓存中对象的状态变化来同步更新数据库。
Session为应用程序提供了两个管理缓存的方法:
evict(Object obj):从缓存中清除参数指定的持久化对象。
clear():清空缓存中所有持久化对象。
2.3.    二级缓存的管理:
2.3.1.      Hibernate的二级缓存策略的一般过程如下:
1) 条件查询的时候,总是发出一条select * from table_name where …. (选择所有字段)这样的SQL语句查询数据库,一次获得所有的数据对象。
2) 把获得的所有数据对象根据ID放入到第二级缓存中。
3) 当Hibernate根据ID访问数据对象的时候,首先从Session一级缓存中查;查不到,如果配置了二级缓存,那么从二级缓存中查;查不到,再查询数据库,把结果按照ID放入到缓存。
4) 删除、更新、增加数据的时候,同时更新缓存。
  Hibernate的二级缓存策略,是针对于ID查询的缓存策略,对于条件查询则毫无作用。为此,Hibernate提供了针对条件查询的Query Cache。
2.3.2.      什么样的数据适合存放到第二级缓存中?
1 很少被修改的数据
2 不是很重要的数据,允许出现偶尔并发的数据
3 不会被并发访问的数据
4 参考数据,指的是供应用参考的常量数据,它的实例数目有限,它的实例会被许多其他类的实例引用,实例极少或者从来不会被修改。
2.3.3.      不适合存放到第二级缓存的数据?
1 经常被修改的数据
2 财务数据,绝对不允许出现并发
3 与其他应用共享的数据。
 
2.3.4.      常用的缓存插件
Hibernater 的二级缓存是一个插件,下面是几种常用的缓存插件:
l EhCache:可作为进程范围的缓存,存放数据的物理介质可以是内存或硬盘,对Hibernate的查询缓存提供了支持。
l OSCache:可作为进程范围的缓存,存放数据的物理介质可以是内存或硬盘,提供了丰富的缓存数据过期策略,对Hibernate的查询缓存提供了支持。
l SwarmCache:可作为群集范围内的缓存,但不支持Hibernate的查询缓存。
l JBossCache:可作为群集范围内的缓存,支持事务型并发访问策略,对Hibernate的查询缓存提供了支持。
 
2.3.5.      配置二级缓存的主要步骤:
1)       选择需要使用二级缓存的持久化类,设置它的命名缓存的并发访问策略。这是最值得认真考虑的步骤。
2)       选择合适的缓存插件,然后编辑该插件的配置文件。
2.4.    使用EhCache配置二级缓存:
2.4.1.      配置准备:
1)       把ehcache-1.2.3.jar加入到当前应用的classpath中。
2)       在hibernate.cfg.xml文件中加入EhCache缓存插件的提供类。
<!-- 配置缓存插件  -->
< property  name= "hibernate.cache.provider_class" >
    org.hibernate.cache.EhCacheProvider
</ property >
3)       挎贝ehcache.xml文件到类路径(项目工程的src目录下),这个文件在Hibernate安装目录的etc下。
2.4.2.      配置步骤:
Hibernate允许在类和集合的粒度上设置第二级缓存。在映射文件中,<class>和<set>元素都有一个<cache>子元素,这个子元素用来配置二级缓存。
示例:以category(产品类别)和product(产品)的映射为例:
1)       修改要配置缓存的那个持久化类的对象关系映射文件:
Category.hbm.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  name= "org.qiujy.domain.cachedemo.Category"  table= "categories" >
        <!—
             配置缓存,必须紧跟在class元素后面
            对缓存中的Category对象采用读写型的并发访问策略
        -->
       <cache usage="read-write"/>
      
        < id  name= "id"  type= "java.lang.Long" >
            < column  name= "id"  />
            < generator  class= "native"  />
        </ id >
        <!--  配置版本号 , 必须紧跟在 id 元素后面  -->
        < version  name= "version"  column= "version"  type= "java.lang.Long"  />
      
        < property  name= "name"  type= "java.lang.String" >
            < column  name= "name"  length= "32"  not-null= "true" />
        </ property >
      
        < property  name= "description"  type= "java.lang.String" >
            < column  name= "description"  length= "255" />
        </ property >
      
        < set  name= "products"  table= "products"  cascade= "all"  inverse= "true" >
            <!-- Hibernate只会缓存对象的简单属性的值,
       要缓存集合属性,必须在集合元素中也加入<cache>子元素
       Hibernate仅仅是把与当前持久对象关联的对象的OID存放到缓存中。
如果希望把整个关联的对象的所有数据都存入缓存,
则要在相应关联的对象的映射文件中配置<cache>元素
           -->
           <cache usage="read-write"/>
          
            < key  column= "categoryId"  not-null= "true" />
            < one-to-many  class= "org.qiujy.domain.cachedemo.Product" />
        </ set >
      
     </ class >
</ hibernate-mapping >
 
Product.hbm.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  name= "org.qiujy.domain.cachedemo.Product"  table= "products" >
      
        <cache usage="read-write"/>
      
        < id  name= "id"  type= "java.lang.Long" >
            < column  name= "id"  />
            < generator  class= "native"  />
        </ id >
        <!--  配置版本号 , 必须紧跟在 id 元素后面  -->
        < version  name= "version"  column= "version"  type= "java.lang.Long"  />
      
        < property  name= "name"  type= "java.lang.String" >
            < column  name= "name"  length= "32"  not-null= "true" />
        </ property >
      
        < property  name= "description"  type= "java.lang.String" >
            < column  name= "description"  length= "255" />
        </ property >
      
        < property  name= "unitCost"  type= "java.lang.Double" >
            < column  name= "unitCost"  />
        </ property >
      
        < property  name= "pubTime"  type= "java.util.Date" >
            < column  name= "pubTime"  not-null= "true"  />
        </ property >
      
        < many-to-one  name= "category"
                 column= "categoryId"
                class= "org.qiujy.domain.cachedemo.Category"
                cascade= "save-update"
                 not-null= "true" >
         </ many-to-one >
      
     </ class >
</ hibernate-mapping >
 
 
2)       编辑ehcache.xml文件:
< ehcache >
     < diskStore  path= "c://ehcache/" />
     < defaultCache
         maxElementsInMemory= "10000"
         eternal= "false"
         timeToIdleSeconds= "120" 
         timeToLiveSeconds= "120" 
         overflowToDisk= "true"  
         />
       
     <!--  设置 Category 类的缓存的数据过期策略  -->
     < cache  name= "org.qiujy.domain.cachedemo.Category"
         maxElementsInMemory= "100"
         eternal= "true"
         timeToIdleSeconds= "0"
         timeToLiveSeconds= "0"
         overflowToDisk= "false"
         />
       
      <!--  设置 Category 类的 products 集合的缓存的数据过期策略  -->
      < cache  name= " org.qiujy.domain.cachedemo.Category.products "
         maxElementsInMemory= "500"
         eternal= "false"
         timeToIdleSeconds= "300"
         timeToLiveSeconds= "600"
         overflowToDisk= "true"
         />
       
     < cache  name= "org.qiujy.domain.cachedemo.Product"
         maxElementsInMemory= "500"
         eternal= "false"
         timeToIdleSeconds= "300"
         timeToLiveSeconds= "600"
         overflowToDisk= "true"
         />
   
</ ehcache >
配置的元素说明:
元素或属性 描述
<diskStore> 设置缓存数据文件的存放目录
<defaultCache> 设置缓存的默认数据过期策略
<cache>
设定具体的命名缓存的数据过期策略
每个命名缓存代表一个缓存区域,每个缓存区域有各自的数据过期策略。命名缓存机制使得用户能够在每个类以及类的每个集合的粒度上设置数据过期策略。
cache元素的属性  
name
设置缓存的名字 , 它的取值为类的全限定名或类的集合的名字
maxInMemory
设置基于内存的缓存中可存放的对象最大数目
eternal
设置对象是否为永久的 ,true 表示永不过期 , 此时将忽略 timeToIdleSeconds timeToLiveSeconds 属性 ;
默认值是 false
timeToIdleSeconds
设置对象空闲最长时间 , 超过这个时间 , 对象过期。当对象过期时 ,EHCache 会把它从缓存中清除。
如果此值为 0, 表示对象可以无限期地处于空闲状态。
timeToLiveSeconds
设置对象生存最长时间 , 超过这个时间 , 对象过期。
如果此值为 0, 表示对象可以无限期地存在于缓存中。
overflowToDisk
设置基于内在的缓存中的对象数目达到上限后 , 是否把溢出的对象写到基于硬盘的缓存中
 
3)       写一测试类:
package org.qiujy.test.cache;
 
import java.util.List;
 
import org.hibernate.HibernateException;
import org.hibernate.Session;
import org.hibernate.Transaction;
import org.qiujy.common.HibernateSessionFactory;
import org.qiujy.domain.cachedemo.Product;
 
public class TestCache {
 
       public static void main(String[] args) {
       
              //test cache.........
              Session session2 = HibernateSessionFactory.getSession();
              Transaction tx2 =null;
             
              try{
                     tx2 = session2.beginTransaction();
                    
                     List list = session2.createQuery("from Product").list();
                    
                     for(int i = 0 ; i < list.size(); i++){
                            Product prod = (Product)list.get(i);
                            System.out.println(prod.getName());
                     }
                    
                    tx2.commit();
              }catch(HibernateException e){
                     if(tx2 != null){
                            tx2.rollback();
                     }
                     e.printStackTrace();
              }finally{
                     HibernateSessionFactory.closeSession();
              }
             
              //-------------------
              Session session3 = HibernateSessionFactory.getSession();
              Transaction tx3 =null;
             
              try{
                     tx3 = session3.beginTransaction();
                    
                     Product prod = (Product)session3.get(Product.class, new Long(1));
                     System.out.println("从cache中得到,不执行SQL---" + prod.getName());
                 
                    tx3.commit();
              }catch(HibernateException e){
                     if(tx3 != null){
                            tx3.rollback();
                     }
                     e.printStackTrace();
              }finally{
                     HibernateSessionFactory.closeSession();
              }
       }
}
首先数据库插入1000条产品记录和1条类别记录。此1000个产品都属于这一类别。然后执行以上测试类,在Session2中查询所有的产品,输出它的产品名,Session2会把这些数据加载到二级缓存中,由于有1000个对象,而配置中定义内存中只能存放500个,剩下的对象就会写到指定的磁盘目录中缓存起来。所以在磁盘相应位置可看到数据文件:
2.5.    查询缓存(Query Cache)
对于经常使用的查询语句,如果启用了查询缓存,当第一次执行查询语句时,Hibernate会把查询结果存放在第二缓存中。以后再次执行该查询语句时,只需从缓存中获得查询结果,从而提高查询性能。
2.5.1.      查询缓存适用于以下场合:
l 在应用程序运行时经常使用的查询语句。
l 很少对与查询语句关联的数据库数据进行插入、删除或更新操作。
2.5.2.      HibernateQuery缓存策略的过程如下:
1) Hibernate首先根据这些信息组成一个Query Key,Query Key包括条件查询的请求一般信息:SQL, SQL需要的参数,记录范围(起始位置rowStart,最大记录个数maxRows),等。
2) Hibernate根据这个Query Key到Query缓存中查找对应的结果列表。如果存在,那么返回这个结果列表;如果不存在,查询数据库,获取结果列表,把整个结果列表根据Query Key放入到Query缓存中。
3) Query Key中的SQL涉及到一些表名,如果这些表的任何数据发生修改、删除、增加等操作,这些相关的Query Key都要从缓存中清空。
只有当经常使用同样的参数进行查询时,这才会有些用处。
启用查询缓存的步骤:
1)       配置二级缓存:
Hibernate提供了三种和查询相关的缓存区域:
l 默认的查询缓存区域:org.hibernate.cache.StandardQueryCache
l 用户自定义的查询缓存区域:
l 时间戳缓存区域:org.hibernate.cache.UpdateTimestampCache
默认的查询缓存区域以及用户自定义的查询缓存区域都用于存放查询结果。而时间戳缓存区域存放了对与查询结果相关的表进行插入、更新或删除操作的时间戳。Hibernate通过时间戳缓存区域来判断被缓存的查询结果是否过期。所以,当应用程序对数据库的相关数据做了修改,Hibernate会自动刷新缓存的查询结果。但是如果其他应用程序对数据库的相关数据做了修改,则无法监测,此时必须由应用程序负责监测这一变化,然后手工刷新查询结果。Query接口的setForceCacheRefresh(true)可以手工刷新查询结果。
在ehcache.xml中添加如下配置:
<!--  设置默认的查询缓存的数据过期策略  -->
     < cache  name= "org.hibernate.cache.StandardQueryCache"
        maxElementsInMemory= "50"
        eternal= "false"
        timeToIdleSeconds= "3600"
        timeToLiveSeconds= "7200"
        overflowToDisk= "true" />
        
     <!--  设置时间戳缓存的数据过期策略  -->
     < cache  name= "org.hibernate.cache.UpdateTimestampsCache"
        maxElementsInMemory= "5000"
        eternal= "true"
        overflowToDisk= "true" />
   
     <!--  设置自定义命名查询缓存 customerQueries 的数据过期策略  -->
     < cache  name= "myCacheRegion"
         maxElementsInMemory= "1000"
         eternal= "false"
         timeToIdleSeconds= "300"
         timeToLiveSeconds= "600"
         overflowToDisk= "true"
         />
 
2)       打开查询缓存:在hibernate.cfg.xml添加如下配置
<!-- 启用查询缓存  -->
< property  name= "cache.use_query_cache" >true </ property >
 
3)       在程序中使用:
虽然按以上设置好了查询缓存,但Hibernate在执行查询语句语句时仍不会启用查询缓存。对于希望启用查询缓存的查询语句,应该调用Query接口的setCacheeable(true)方法:
       测试类如下:
package org.qiujy.test.cache;
 
import java.util.List;
 
import org.hibernate.HibernateException;
import org.hibernate.Query;
import org.hibernate.Session;
import org.hibernate.Transaction;
import org.qiujy.common.HibernateSessionFactory;
import org.qiujy.domain.cachedemo.Product;
 
public class TessQueryCache {
 
       public static void main(String[] args) {
              Session session = HibernateSessionFactory.getSession();
              Transaction tx =null;
             
              try{
                     tx = session.beginTransaction(); 
                     Query query = session.createQuery("from Product");
                      // 激活查询缓存
                     query.setCacheable(true);
                     // 使用自定义的查询缓存区域 , 若不设置 , 则使用标准查询缓存区域
                      query.setCacheRegion("myCacheRegion");
                    
                     List list = query.list();
                     for(int i = 0 ; i < list.size(); i++){
                            Product prod = (Product)list.get(i);
                            System.out.println(prod.getName());
                     }
                    
                    tx.commit();
              }catch(HibernateException e){
                     if(tx != null){
                            tx.rollback();
                     }
                     e.printStackTrace();
              }finally{
                     HibernateSessionFactory.closeSession();
              }
       }
}



使用Ehcache

一,分类
       一级缓存---session
       二级缓存---sessionFactory
       查询缓存---生命周期不确定(如果有人更改了这张表,查询缓存立即失效)
 
二,基础知识
      2.1 一级和二级缓存不支持普通属性,查询缓存支持
      2.2 get(),load(),iterator() 这3种方法支持缓存
                Query,Criteria(查询缓存)由于命中率较低,所以hibernate缺省是关闭;
                        ①修改cache.use_query_cache 为true 打开对查询的缓存,
                        ②调用query.setCacheable(true) 或criteria.setCacheable(true) 。
                list()默认情况下每次都会发的,只要配置了查询缓存时它才不会发sql
      
      2.3 可以使用下面方法管理缓存
                 //管理一级缓存
                 session.evict(obj); ----一条记录(在使用uuid生成策略时。你手动调用该方法会报错 )
                 session.clear();----所有的
                 session.flush() --------会发uuid没发的sql语句(在调用evict或clear方法前,先调用该方法)
                         作用:可以改变sql语句的执行顺序 (hibernate默认是save,update,delete )
 
                 //管理二级缓存
                 sessionFactory.evict(User.class); ----所有的该类型对象
                 sessionFactory.evict(User.class,12); ----一条记录
 
 
      2.4 往缓存里添加数据(有很多方法可以往缓存中添加数据)
                   往二级缓存 添加数据时,save方法不适合native生成方式的主键
      2.5 查询缓存缓存了实体的id,二级缓存缓存了实体
 
三,一级缓存 (一级缓存可以理解为session对象的一个map)
 
 
四,二级缓存
 
       1.配置
Xml代码 复制代码 收藏代码
<!--开启二级缓存-->  
<property name="hibernate.cache.use_second_level_cache">true</property>  
<property name="cache.provider_class">org.hibernate.cache.OSCacheProvider </property>  
  
<!--hibernate.cfg.xml里配置-->  
<class-cache class="yangzhibin.User" user="read-only "/>  
<!--User.hbm.xml里配置-->  
<cache usage="read-write "/>  
[xml] view plaincopy
<!--开启二级缓存-->  
<property name="hibernate.cache.use_second_level_cache">true</property>  
<property name="cache.provider_class">org.hibernate.cache.OSCacheProvider </property>  
  
<!--hibernate.cfg.xml里配置-->  
<class-cache class="yangzhibin.User" user="read-only "/>  
<!--User.hbm.xml里配置-->  
<cache usage="read-write "/>  
        2.usage(策略)取值:
            read-only : 效率最高,但是对象不能修改,修改了会报异常(如:省市表) 
            read-write:严格的读写(如:银行)
            nonstrict-read-write:不严格的读写(如:帖子的回帖量)效率高点
            transactional:一般缓存没这个功能(jboss-cache实现了,oscache不提供这个功能)          
 
五,一级缓存和二级缓存交互
session.setCacheMode(CacheMode.GET ); //仅向二级缓存读 数据,而不向二级缓存写 数据
session.setCacheMode(CacheMode.PUT ); //只向二级缓存写 数据,而不从二级缓存读 数据
 
 
六,查询缓存(对 iterator() 不起作用)
    6.1生命周期
       当前关联的表发生修改,那么查询缓存生命周期结束
    6.2缓存的内容
       针对普通结果集,对实体对象的结果集只缓存id
    6.3配置(hibernate默认查询缓存时关闭 的)
      ① 通过下面方法启动
Xml代码
<!-- 启用查询缓存 -->  
<property name="hibernate.cache.use_query_cache">true</property>  
[xml] view plaincopy
<!-- 启用查询缓存 -->  
<property name="hibernate.cache.use_query_cache">true</property>  
      ②在程序中启用查询缓存(只对query.list()起作用,对query.iterator()不起作用):
        query.setCacheable(true);
        criteria.setCacheable(true);
    6.4会发现奇异的情况
        1,query.list() 可能会发出n条语句(因为查询缓存开着了,所有hibernate在调用query.list()是不是发一条sql语句,而是根据实体类的id来一个一个查询)

在Hibernate配置文件中设置:

<!-- Hibernate SessionFactory -->

    <bean id="sessionFactory" class="org.springframework.orm.hibernate3.LocalSessionFactoryBean">
        <property name="dataSource" ref="dataSource"/>
        <property name="mappingResources">
        <list>
            <value>com/ouou/model/Videos.hbm.xml</value>   
         </list>
         </property>
        <property name="hibernateProperties">
            <props>
                <prop key="hibernate.dialect">org.hibernate.dialect.MySQLDialect</prop>
                <prop key="hibernate.current_session_context_class">thread</prop>
                <prop key="hibernate.cglib.use_reflection_optimizer">false</prop>
                <prop key="hibernate.query.substitutions">true 'Y', false 'N'</prop>
                <!--add ehcache-->
                <prop key="hibernate.cache.provider_class">org.hibernate.cache.EhCacheProvider</prop>
                <prop key="hibernate.cache.use_query_cache">false</prop><!-- 是否使用查询缓存 -->
                <!--
                <prop key="hibernate.cache.provider_configuration_file_resource_path">/ehcache.xml</prop>
                <prop key="hibernate.show_sql">true</prop>
                -->
                <!--<prop key="hibernate.transaction.auto_close_session">true</prop>-->
                <prop key="connection.provider_class">org.hibernate.connection.C3P0ConnectionProvider</prop>
                <!-- Create/update the database tables automatically when the JVM starts up
                 <prop key="hibernate.hbm2ddl.auto">update</prop> -->
                <!-- Turn batching off for better error messages under PostgreSQL -->
                <prop key="hibernate.jdbc.batch_size">25</prop>
                <!--
                <prop key="hibernate.connection.pool_size">10</prop>
                -->
            </props>
        </property>

    </bean>

如果不设置“查询缓存”,那么hibernate只会缓存使用load()方法获得的单个持久化对象,如果想缓存使用findall()、 list()、Iterator()、createCriteria()、createQuery()等方法获得的数据结果集的话,就需要设置hibernate.cache.use_query_cache true 才行

2.首先设置EhCache,建立配置文件ehcache.xml,默认的位置在class-path,可以放到你的src目录下:

<ehcache>

    <!-- Sets the path to the directory where cache .data files are created.

     If the path is a Java System Property it is replaced by
     its value in the running VM.
     The following properties are translated:
     user.home - User's home directory
     user.dir - User's current working directory
     java.io.tmpdir - Default temp file path -->
     <!--<diskStore path="java.io.tmpdir"/>-->
     <diskStore path="/data/ehcache"/>

    <!--Default Cache configuration. These will applied to caches programmatically created through
        the CacheManager.

        The following attributes are required:

        maxElementsInMemory            - Sets the maximum number of objects that will be created in memory
        eternal                                     - Sets whether elements are eternal. If eternal,  timeouts are
                                                            ignored and the element is never expired.
        overflowToDisk                      - Sets whether elements can overflow to disk when the in-memory cache
                                                        has reached the maxInMemory limit.

        The following attributes are optional:
        timeToIdleSeconds            - Sets the time to idle for an element before it expires.
                                                        i.e. The maximum amount of time between accesses before an
                                                        element expires Is only used if the element is not eternal.
                                                        Optional attribute. A value of 0 means that an Element can idle
                                                        for infinity.The default value is 0.
        timeToLiveSeconds              - Sets the time to live for an element before it expires.
                                                         i.e. The maximum time between creation time and when an element
                                                         expires.  Is only used if the element is not eternal.
                                                         Optional attribute. A value of 0 means that and Element can live
                                                         for infinity.
                                                        The default value is 0.
        diskPersistent                           - Whether the disk store persists between restarts of the Virtual
                                                             Machine.
                                                         The default value is false.
        diskExpiryThreadIntervalSeconds   - The number of seconds between runs of the disk expiry thread.
                                                         The default value  is 120 seconds.
        -->

    <defaultCache
        maxElementsInMemory="10000"
        eternal="false"
        overflowToDisk="true"
        timeToIdleSeconds="120"
        timeToLiveSeconds="120"
        diskPersistent="false"
        diskExpiryThreadIntervalSeconds="120"/>
    <cache name="org.hibernate.cache.UpdateTimestampsCache" maxElementsInMemory="5000"
     eternal="true" overflowToDisk="true"/>
    <cache name="org.hibernate.cache.StandardQueryCache" maxElementsInMemory="5" eternal="false"
    timeToLiveSeconds="120" overflowToDisk="true"/>
    <cache name="userCache" maxElementsInMemory="100000" eternal="false" timeToIdleSeconds=
        "600"    timeToLiveSeconds="600" overflowToDisk="false" diskPersistent="false"/>
    <cache name="com.ouou.webapp.util.OuouMethodIntecepter" maxElementsInMemory="100000"
    eternal="false" timeToIdleSeconds="600" timeToLiveSeconds="600" overflowToDisk="false"
    diskPersistent="false"/>
    <cache name="bbcode" maxElementsInMemory="100000" eternal="false" timeToIdleSeconds="600"
    timeToLiveSeconds="600"
    overflowToDisk="false" diskPersistent="false"/>
    <cache name="com.ouou.model.Videos" maxElementsInMemory="10000"  eternal="false"
    overflowToDisk="false" timeToIdleSeconds="120" timeToLiveSeconds="120" diskPersistent="false"/>
    <cache name="com.ouou.model.Tags" maxElementsInMemory="10000"  eternal="false"
    overflowToDisk="false" timeToIdleSeconds="120" timeToLiveSeconds="120" diskPersistent="false"/>
</ehcache>

以com.ouou.model.Videos为例子
在Videos.hbm.xml中配置:
<class name="Videos" table="TEST" lazy="false">
  <cache usage="read-write" region="ehcache.xml中的name的属性值"/>注意:这一句需要紧跟在class标签下面,其他位置无效。
hbm文件查找cache方法名的策略:如果不指定hbm文件中的region="ehcache.xml中的name的属性值",则使用name名为com.ouou.model.Videos的cache,
如果不存在与类名匹配的cache名称,则用defaultCache。
如果Videos包含set集合,则需要另行指定其cache
例如Videos包含Tags集合,则需要
添加如下配置到ehcache.xml中
<cache name="com.ouou.model.Tags"
        maxElementsInMemory="10000" eternal="false" timeToIdleSeconds="120"
        timeToLiveSeconds="120" overflowToDisk="false" />
另,针对查询缓存的配置如下:
<cache name="org.hibernate.cache.UpdateTimestampsCache"
        maxElementsInMemory="5000"
        eternal="true"
        overflowToDisk="true"/>
<cache name="org.hibernate.cache.StandardQueryCache"
        maxElementsInMemory="10000"
        eternal="false"
        timeToLiveSeconds="120"
        overflowToDisk="true"/>

3、 选择缓存策略依据:

<cache  usage="transactional|read-write|nonstrict-read-write|read-only" (1)/>
ehcache不支持transactional,其他三种可以支持。
read-only:无需修改, 那么就可以对其进行只读 缓存,注意,在此策略下,如果直接修改数据库,即使能够看到前台显示效果,
但是将对象修改至cache中会报error,cache不会发生作用。另:删除记录会报错,因为不能在read-only模式的对象从cache中删除。
read-write:需要更新数据,那么使用读/写缓存 比较合适,前提:数据库不可以为serializable transaction isolation level
(序列化事务隔离级别)
nonstrict-read-write:只偶尔需要更新数据(也就是说,两个事务同时更新同一记录的情况很不常见),也不需要十分严格的事务隔离,
那么比较适合使用非严格读/写缓存策略。

4、 调试时候使用log4j的log4j.logger.org.hibernate.cache=debug,更方便看到ehcache的操作过程,主要用于调试过程,实际应用发布时候,请注释掉,以免影响性能。

5、 使用ehcache,打印sql语句是正常的,因为query cache设置为true将会创建两个缓存区域:一个用于保存查询结果集 (
org.hibernate.cache.StandardQueryCache);另一个则用于保存最近查询的一系列表的时间戳(org.hibernate.cache.UpdateTimestampsCache)。
请注意:在查询缓存中,它并不缓存结果集中所包含的实体的确切状态;它只缓存这些实体的标识符属性的值、以及各值类型的结果。
需要将打印sql语句与最近的cache内容相比较,将不同之处修改到cache中,所以查询缓存通常会和二级缓存一起使用


Hibernate缓存详解


hibernate 提供的一级缓存

hibernate是一个线程对应一个session,一个线程可以看成一个用户。也就是说session级缓存(一级缓存)只能给一个线程用,别的线程用不了,一级缓存就是和线程绑定了。

hibernate一级缓存生命周期很短,和session生命周期一样,一级缓存也称session级的缓存或事务级缓存。如果tb事务提交或回滚了,我们称session就关闭了,生命周期结束了。

缓存和连接池的区别:缓存和池都是放在内存里,实现是一样的,都是为了提高性能的。但有细微的差别,池是重量级的,里面的数据是一样的,比如一个池里放100个Connection连接对象,这个100个都是一样的。缓存里的数据,每个都不一样。比如读取100条数据库记录放到缓存里,这100条记录都不一样。

缓存主要是用于查询

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

Student student = (Student)session.load(Student.class, 1);

System.out.println("student.name=" + student.getName());

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

student = (Student)session.load(Student.class, 1);

System.out.println("student.name=" + student.getName());

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

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

Student student = (Student)session.get(Student.class, 1);

System.out.println("student.name=" + student.getName());

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

student = (Student)session.get(Student.class, 1);

System.out.println("student.name=" + student.getName());

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

//同一个session,发出两次iterate查询实体对象

Iterator iter = session.createQuery

("from Student s where s.id<5").iterate();

while (iter.hasNext()) {

Student student = (Student)iter.next();

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

}

System.out.println("--------------------------------------");

//它会发出查询id的语句,但不会发出根据id查询学生的语句,因为iterate使用缓存

iter = session.createQuery("from Student s where s.id<5").iterate();

while (iter.hasNext()) {

Student student = (Student)iter.next();

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

}

一说到iterater查询就要立刻想起:iterater查询在没有缓存的情况下会有N+1的问题。

执行上面代码查看控制台的sql语句,第一次iterate查询会发出N+1条sql语句,第一条sql语句查询所有的id,然后根据id查询实体对象,有N个id就发出N条语句查询实体。

第二次iterate查询,却只发一条sql语句,查询所有的id,然后根据id到缓存里取实体对象,不再发sql语句到数据库里查询了。

//同一个session,发出两次iterate查询,查询普通属性

Iterator iter = session.createQuery(

"select s.name from Student s where s.id<5").iterate();

while (iter.hasNext()) {

String name = (String)iter.next();

System.out.println(name);

}

System.out.println("--------------------------------------");

//iterate查询普通属性,一级缓存不会缓存,所以发出查询语句

//一级缓存是缓存实体对象的

iter = session.createQuery

("select s.name from Student s where s.id<5").iterate();

while (iter.hasNext()) {

String name = (String)iter.next();

System.out.println(name);

}

执行代码看控制台sql语句,第一次发出N+1条sql语句,第二次还是发出了N+1条sql语句。因为一级缓存只缓存实体对象,tb不会缓存普通属性,所以第二次还是发出sql查询语句。

//两个session,每个session发出一个load方法查询实体对象

try {

session = HibernateUtils.getSession();

session.beginTransaction();

Student student = (Student)session.load(Student.class, 1);

System.out.println("student.name=" + student.getName());

session.getTransaction().commit();

}catch(Exception e) {

e.printStackTrace();

session.getTransaction().rollback();

}finally {

HibernateUtils.closeSession(session);

}

第二个session调用load方法

try {

session = HibernateUtils.getSession();

session.beginTransaction();

Student student = (Student)session.load(Student.class, 1);

//会发出查询语句,session间不能共享一级缓存数据

//因为他会伴随着session的消亡而消亡

System.out.println("student.name=" + student.getName());

session.getTransaction().commit();

}catch(Exception e) {

e.printStackTrace();

session.getTransaction().rollback();

}finally {

HibernateUtils.closeSession(session);

}

第一个session的load方法会发出sql语句查询实体对象,第二个session的load方法也会发出sql语句查询实体对象。因为session间不能共享一级缓存的数据,所以第二个session的load方法查询相同的数据还是要到数据库中查询,因为它找不到第一个session里缓存的数据。

//同一个session,先调用save方法再调用load方法查询刚刚save的数据

Student student = new Student();

student.setName("张三");

//save方法返回实体对象的id

Serializable id = session.save(student);

student = (Student)session.load(Student.class, id);

//不会发出查询语句,因为save支持缓存

System.out.println("student.name=" + student.getName());

先save保存实体对象,再用load方法查询刚刚save的实体对象,则load方法不会发出sql语句到数据库查询的,而是到缓存里取数据,因为save方法也支持缓存。当然前提是同一个session。

//大批量的数据添加

for (int i=0; i<100; i++) {

Student student = new Student();

student.setName("张三" + i);

session.save(student);

//每20条更新一次

if (i % 20 == 0) {

session.flush();

//清除缓存的内容

session.clear();

}

}

大批量数据添加时,会造成内存溢出的,因为save方法支持缓存,每save一个对象就往缓存里放,如果对象足够多内存肯定要溢出。一般的做法是先判断一下save了多少个对象,如果save了20个对象就对缓存手动的清理缓存,这样就不会造成内存溢出。

注意:清理缓存前,要手动调用flush方法同步到数据库,否则save的对象就没有保存到数据库里。

注意:大批量数据的添加还是不要使用hibernate,这是hibernate弱项。可以使用jdbc(速度也不会太快,只是比hibernate好一点),或者使用工具产品来实现,比如oracle的Oracle SQL Loader,导入数据特别快。 



Hibernate  二级缓存

二级缓存需要sessionFactory来管理,它是进初级的缓存,所有人都可以使用,它是共享的。

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

使用缓存,肯定是长时间不改变的数据,如果经常变化的数据放到缓存里就没有太大意义了。因为经常变化,还是需要经常到数据库里查询,那就没有必要用缓存了。

hibernate做了一些优化,和一些第三方的缓存产品做了集成。老师采用EHCache缓存产品。

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

//默认配置,所有的类都遵循这个配置

<defaultCache

        //缓存里可以放10000个对象

        maxElementsInMemory="10000"

        //过不过期,如果是true就是永远不过期

        eternal="false"

        //一个对象被访问后多长时间还没有访问就失效(120秒还没有再次访问就失效)

        timeToIdleSeconds="120"

        //对象存活时间(120秒),如果设置永不过期,这个就没有必要设了

        timeToLiveSeconds="120"

        //溢出的问题,如果设成true,缓存里超过10000个对象就保存到磁盘里

        overflowToDisk="true"

        />

我们也可以对某个对象单独配置:

<cache name="com.bjpowernode.hibernate.Student"

        maxElementsInMemory="100"

        eternal="false"

        timeToIdleSeconds="10000"

        timeToLiveSeconds="10000"

        overflowToDisk="true"

        />

还需要在hibernate.cfg.xml配置文件配置缓存,让hibernate知道我们使用的是那个二级缓存。

<!-- 配置缓存提供商 -->

<property name="hibernate.cache.provider_class">

org.hibernate.cache.EhCacheProvider</property>

<!-- 启用二级缓存,这也是它的默认配置 -->

<property name="hibernate.cache.use_second_level_cache">

true</property>

启用二级缓存的配置可以不写的,因为默认就是true开启二级缓存。

必须还手动指定那些实体类的对象放到缓存里在hibernate.cfg.xml里:

//在<sessionfactory>标签里,在<mapping>标签后配置

<class-cache class="com.bjpowernode.hibernate.Student"

usage="read-only"/>

或者在实体类映射文件里:

//在<class>标签里,<id>标签前配置

<cache usage="read-only"/>

usage属性表示使用缓存的策略,一般优先使用read-only,表示如果这个数据放到缓存里了,则不允许修改,如果修改就会报错。这就要注意我们放入缓存的数据不允许修改。因为放缓存里的数据经常修改,也就没有必要放到缓存里。

使用read-only策略效率好,因为不能改缓存。但是可能会出现脏数据的问题,这个问题解决方法只能依赖缓存的超时,比如上面我们设置了超时为120秒,120后就可以对缓存里对象进行修改,而在120秒之内访问这个对象可能会查询脏数据的问题,因为我们修改对象后数据库里改变了,而缓存却不能改变,这样造成数据不同步,也就是脏数据的问题。

第二种缓存策略read-write,当持久对象发生变化,缓存里就会跟着变化,数据库中也改变了。这种方式需要加解锁,效率要比第一种慢。

还有两种策略,请看hibernate文档,最常用还是第一二种策略。

二级缓存测试代码演示:注意上面我们讲的两个session分别调用load方法查询相同的数据,第二个session的load方法还是发了sql语句到数据库查询数据,这是因为一级缓存只在当前session中共享,也就是说一级缓存不能跨session访问。

//开启二级缓存,二级缓存是进程级的缓存,可以共享

//两个session分别调用load方法查询相同的实体对象

try {

session = HibernateUtils.getSession();

session.beginTransaction();

Student student = (Student)session.load(Student.class, 1);

System.out.println("student.name=" + student.getName());

session.getTransaction().commit();

}catch(Exception e) {

e.printStackTrace();

session.getTransaction().rollback();

}finally {

HibernateUtils.closeSession(session);

}

try {

session = HibernateUtils.getSession();

session.beginTransaction();

Student student = (Student)session.load(Student.class, 1);

//不会发出查询语句,因为配置二级缓存,session可以共享二级缓存中的数据

//二级缓存是进程级的缓存

System.out.println("student.name=" + student.getName());

session.getTransaction().commit();

}catch(Exception e) {

e.printStackTrace();

session.getTransaction().rollback();

}finally {

HibernateUtils.closeSession(session);

}

如果开启了二级缓存,那么第二个session调用的load方法查询第一次查询的数据,是不会发出sql语句查询数据库的,而是去二级缓存中取数据。

//开启二级缓存

//两个session分别调用get方法查询相同的实体对象

try {

session = HibernateUtils.getSession();

session.beginTransaction();

Student student = (Student)session.get(Student.class, 1);

System.out.println("student.name=" + student.getName());

session.getTransaction().commit();

}catch(Exception e) {

e.printStackTrace();

session.getTransaction().rollback();

}finally {

HibernateUtils.closeSession(session);

}

try {

session = HibernateUtils.getSession();

session.beginTransaction();

Student student = (Student)session.get(Student.class, 1);

//不会发出查询语句,因为配置二级缓存,session可以共享二级缓存中的数据

//二级缓存是进程级的缓存

System.out.println("student.name=" + student.getName());

session.getTransaction().commit();

}catch(Exception e) {

e.printStackTrace();

session.getTransaction().rollback();

}finally {

HibernateUtils.closeSession(session);

}

注意:二级缓存必须让sessionfactory管理,让sessionfactory来清除二级缓存。sessionFactory.evict(Student.class);//清除二级缓存中所有student对象,sessionFactory.evict(Student.class,1);//清除二级缓存中id为1的student对象。

如果在第一个session调用load或get方法查询数据后,把二级缓存清除了,那么第二个session调用load或get方法查询相同的数据时,还是会发出sql语句查询数据库的,因为缓存里没有数据只能到数据库里查询。

我们查询数据后会默认自动的放到二级和一级缓存里,如果我们想查询的数据不放到缓存里,也是可以的。也就是说我们可以控制一级缓存和二级缓存的交换。

session.setCacheMode(CacheMode.IGNORE);禁止将一级缓存中的数据往二级缓存里放。

还是用上面代码测试,在第一个session调用load方法前,执行session.setCacheMode(CacheMode.IGNORE);这样load方法查询的数据不会放到二级缓存里。那么第二个session执行load方法查询相同的数据,会发出sql语句到数据库中查询,因为二级缓存里没有数据,一级缓存因为不同的session不能共享,所以只能到数据库里查询。

上面我们讲过大批量的数据添加时可能会出现溢出,解决办法是每当天就20个对象后就清理一次一级缓存。如果我们使用了二级缓存,光清理一级缓存是不够的,还要禁止一二级缓存交互,在save方法前调用session.setCacheMode(CacheMode.IGNORE)。

二级缓存也不会存放普通属性的查询数据,这和一级缓存是一样的,只存放实体对象。session级的缓存对性能的提高没有太大的意义,因为生命周期太短了。



Hibernate  查询缓存

一级缓存和二级缓存都只是存放实体对象的,如果查询实体对象的普通属性的数据,只能放到查询缓存里,查询缓存还存放查询实体对象的id。

查询缓存的生命周期不确定,当它关联的表发生修改,查询缓存的生命周期就结束。这里表的修改指的是通过hibernate修改,并不是通过数据库客户端软件登陆到数据库上修改。

hibernate的查询缓存默认是关闭的,如果要使用就要到hibernate.cfg.xml文件里配置:

<property name="hibernate.cache.use_query_cache">true</property>

并且必须在程序中手动启用查询缓存,在query接口中的setCacheable(true)方法来启用。

//关闭二级缓存,没有开启查询缓存,采用list方法查询普通属性

//同一个sessin,查询两次

List names = session.createQuery("select s.name from Student s")

.list();

for (int i=0; i<names.size(); i++) {

String name = (String)names.get(i);

System.out.println(name);

}

System.out.println("-----------------------------------------");

//会发出sql语句

names = session.createQuery("select s.name from Student s")

.setCacheable(true)

.list();

for (int i=0; i<names.size(); i++) {

String name = (String)names.get(i);

System.out.println(name);

}

上面代码运行,由于没有使用查询缓存,而一、二级缓存不会缓存普通属性,所以第二次查询还是会发出sql语句到数据库中查询。

现在开启查询缓存,关闭二级缓存,并且在第一次的list方法前调用setCacheable(true),并且第二次list查询前也调用这句代码,可以写出下面这样:

List names = session.createQuery("select s.name from Student s")

.setCacheable(true)

.list();

其它代码不变,运行代码后发现第二次list查询普通属性没有发出sql语句,也就是说没有到数据库中查询,而是到查询缓存中取数据。

//开启查询缓存,关闭二级缓存,采用list方法查询普通属性

//在两个session中调用list方法

try {

session = HibernateUtils.getSession();

session.beginTransaction();

List names = session.createQuery("select s.name from Student s")

.setCacheable(true)

.list();

for (int i=0; i<names.size(); i++) {

String name = (String)names.get(i);

System.out.println(name);

}

session.getTransaction().commit();

}catch(Exception e) {

e.printStackTrace();

session.getTransaction().rollback();

}finally {

HibernateUtils.closeSession(session);

}

System.out.println("----------------------------------------");

try {

session = HibernateUtils.getSession();

session.beginTransaction();

//不会发出查询语句,因为查询缓存和session的生命周期没有关系

List names = session.createQuery("select s.name from Student s")

.setCacheable(true)

.list();

for (int i=0; i<names.size(); i++) {

String name = (String)names.get(i);

System.out.println(name);

}

session.getTransaction().commit();

}catch(Exception e) {

e.printStackTrace();

session.getTransaction().rollback();

}finally {

HibernateUtils.closeSession(session);

}

运行结果是第二个session发出的list方法查询普通属性,没有发出sql语句到数据库中查询,而是到查询缓存里取数据,这说明查询缓存和session生命周期没有关系。

//开启缓存,关闭二级缓存,采用iterate方法查询普通属性

//在两个session中调用iterate方法查询

运行结果是第二个session的iterate方法还是发出了sql语句查询数据库,这说明iterate迭代查询普通属性不支持查询缓存。

//关闭查询缓存,关闭二级缓存,采用list方法查询实体对象

//在两个session中调用list方法查询

运行结果第一个session调用list方法查询实体对象会发出sql语句查询数据,因为关闭了二级缓存,所以第二个session调用list方法查询实体对象,还是会发出sql语句到数据库中查询。

//开启查询缓存,关闭二级缓存

//在两个session中调用list方法查询实体对象

运行结果第一个session调用list方法查询实体对象会发出sql语句查询数据库的。第二个session调用list方法查询实体对象,却发出了很多sql语句查询数据库,这跟N+1的问题是一样的,发出了N+1条sql语句。为什么会出现这样的情况呢?这是因为我们现在查询的是实体对象,查询缓存会把第一次查询的实体对象的id放到缓存里,当第二个session再次调用list方法时,它会到查询缓存里把id一个一个的拿出来,然后到相应的缓存里找(先找一级缓存找不到再找二级缓存),如果找到了就返回,如果还是没有找到,则会根据一个一个的id到数据库中查询,所以一个id就会有一条sql语句。

注意:如果配置了二级缓存,则第一次查询实体对象后,会往一级缓存和二级缓存里都存放。如果没有二级缓存,则只在一级缓存里存放。(一级缓存不能跨session共享)

//开启查询缓存,开启二级缓存

//在两个session中调用list方法查询实体对象

运行结果是第一个session调用list方法会发出sql语句到数据库里查询实体对象,因为配置了二级缓存,则实体对象会放到二级缓存里,因为配置了查询缓存,则实体对象所有的id放到了查询缓存里。第二个session调用list方法不会发出sql语句,而是到二级缓存里取数据。

查询缓存意义不大,查询缓存说白了就是存放由list方法或iterate方法查询的数据。我们在查询时很少出现完全相同条件的查询,这也就是命中率低,这样缓存里的数据总是变化的,所以说意义不大。除非是多次查询都是查询相同条件的数据,也就是说返回的结果总是一样,这样配置查询缓存才有意义。



  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值