JAVA框架之Hibernate【Hibernate缓存详解】

一、缓存介绍

Hibernate中提供了两级Cache,第一级别的缓存是Session级别的缓存,它是属于事务范围的缓存。这一级别的缓存由hibernate管理的,通常状况下无需进行干预;第二级别的缓存是SessionFactory级别的缓存,它是属于进程范围或群集范围的缓存。这一级别的缓存能够进行配置和更改,而且能够动态加载和卸载。 Hibernate还为查询结果提供了一个查询缓存,它依赖于第二级缓存。

一. 一级缓存和二级缓存的比较:

一级缓存和第二级缓是存放数据的形式是以相互关联的持久化对象,对象的散装数据等方式存储的。每一个事务都有单独的第一级缓存,缓存被同一个进程或集群范围内的全部事务共享,因为每一个事务都拥有单独的第一级缓存,不会出现并发问题,无需提供并发访问策略,因为多个事务会同时访问第二级缓存中相同数据,所以必须提供适当的并发访问策略,来保证特定的事务隔离级别,没有提供数据过时策略。处于一级缓存中的对象永远不会过时,除非应用程序显式清空缓存或者清除特定的对象,因此必须提供数据过时策略,数据过时策略如:基于内存的缓存中的对象的最大数目,容许对象处于缓存中的最长时间,以及容许对象处于缓存中的最长空闲时间,物理存储介质内存,内存和硬盘。对象的散装数据首先存放在基于内存的缓存中,当内存中对象的数目达到数据过时策略中指定上限时,就会把其他的对象写入基于硬盘的缓存中。缓存的软件实如今Hibernate的Session的实现中包含了缓存的实现由第三方提供,Hibernate仅提供了缓存适配器(CacheProvider)。用于把特定的缓存插件集成到Hibernate中。启用缓存的方式只要应用程序经过Session接口来执行保存、更新、删除、加载和查询数据库数据的操做,Hibernate就会启用第一级缓存,把数据库中的数据以对象的形式拷贝到缓存中,对于批量更新和批量删除操做,若是不但愿启用第一级缓存,能够绕过Hibernate API,直接经过JDBC API来执行指操做。用户能够在单个类或类的单个集合的粒度上配置第二级缓存。若是类的实例被常常读但不多被修改,就能够考虑使用第二级缓存。只有为某个类或集合配置了第二级缓存,Hibernate在运行时才会把它的实例加入到第二级缓存中。用户管理缓存的方式:第一级缓存的物理介质为内存,因为内存容量有限,必须经过恰当的检索策略和检索方式来限制加载对象的数目。Session的 evit()方法能够显式清空缓存中特定对象,但这种方法不值得推荐。第二级缓存的物理介质能够是内存和硬盘,所以第二级缓存能够存放大量的数据,数据过时策略的maxElementsInMemory属性值能够控制内存中的对象数目。管理第二级缓存主要包括两个方面:选择须要使用第二级缓存的持久类,设置合适的并发访问策略:选择缓存适配器,设置合适的数据过时策略。

二. 一级缓存的管理:

当应用程序调用Session的save()、update()、savaeOrUpdate()、get()或load(),以及调用查询接口的 list()、iterate()或filter()方法时,若是在Session缓存中还不存在相应的对象,Hibernate就会把该对象加入到第一级缓存中。当清理缓存时,Hibernate会根据缓存中对象的状态变化来同步更新数据库。 Session为应用程序提供了两个管理缓存的方法: evict(Object obj):从缓存中清除参数指定的持久化对象。 clear():清空缓存中全部持久化对象。

三. Hibernate二级缓存的管理:

1. Hibernate二级缓存策略的通常过程以下:
1) 条件查询的时候,老是发出一条select * from table_name where …. (选择全部字段)这样的SQL语句查询数据库,一次得到全部的数据对象
2) 把得到的全部数据对象根据ID放入到第二级缓存中。
3) 当Hibernate根据ID访问数据对象的时候,首先从Session一级缓存中查;查不到,若是配置了二级缓存,那么从二级缓存中查;查不到,再查询数据库,把结果按照ID放入到缓存。
4) 删除、更新、增长数据的时候,同时更新缓存。
Hibernate二级缓存策略,是针对于ID查询的缓存策略,对于条件查询则毫无做用。为此,Hibernate提供了针对条件查询的Query Cache。

2. 什么样的数据适合存放到第二级缓存中?
1) 不多被修改的数据
2) 不是很重要的数据,容许出现偶尔并发的数据
3) 不会被并发访问的数据
4) 参考数据,指的是供应用参考的常量数据,它的实例数目有限,它的实例会被许多其余类的实例引用,实例极少或者历来不会被修改。

3. 不适合存放到第二级缓存的数据?
1) 常常被修改的数据
2) 财务数据,绝对不容许出现并发
3) 与其余应用共享的数据。

4. 经常使用的缓存插件 Hibernater二级缓存是一个插件,下面是几种经常使用的缓存插件:
◆EhCache:可做为进程范围的缓存,存放数据的物理介质能够是内存或硬盘,对Hibernate的查询缓存提供了支持。
◆OSCache:可做为进程范围的缓存,存放数据的物理介质能够是内存或硬盘,提供了丰富的缓存数据过时策略,对Hibernate的查询缓存提供了支持。
◆SwarmCache:可做为群集范围内的缓存,但不支持Hibernate的查询缓存。
◆JBossCache:可做为群集范围内的缓存,支持事务型并发访问策略,对Hibernate的查询缓存提供了支持。

5. 配置Hibernate二级缓存的主要步骤:
1) 选择须要使用二级缓存的持久化类,设置它的命名缓存的并发访问策略。这是最值得认真考虑的步骤。
2) 选择合适的缓存插件,而后编辑该插件的配置文件。

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

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

 

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

 

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

 

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

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

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

     
当你需要数据一致性,而又能保存数据一致性时,频繁更新的数据是能够被缓存的。这里咱们使用缓存的“锁”机制来保证,你使用的Hibernate第三方缓存要支持“锁”,就是read-write模式。这是重点啊。第三方缓存锁的实现方法不一样性能也不一样,锁是缓存性能降低第一缘由,必定要使用高性能的锁,这就要了解多款Hibernate第三方缓存.
网络

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

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


4、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中的?代替参数。


5、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须要漫长的两阶段提交处理,这致使它基本是性能最差的策略。


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

 

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

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

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

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

 

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

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开销。若是内存有限,那这种状况会很明显。


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

相关文章
相关标签/搜索