一、Hibernate缓存深入详解

看附件。。。。。



二、Hibernate 锁机制


hibernate 锁机制包括悲观锁和乐观锁
1.悲观锁:
     它指的是对数据被外界修改持保守态度。假定任何时刻存取数据时,都可能有另一个客户也正在存取同一笔数据,为了保持数据被操作的一致性,于是对数据采取了数据库层次的锁定状态,依靠数据库提供的锁机制来实现。


基于 jdbc 实现的数据库加锁如下:
  select * from account where name="Erica" for update.在更新的过程中,数据库处于加锁状态,任何其他的针对本条数据的操作都将被延迟。本次事务提交后解锁。
而 hibernate 悲观锁的具体实现如下:
  String sql="查询语句";
  Query query=session.createQuery(sql);
  query.setLockMode("对象",LockModel.UPGRADE);

  说到这里,就提到了hibernate 的加锁模式:
LockMode.NONE : 无锁机制。
LockMode.WRITE:Hibernate 在 Insert 和 Update 记录的时候会自动获取。
LockMode.READ: Hibernate 在读取记录的时候会自动获取。
这三种加锁模式是供 hibernate 内部使用的,与数据库加锁无关


LockMode.UPGRADE:利用数据库的 for update 字句加锁。
  在这里我们要注意的是:只有在查询开始之前(也就是hiernate生成sql语句之前)加锁,才会真正通过数据库的锁机制加锁处理。否则,数据已经通过不包含for updata子句的sql语句加载进来,所谓的数据库加锁也就无从谈起。

  但是,从系统的性能上来考虑,对于单机或小系统而言,这并不成问题,然而如果是在网络上的系统,同时间会有许多联机,假设有数以百计或上千甚至更多的并发访问出现,我们该怎么办?如果等到数据库解锁我们再进行下面的操作,我们浪费的资源是多少?--这也就导致了乐观锁的产生。


 2.乐观锁:
 乐观锁定(optimistic locking)则乐观的认为资料的存取很少发生同时存取的问题,因而不作数据库层次上的锁定,为了维护正确的数据,乐观锁定采用应用程序上的逻辑实现版本控制的方法。
 例如若有两个客户端,A客户先读取了账户余额100元,之后B客户也读取了账户余额100元的数据,
A客户提取了50元,对数据库作了变更,此时数据库中的余额为50元,B客户也要提取30元,根据其所取得的资料,100-30将为70余额,若此时再对数据库进行变更,最后的余额就会不正确。
 在不实行悲观锁定策略的情况下,数据不一致的情况一但发生,有几个解决的方法,一种是先更新为主,一种是后更新的为主,比较复杂的就是检查发生变动的数据来实现,或是检查所有属性来实现乐观锁定。


 Hibernate 中透过版本号检查来实现后更新为主,这也是Hibernate 所推荐的方式 ,在数据库中加入一个 VERSON 栏记录,在读取数据时连同版本号一同读取,并在更新数据时递增版本号,然后比对版本号与数据库中的版本号,如果大于数据库中的版本号则予以更新,否则就回报错误。
 以刚才的例子,A客户读取账户余额1000元,并连带读取版本号为5的话,B客户此时也读取账号余额1000元,版本号也为5,A客户在领款后账户余额为500,此时将版本号加1,版本号目前为6,而数据库中版本号为5,所以予以更新,更新数据库后,数据库此时余额为500,版本号为6,B客户领款后要变更数据库,其版本号为5,但是数据库的版本号为6,此时不予更新,B客户数据重新读取数据库中新的数据并重新进行业务流程才变更数据库。
 以Hibernate 实现版本号控制锁定的话,我们的对象中增加一个version属性,例如:
public class Account {
   private int version;
   ....

   public void setVersion(int version) {
       this.version = version;
   }

   public int getVersion() {
       return version;
   }
}

而在映像文件中,我们使用optimistic-lock属性设定version 控制,<id> 属性栏之后增加一个
<version>标签,如下:

<hibernate-mapping>
   <class name="onlyfun.caterpillar.Account" talble="ACCOUNT"
          optimistic-lock="version">
       <id...../>
       <version name="version" column="VERSION"/>
        ....
   </class>
</hibernate-mapping>


设定好版本控制之后,在上例中如果B 客户试图更新数据,将会引发StableObjectStateException 例外 ,我们可以捕捉这个例外,在处理中重新读取数据库中的数据,同时将 B客户目前的数据与数据库中的数据秀出来,让B客户有机会比对不一致的数据,以决定要变更的部份,或者您可以设计程式自动读取新的资料,并重复扣款业务流程,直到数据可以更新为止,这一切可以在背景执行,而不用让您的客户知道。
  但是乐观锁也有不能解决的问题存在:上面已经提到过乐观锁机制的实现往往基于系统中的数据存储逻辑,在我们的系统中实现,来自外部系统的用户余额更新不受我们系统的控制,有可能造成非法数据被更新至数据库。因此我们在做电子商务的时候,一定要小心的注意这项存在的问题,采用比较合理的逻辑验证,避免数据执行错误。

 也可以在使用Session的load()或是lock()时指定锁定模式以进行锁定。
 如果数据库不支持所指定的锁定模式,Hibernate会选择一个合适的锁定替换,而不是丢出一个例外


三、hibernate 事务,一级缓存,二级缓存

通过以上的介绍可以看出 hibernate 主要从以下几个方面来优化查询性能:

  1,降低访问数据库的频率,减少select语句的数目,实现手段有:使用迫切左外连接或迫切内连接;对延迟检索或立即检索设置批量检索数目;使用查询缓存。

  2,避免加载多余的应用程序不需要访问的数据,实现手段有:使用延迟加载策略;使用集合过滤。

  3,避免报表查询数据占用缓存,实现手段为利用投影查询功能,查询出实体的部分属性。

  4,减少 select 语句中的字段,从而降低访问数据库的数据量,实现手段为利用Query 的 iterate() 方法。


Query的 iterate() 方法首先检索ID字段,然后根据 ID 字段到 hibernate 的第一级缓存以及第二级缓存中查找匹配的Customer对象,如果存在,就直接把它加入到查询结果集中,否则就执行额外的select语句,根据ID字段到数据库中检索该对象。

Java代码   收藏代码
  1. Query query = session.createQuery("from Customer where age<30");  

  2. Iterator result = query.iterate();  


对于经常使用的查询语句,如果启用了查询缓存,当第一次执行查询语句时,hibernate 会把查询结果存放在第二级缓存中,以后再次执行该查询语句时,只需从缓存中获得查询结果,从而提高查询性能。如果查询结果中包含实体,第二级缓存只会存放实体的OID,而对于投影查询,第二级缓存会存放所有的数据值。


  查询缓存适用于以下场合:在应用程序运行时经常使用的查询语句;很少对与查询语句关联的数据库数据进行插入,删除,更新操作。


对查询语句启用查询缓存的步骤如下:

  1,配置第二级缓存。

  2,在hibernate的配置文件中设置查询缓存属性:


Txt代码   收藏代码
  1. hibernate.cache.use_query_cache=true  

   3,即使设置了缓存,在执行查询语句时仍然不会启用查询缓存,只有在调用query.setCacheable() 后才启用缓存:

Java代码   收藏代码
  1. Query query = session.createQuery("from Customer c where c.age > :age");  

  2. query.setInteger("age", age):  

  3. query.setCacheable(true);  


如果希望更加精粒度地控制查询缓存,可以设置缓存区域:

Java代码   收藏代码
  1. query.setCacheRegion("customerQueries");  


hibernate提供了3种和查询相关的缓存区域:

  1,默认的查询缓存区域:net.sf.hibernate.cache.StandardQueryCache。

  2,用户自定义的查询缓存区域:如 customerQueries。

  3,时间戳缓存区域:net.sf.hibernate.cache.UpdateTimestampCache。

 默认的查询缓存区域以及用户自定义的查询缓存区域都用于存放查询结果,而时间戳缓存区域存放了对于查询结果相关的表进行插入,更新,删除操作的时间戳。hibernate 通过时间戳缓存区域来判断被缓存的查询结果是否过期。当应用进程对数据库的相关数据做了修改,hibernate 会自动刷新缓存的查询结果。但是如果其它应用进程对数据库的相关数据做了修改,hibernate 无法监测到这一变化,此时必须由应用程序负责监测这一变化(如通过发送和接收事件或消息机制),然后手工刷新查询结果。

  Query.setForceCacheRefresh(true) 方法允许手工刷新查询结果,它使得hibernate 丢弃查询缓存区域中己有的查询结果,重新到数据库中查询数据,再把查询结果存放在查询缓存区域中。


 一个session可以和多个事务对应:

Java代码   收藏代码
  1. Transaction trans1 = session.beginTransaction();  

  2.   ... ...//数据库操作

  3.   trans1.commit();//提交第一个事务

  4.   session.disconnect();//释放数据库连接

  5.   ... ...//执行一些耗时的操作,这段操作不属于任何事务

  6.   session.reconnect();//重新获取数据库连接

  7.   Transaction trans2 = session.beginTransaction();//开始第二个事务

  8.   ... ...//数据库操作

  9.   trans2.commit();//提交第二个事务


 注意:如果在执行session的一个事务时出现了异常,就必须立即关闭这个session,不能再利用这个session来执行其它的事务。

  许多数据库系统都有自动管理锁的功能,它们能根据事务执行的SQL语句,自动在保证事务间的隔离性与保证事务间的并发性之间做出权衡,然后自动为数据库资源加上适当的锁,在运行期间还会自动升级锁的类型,以优化系统的性能。

  对于普通的并发性事务,通过系统的自动锁定管理机制基本可以保证事务之间的隔离性,但如果对数据安全,数据库完整性和一致性有特殊要求,也可以由事务本身来控制对数据资源的锁定和解锁。

  数据库系统能够锁定的资源包括:数据库,表,区域,页面,键值(指带有索引的行数据),行(即表中的单行数据)。在数据库系统中,一般都支持锁升级,以提高性能。

按照封锁程序,锁可以分为:共享锁,独占锁,更新锁。

 共享锁:用于读数据操作,它是非独占的,允许其它事务同时读取其锁定的资源,但不允许其它事务更新它。

 独占锁:也称排它锁,适用于修改数据的场合,它所销定的资源,其它事务不能读取也不能修改。

更新锁:在更新操作的初始化阶段用来锁定可能要被修改的资源,这可以避免使用共享锁造成的死锁现象。

  许多的数据库系统能够自动定期搜索和处理死锁问题,当检测到锁定请求环时,系统将结束死锁优先级最低的事务,并且撤销该事务。

应用程序中可以采用下面的一些方法尽量避免死锁:

  1,合理安排表访问顺序;

  2,使用短事务;

  3,如果对数据的一致性要求不高,可以允许脏读,脏读不需要对数据资源加锁,可以避免冲突;

  4,如果可能的话,错开多个事务访问相同数据资源的时间,以防止锁冲突。

  5,使用尽可能低的事务隔离级别。

  为了实现短事务,在应用程序中可以考虑使用以下策略:

  1,如果可能的话,尝试把大的事务分解为多个小的事务,然后分别执行,这保证每个小事务都很快完成,不会对数据资源锁定很长时间。

  2,应该在处理事务之前就准备好用户必须提供的数据,不应该在执行事务过程中,停下来长时间等待输入数据。

 数据库系统提供了四种事务隔离级别供用户选择:

  1,Serializable:串行化。

  2,Repeatable Read:可重复读。

  3,Read Commited:读己提交数据。

  4,Read Uncommited:读未提交数据。


隔离级别越高,越能保证数据的完整性和一致性,但是对并发性能的影响也越大。对于多数应用程序,可以优先把数据库系统的隔离级别设为 ReadCommited ,它能够避免脏读,而且具有较好的并发性能,尽管它会导致不可重复读,虚读和第二类丢失更新这些并发问题,在可能出现这类问题的个别场合,可以由应用程序采用悲观锁或乐观锁来控制。

  JDBC数据库连接使用数据库系统默认的隔离级别,在hibernate的配置文件中可以显式地设置隔离级别(hibernate.connection.isolation=2),每一种隔离级别都对应一个整数。


1:Read Uncommitted;

  2:Read Committed;

  4:Repeatable Read;

  8:Serializable

在受管理的环境中,如果hibernate使用的是数据库连接来自于应用服务器提供的数据源,hibernate不会修改这些连接的事务隔离级别,在这种情况下,应该通过修改应用服务器的数据源配置来修改隔离级别。

  悲观锁: 指在应用程序中显式地为数据资源加锁,先锁定资源再进行操作,尽管悲观锁能够防止丢失更新和不可重复读这类并发问题,但是它会影响并发性能,因此应该很谨慎地使用悲观锁。


乐观锁:完全依靠数据库的隔离级别来自动管理锁的工作,应用程序采用版本控制手段来避免可能出现的并发问题。

  悲观锁有两种实现方式:1,在应用程序中显式指定采用数据库系统的独占锁来锁定数据资源;2,在数据库表中增加一个表明记录状态的LOCK字段,当它取值为Y时,表示该记录己经被某个事务锁定,如果为N,表明该记录处于空闲状态,事务可以访问它。

  以下select语句,指定采用独占锁来锁定查询的记录:select ... for update;执行该查询语句的事务持有这把锁,直到事务结束才会释放锁。

  hibernate可以采用如下方式声明使用悲观锁:Account account = (Account)session.get(Account.class, 1, LockMode.UPGRADE);


net.sf.hibernate.LockMode 类表示锁模式,它的取值如下:

LockMode.NONE:默认值。先查缓存,缓存没有再去数据库中查。

LockMode.READ:总是查询数据库,如果映射文件设置了版本元素,就执行版本比较。主要用于对一个游离对象进行版本检查。

LockMode.UPGRADE:总是查询数据库,如果映射文件设置了版本元素,就执行版本比较。如果数据库支持悲观锁就执行.... for update。否则执行普通查询。

LockMode.UPGRADE_NOWAIT:和UPGRADE功能一样,此外,对oracle数据库执行... for update nowait; nowait表明,如果不能立即获得悲观锁就抛出异常。

LockMode.WRITE:当hibernate向数据库保存或更新一个对象时,会自动使用这种模式,它仅供hibernate内部使用,应用程序中不应该使用它。

如果数据库不支持 select ... for update 语句,也可以由应用程序来实现悲观锁,这需要要表中增加一个锁字段 lock。

  hibernate 映射文件中的 <version> 和 <timestamp> 元素都具有版本控制功能。<version> 利用一个递增的整数来跟踪数据库表中记录的版本,<timestamp>用时间戳来跟踪数据库表中记录的版本。

version的用法如下:

  配置文件中:<version name="version" column="VERSION"/>必须紧跟在<id>元素的后面。数据库中的 version(int) 字段与 version 属性映射。

  应用程序无需为JavaBean的version属性显示赋值,在持久化JavaBean对象时,hibernate会自动为它赋初始值0,在更新数据时,hibernate会更新自动version属性:

Sql代码   收藏代码
  1. update ACCOUNTS setNAME='Tom',BALANCE=1100,VERSION=1 where ID=1 and VERSION=0;  


  如果在此过程中有其它程序操作过此记录,那么它的version就会有更新,再次执行update语句时会找不到匹配的记录,此时hibernate会抛出StaleObjectStateException。在应用程序中应该处理这种异常,处理方法有两种:

  1,自动撤消事务,通知用户信息己被其它事务修改,需要重新开始事务。

  2,通知用户信息己被其它事务修改,显示最新数据,由用户决定如果继续。

  只有当hibernate通过 update 语句更新一个对象时,才会修改它的version属性,对于存在关联关系的对象,只更新发生变化的对象,对没有发生变化的关联对象是不会更新的,也就是说 version 不具有级联特性。


timestamp用法如下:

  配置文件和表中各加一个属性(表中是timestamp类型):<timestamp name="lastUpdatedTime" column="LAST_UPDATED_TIME" />必须紧跟在<id>元素的后面。

  当持久化一个JavaBean对象时,hibernate会自动用当前的系统时间为lastUpdatedTime属性赋值,更新时也会用系统时间来更新此字段。理论上<version>元素比<timestamp>更安全一些,因为<timestamp>只能精确到秒,不能处理毫秒内的同步。

  因此,建议使用基于整数的<version>元素。


对游离对象进行版本检查,如果不一致,会抛出StaleObjectStateException()。:

Java代码   收藏代码
  1. Transaction trans = session.beginTransaction();  

  2. session.lock(account, LockMode.READ);//仅仅执行版本检查(与数据库中的最新数据进行比较),而不会保存数据库。

  3. trans.commit();  


如果数据库中不包含代表版本或时间戳的字段,hibernate提供了其它方法实现乐观锁,把<class>元素的optimistic-lock属性设为all。把<class>元素的optimistic-lock属性设为all或dirty。必须同时把dynamic-update属性设为true。

  optimistic-lock=true时,hibernate更新时会在where子句中包含JavaBean对象被加载时的所有属性。

  optimistic-lock=dirty时,hibernate更新时会在where子句中仅包含被更新过的属性。

  尽管这种方法也能实现乐观锁,但是这种方法速度很慢,而且只适用于在同一个session中加载了该对象,然后又在同一个session中更新了此对象的场合。如果在不同的session中,会导致第二个session无法知道JavaBean对象被第一个session加载时所有属性的初始值,因此不能在update语句的where子句中包含JavaBean对象的属性的初始值,因此执行以下update语句:update ACCOUNTS set NAME='tom',BALANCE=900 where ID=1;这会导致当前事务覆盖其它事务对这条记录己做的更新。


  hibernate的二级缓存本身的实现很复杂,必须实现并发访问策略以及数据过期策略。SessionFactory的外置缓存是一个可配置的缓存插件,在默认情况下不会启用。

二级缓存,进程范围或群集范围,会出现并发问题,对二级缓存可以设定以下四种类型的并发访问策略,每一种策略对应一种事务隔离级别。

  1,事务型:仅仅在受管理环境中适用,它提供Repeatable Read事务隔离级别,对于经常读但是很少写的数据,可以采用这种隔离级别,因为它可以防止脏读和不可重复读这类并发问题。

  2,读写型:提供Read Committed事务隔离级别,仅仅在非群集的环境中适用,对于经常读但是很少写的数据,可以采用这种隔离类型,因为它可以防止脏读这类并发问题。

  3,非严格读写型:不保证缓存与数据库中数据的一致性。如果存在两个事务同时访问缓存中相同数据的可能,必须为该数据配置一个很短的数据过期时间,从而尽量避免脏读,对于极少被修改并且允许脏读的数据,可以采用这种并发访问策略。

  4,只读型:对于从来不会写的数据,可以使用这种并发访问策略。


事务型策略的隔离级别最高,只读型的最低,事务隔离级别越高,并发性能越低,如果二级缓存中存放中的数据会经常被事务修改,就不得不提高缓存的事务隔离级别,但这又会降低并发性能,因此,只有符合以下条件的数据才适合于存放到二级缓存中:

  1,很少被修改;

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

3,不会被并发访问的数据;

   4,参考数据;

以下数据不适合于存放到二级缓存中:

  1,经常被修改的数据;

2,财务数据,绝对不允许出现并发问题;

3,与其它应用共享的数据;

  hibernate 还为查询结果提供了一个查询缓存,它依赖于二级缓存。

  Session 为应用程序提供了两个管理一缓存的方法:

  evict():从缓存中清除参数指定的持久化对象;如果在映射文件关联关系的cascade为all或all-delete-orphan时,会级联清除;它适用于不希望session继续按照该对象的状态变化来同步更新数据库;在批量更新或指量删除的场合,当更新或删除一个对象后,及时释放该对象占用的内存;值得注意的是,批量更新或删除的最佳方式是直接通过JDBC API执行相关的SQL语句或者调用相关的存储过程。

  clear():清空缓存中所有持久化对象;

  在多数情况下,不提倡通过evict()和clear()方法来管理一级缓存,因为它们并不能显着地提高应用的性能,管理一级缓存的最有效的方法是采用合理的检索策略和检索方式,如通过延迟加载,集合过滤,投影查询等手段来节省内存开销。

  hibernate中直接通过JDBC API来执行更新或删除操作的方法如下:

Java代码   收藏代码
  1. Transaction trans = session.beginTransaction();  

  2.   Connection conn = session.connection();  

  3.   PreparedStatement statement = conn.prepareStatement("update ACCOUNTS set AGE=AGE+1 where AGE>0");  

  4.   statement.executeUpdate();  

  5.   trans.commit();  



如果底层数据库支持存储过程,也可以直接调用存储过程来执行指量更新:

Java代码   收藏代码
  1. Transaction trans = session.beginTransaction();  

  2.  Connection conn = session.connection();  

  3.  CallableStatement statement = conn.prepareCall("{call batchUpdateCustomer(?)}");  

  4.  statement.setInt(1, 0);//把第1个参数的值设为0

  5.  statement.executeUpdate();  

  6.  trans.commit();  


  hibernate中session的各种重载的update()方法一次都只能更新一个对象,而delete()方法有些重载形式允许以HQL语句作为参数,如:

Java代码   收藏代码
  1. session.delete("from Customer c where c.age>0");  


 但是它并不是执行一条delete语句,而是把符合条件的数据先查找出来,再一个个地执行delete操作。


hibernate的二级缓存允许选用以下类型的缓存插件:

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

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

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

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

  下表列出了以上四种类型的缓存插件支持的并发访问策略:

8ce2dc9a-a69e-37a1-8c61-ac832d52f1cf.jpg


以面的四种缓存插件都是由第三方提供的。EHCache来自于hibernate开放源代码组织的另一个项目;JBossCache由JBoss开放源代码组织提供;为了把这些缓存插件集成到hibernate中,hibernate提供了net.sf.hibernate.cache.CacheProvider接口,它是缓存插件与hibernate之间的适配器。hibernate为以上缓存插件分别提供了内置的CacheProvider实现:

net.sf.hibernate.cache.EhCacheProvider

  net.sf.hibernate.cache.OSCacheProvider

  net.sf.hibernate.cache.SwarmCacheProvider

  net.sf.hibernate.cache.TreeCacheProvider:JBossCache

插件适配器。

配置进程范围内的二级缓存主要包含以下步骤:

  1,选择需要使用二级缓存的持久化类。设置它的命名缓存的并发访问策略。hibernate既允许在分散的各个映射文件中为持久化类设置二级缓存,还允许在hibernate的配置文件hibernate.cfg.xml中集中设置二级缓存,后一种方式更有利于和缓存相关的配置代码的维护。示例如下:

Xml代码   收藏代码
  1. <hibernate-configuration>

  2. <session-factory>

  3. <property ... >

  4. <!-- 设置JBossCache适配器 -->

  5. <propertyname="cache.provider_class">net.sf.hibernate.cache.TreeCacheProvider</property>

  6. <propertyname="cache.use_minimal_puts">true</property>

  7. <mapping .../>

  8. <!-- 设置Category类的二级缓存的并发访问策略 -->

  9. <class-cacheclass="mypack.Category"usage="transaction"/>

  10. <!-- 设置Category类的items集合的二级缓存的并发访问策略 -->

  11. <collection-cachecollection="mypack.Category.items"usage="transactional"/>

  12. <!-- 设置Item类的二级缓存的并发访问策略 -->

  13. <class-cacheclass="mypack.Item"usage="transactional"/>

  14. </session-factory>

  15. </hibernate-configuration>


cache.use_minimal_puts属性为true,表示hibernate会先检查对象是否己经存在于缓存中,只有当对象不在缓存中,才会向缓存加入该对象的散装数据,默认为false。对于群集范围的缓存,如果读缓存的系统开销比写缓存的系统开销小,可以将此属性设为true,从而提高访问缓存的性能,而对于进程范围内的缓存,此属性应该取默认值false。

  2,选择合适的缓存插件,每种插件都有自带的配置文件,因此需要手工编辑该配置文件,EHCache的配置文件为ehcache.xml,而JBossCache的配置文件为treecache.xml。在配置文件中需要为每个命名缓存设置数据过期策略。

  hibernate允许在类和集合的粒度上设置二级缓存,在映射文件中,<class>和<set>元素都有一个<cache>子元素,这个子元素用来配置二级缓存,例如以下代码把Category实例放入二级缓存中,采用读写并发访问策略:

Xml代码   收藏代码
  1. <classname="mypack.Category"talbe="CATEGORIES">

  2. <cacheusage="read-write"/>

  3. <id ...>

  4. </id>

  5.   ...  

  6. </class>


  每当应用程序从其它对象导航到Category对象,或者从数据库中加载Category对象时,hibernate就会把这个对象放到第二级缓存中,<class>元素的<cache>子元素表明hibernate会缓存Category对象的简单属性的值,但是它并不会同时缓存Category对象的集合属性,如果希望缓存集合属性中的元素,必须在<set>元素中加入<cache>子元素:

Xml代码   收藏代码
  1. <setname="items"inverse="true"lazy="true">

  2. <cacheusage="read-write"/>

  3. <key ...>

  4. </set>



当应用程序调用category.getItems().iterate()方法时,hibernate会把item集合中的元素存放到缓存中,此时hibernate仅仅把与Category关联的Item对象的OID存放到缓存中,如果希望把整个Item对象的散装数据存入缓存,应该在Item.hbm.xml文件的<class>元素中加入<cache>子元素。

  EHCache缓存插件是理想的进程范围内的缓存实现。如果使用这种缓存插件,需要在hibernate的hibernate.properties配置文件中指定EhCacheProvider适配器,代码如下:


hibernate.cache.provider=net.sf.hibernate.cache.EhCacheProvider

 EHCache缓存有自己的配置文件,名为 ehcache.xml,这个文件必须存放于应用的classpath中,下面是一个样例:

Xml代码   收藏代码
  1. <ehcache>

  2. <diskStorepath="C:\\temp"/>

  3. <defaultCachemaxElementsInMemory="10000"eternal="false"timeToIdleSeconds="120"timeToLiveSeconds="120"overflowToDisk="true"/>

  4. <cachename="mypack.Category"maxElementsInMemory="500"eternal="true"timeToIdleSeconds="0"timeToLiveSeconds="0"overflowToDisk="false"/>

  5. <cachename="mypack.Category.items"maxElementsInMemory="10000"eternal="false"timeToIdleSeconds="300"timeToLiveSeconds="600"overflowToDisk="true"/>

  6. <cachename="mypack.Item"maxElementsInMemory="10000"eternal="false"timeToIdleSeconds="300"timeToLiveSeconds="600"overflowToDisk="true"/>

  7. </ehcache>


  hibernate软件包的etc目录下提供了ehcache.xml文件的样例,并且对于它的配置元素做了详细的说明。

  ehcache.xml 目录下提供了ehcache.xml文件的样例,并且对它的配置元素做了详细的说明:

<diskStore> :指定一个文件目录,当EHCache把数据写到硬盘上时,将把数据写到这个文件目录下。

<defaultCache> :设定缓存的默认数据过期策略。

<cache> :设定具体的命名缓存的数据过期策略。


在映射文件中,对每个需要二级缓存的类和集合都做了单独的配置,与此对应,在ehcache.xml文件中通过<cache>元素来为每个需要二级缓存的类和集合设定缓存的数据过期策略。下面解释一下<cache>元素的各个属性的作用:

name :设置缓存的名字,它的取值为类的完整名字或者类的集合的名字,如果name属性为mypack.Category,表示Category类的二级缓存;如果name属性为mypack.Category.items,表示Category类的items集合的二级缓存。

  maxInMemory :设置基于内存的缓存可存放的对象的最大数目。

  eternal :如果为true,表示对象永远不会过期,此时会忽略timeToIdleSeconds和timeToLiveSeconds属性。默认为false。

  timeToIdleSeconds :设定允许对象处于空闲状态的最长时间,以秒为单位,当对象从最近一次被访问后,如果处于空闲状态的时间超过了指定的值,这个对象会过期,EHCache将把它从缓存中清除,只有当eternal属性为false,它才有效,值为0表示对象可以无限期地处于空闲状态。

  timeToLiveSeconds :设定对象允许存在于缓存中的最长时间,以秒为单位,当对象自从被放入缓存中后,如果处于缓存中的时间超过了指定的值,这个对象就会过期,EHCache将把它从缓存中清除,只有当eternal属性为false,它才有效,值为0表示对象可以无限期地处于空闲状态。它的值必须大于或等于timeToIdleSeconds的值才有意义。

  overflowToDisk :如果为true,表示当基于内存的缓存中的对象数目达到了maxInMemory界限,会把溢出的对象写到基于硬盘的缓存中。

  每个命名缓存代表一个缓存区域,每个缓存区域有各自的数据过期策略,命名缓存机制使得用户能够在每个类以及类的每个集合的粒度上设置数据过期策略。


EHCache适用于hibernate应用发布在单个机器中的场合。



在群集环境下,可以用 JBossCache 作为 hibernate 的二级缓存,它的配置步骤如下:

1,在hibernate配置文件中设置JBossCache适配器,并且为需要使用二级缓存的类和集合设置缓存的并发访问策略。

  2,编辑JBossCache自身的配置文件,名为treecache.xml,这个文件必须放在classpath中,对于群集环境中的每个节点,都必须提供单独的treecache.xml文件,假如群集环境中有两个节点node A和node B,node A节点的名字为ClusterA,下面是node A节点的 treecache.xm l文件的样例:

Xml代码   收藏代码
  1. <?xmlversion="1.0"encoding="UTF-8"?>

  2. <server>

  3. <classpathcodebase="./lib"archives="jboss-cache.jar,jgroups.jar"/>

  4. <!-- 把TreeCache发布为JBoss的一个JMX服务 -->

  5. <mbeancode="org.jboss.cache.TreeCache"name="jboss.cache:service=TreeCache">

  6. <depends>jboss:service=Naming</depends>

  7. <depends>jboss:service=TransactionManager</depends>

  8. <!-- TreeCache运行在群集环境的名为ClusterA的节点上 -->

  9. <attributename="ClusterName">ClusterA</attribute>

  10. <!-- TreeCache采用同步通信机制 -->

  11. <attributename="CacheMode">REPL_SYNC</attribute>

  12. <attributename="SyncReplTimeout">10000</attribute>

  13. <attributename="LockAcquisitionTimeout">15000</attribute>

  14. <attributename="FetchStateOnStartup">true</attribute>

  15. <!-- TreeCache采用内置的数据过期策略:LRUPolicy -->

  16. <attributename="EvictionPolicyClass">org.jboss.cache.eviction,LRUPolicy</attribute>

  17. <attributename="EvictionPolicyConfig">

  18. <config>

  19. <attributename="wakeUpIntervalSeconds">5</attribute>

  20. <!-- Cache wide default -->

  21. <regionname="/_default_">

  22. <attributename="maxNodes">5000</attribute>

  23. <attributename="timeToIdleSeconds">1000</attribute>

  24. </region>

  25. <!-- 配置Category类的数据过期策略 -->

  26. <regionname="/mypack/Category">

  27. <attributename="maxNodes">500</attribute>

  28. <attributename="timeToIdleSeconds">5000</attribute>

  29. </region>

  30. <!-- 配置Category类的items集合的数据过期策略 -->

  31. <regionname="/mypack/Category/items">

  32. <attributename="maxNodes">5000</attribute>

  33. <attributename="timeToIdleSeconds">1800</attribute>

  34. </region>

  35. </config>

  36. </attribute>

  37. <!-- 配置JGroup -->

  38. <attributename="ClusterConfig">

  39. <config>

  40. <UDPbind_addr="202.145.1.2"ip_mcast="true"loopback="false"/>

  41. <PINGtimeout="2000"num_initial_members="3"up_thread="false"down_thread="false"/>

  42. <FD_SOCK/>

  43. <pbcast.NAKACKgc_lag="50"retransmit_timeout="600,1200,2400,4800"max_xmit_size="8192"up_thread="false"down_thread="false"/>

  44. <UNICASTtimeout="600,1200,2400"window_size="100"min_threshold="10"down_thread="false"/>

  45. <pbcast.STABLEdesired_avg_gossip="20000"up_thread="false"down_thread="false"/>

  46. <FRAGfrag_size="8192"down_thread="false"up_thread="false"/>

  47. <pbcast.GMSjoin_timeout="5000"join_retry_timeout="2000"shun="true"print_local_addr="true"/>

  48. <pbcast.STATE_TRANSFERup_thread="true"down_thread="true"/>

  49. </config>

  50. </attribute>

  51. </mbean>

  52. </server>



以上配置文件把JBossCache配置为JBoss的一个JMX服务,此外还配置了JGroup,它是一个通信库。JBoss为JBossCache提供了几种实现,hibernate采用的是TreeCache实现。treecache.xml文件夹的开头几行是JBoss的JMX服务的发布描述符,如果TreeCache不运行在JBoss服务器中,这几行会被忽略。

  TreeCache采用内置的org.jboss.cache.eviction.LRUPolicy策略,它是一种控制缓存中的数据过期的策略,由于当一个对象过期后,就会从缓存中清除,因此数据过期策略也叫做数据清除策略。接下来配置了Category类和它的items集合设置了具体的数据过期策略。

  最后配置了JGroup,它包含一系列通信协议,这些通信协议的次序很重要,不能随意修改它们。第一个协议为UDP,它和一个IP地址202.145.1.1绑定,这是当前节点的IP地址,UDP协议使得该节点支持广播通信,如果节点选用的是微软的windows平台,必须把loopback属性设为true。其它的JGroup属性很复杂,它们主要用于管理群集中节点之间的通信,想进一步了解,可以参考JBoss网站上的JGroup文档。

  可以按照同样的方式配置node B节点的treecache.xml文件,只需修改其中UDP协议的IP绑定地址。

  通过以上配置,hibernate将启用群集范围内的事务型缓存,每当一个新的元素加入到一个节点的缓存中时,这个元素就会被复制到其它节点的缓存中,如果缓存中的一个元素被更新,那么它就会过期,并从缓存中清除。

只有在hibernate的配置文件或映射文件中为一个持久化类设置了二级缓存,hibernate在加载这个类的实例时才会启用二级缓存。


SessionFactory 也提供了evict() 方法用于从二级缓存中清除对象的散装数据,如:

Java代码   收藏代码
  1. sessionFactory.evict(Category.class, 1);//清除二级缓存中OID为1的Category对象

  2. sessionFactory.evict("mypack.Category");//清除二级缓存中所有的Category对象

  3. sessionFactory.evictCollection("mypack.Category.items");//清除二级缓存中Category类的所有对象的items集合


下面是比较hibernate的一级缓存和二级缓存

98fa1692-83fa-32d7-91eb-88ab0e176728.jpg