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

1、缓存介绍


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) 选择合适的缓存插件,然后编辑该插件的配置文件。

二、缓存应用的场景:
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


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值