Hibernate中的事务管理以及缓存管理<?xml:namespace prefix = o ns = "urn:schemas-microsoft-com:office:office" />
l事务的概念(前面已经讲述)
lHibernate中的Session缓存(前面已经讲述)
lHibernate中的二级缓存以及二级缓存的缓存策略
lQuery查询缓存
lQuery.list()、iterator()深入
l悲观锁定(PessimisticLocking)
l乐观锁定(OptimisticLocking)
二级缓存
前面,我们讲述过Session级别的缓存。
Session级别的缓存,它的生命周期与Session的生命周期一致。当Session关闭时,其缓存将失效。
这里,进一步讨论Hibernate提供的另一种缓存机制-二级缓存,它比Session级别的缓存更高一级,是属于SessionFactory级别的缓存,该缓存将被属于同一个SessionFactory级别的所有Session共享。
二级缓存为每个类(或集合),配置集群、或JVM级别(SessionFactory级别)的缓存。你甚至可以为之插入一个集群的缓存。注意,缓存永远不知道其他应用程序对持久化仓库(数据库)可能进行的修改(即使可以将缓存数据设定为定期失效)。
Hibernate的Sessionlevel缓存随着Session生命周期起始与消灭。
以第一个Hibernate中的范例来说,在未使用二级缓存的情况下,如果使用以下的程序片段来查询数据:
Sessionsession=sessionFactory.openSession();
Useruser1=(User)session.load(User.class,newInteger(1));
user1.getName();
session.close();
session=sessionFactory.openSession();
Useruser2=(User)session.load(User.class,newInteger(1));
user2.getName();
session.close();
则Hibernate将会使用以下的SQL来进行数据查询:
Hibernate:selectuser0_.idasid0_,user0_.nameasname0_0_,user0_.ageasage0_0_fromuseruser0_whereuser0_.id=?
Hibernate:selectuser0_.idasid0_,user0_.nameasname0_0_,user0_.ageasage0_0_fromuseruser0_whereuser0_.id=?
由于Session被关闭,Sessionlevel无法起作用,所以第二次的查询仍必须向数据库直接查询。
Hibernate二级缓存可以跨越数个Session,二级缓存由同一个SessionFactory所建立的Session所共享,因而又称为SessionFactorylevel缓存。
Hibernate本身并未提供二级缓存的实现,而是藉由第三方(Third-party)产品来实现,Hibernate预设使用EHCache作为其二级缓存的实现,在最简单的情况下,您只需在Hibernate下撰写一个ehcache.xml作为EHCache的资源定义档,可以在Hibernate下载档案中的etc目录下找到一个已经撰写好的ehcache.xml,以下撰写最简单的ehcache.xml:
ehcache.xml
<ehcache>
<diskStorepath="java.io.tmpdir"/>
<defaultCache
maxElementsInMemory="10000"
eternal="false"
timeToIdleSeconds="120"
timeToLiveSeconds="120"
overflowToDisk="true"
/>
</ehcache>
将这个文件放在Hibernate项目Classpath可存取到的路径下,接着重新运行上面的程序片段,您可以发现Hibernate将使用以下的SQL进行查询:
Hibernate:selectuser0_.idasid0_,user0_.nameasname0_0_,user0_.ageasage0_0_fromuseruser0_whereuser0_.id=?
二级缓存被同一个SessionFactory所建立的Session实例所共享,所以即使关闭了Session,下一个Session仍可使用二级缓存,在查询时,Session会先在Sessionlevel缓存中查询看有无数据,如果没有就试着从二级缓存中查询数据,查到数据的话就直接返回该条数据,所以在上例中,第二次无需再向数据库进行SQL查询。
如果打算清除二级缓存的资料,可以使用SessionFactory的evict()方法,例如:
sessionFactory.evict(User.class,user.getId());
如果打算在Hibernate中使用其它第三方产品进行缓存,则可以在hibernate.cfg.xml中定义hibernate.cache.provider_class属性,例如:
hibernate.cfg.xml
<hibernate-configuration>
<session-factory>
....
<propertyname="hibernate.cache.provider_class">
org.hibernate.cache.HashtableCacheProvider
</property>
....
</session-factory>
</hibernate-configuration>
通过在hibernate.cache.provider_class属性中指定org.hibernate.cache.CacheProvider的某个实现的类名,你可以选择让Hibernate使用哪个缓存实现。Hibernate打包一些开源缓存实现,提供对它们的内置支持(见下表)。除此之外,你也可以实现你自己的实现,将它们插入到系统中。注意,在3.2版本之前,默认使用EhCache作为缓存实现,但从3.2起就不再这样了。
HashtableCache是Hibernate自己所提供的二级缓存实现,不过性能与功能上有限,只用于开发时期的测试之用。
可以在映射文件中指定缓存策略,使用<cache>卷标在映像实体或Collection上设定缓存策略,例如:
User.hbm.xml
<hibernate-mapping>
<classname=“com.cstp.User"table="user">
<cacheusage="read-only"/>
<idname="id"column="id"type="java.lang.Integer">
<generatorclass="native"/>
</id>
<propertyname="name"column="name"type="java.lang.String"/>
<propertyname="age"column="age"type="java.lang.Integer"/>
</class>
</hibernate-mapping>
类或者集合映射的“<cache>元素”可以有下列形式:
<cache
usage="transactional|read-write|nonstrict-read-write|read-only"
region="RegionName"
include="all|non-lazy"
/>
usage(必须)说明了缓存的策略:transactional、read-write、nonstrict-read-write或read-only。
region(可选,默认为类或者集合的名字(classorcollectionrolename))指定第二级缓存的区域名(nameofthesecondlevelcacheregion)
include(可选,默认为all)non-lazy当属性级延迟抓取打开时,标记为lazy="true"的实体的属性可能无法被缓存
另外(首选?),你可以在hibernate.cfg.xml中指定<class-cache>和<collection-cache>元素。
这里的usage属性指明了缓存并发策略(cacheconcurrencystrategy)。
可以设定的策略包括read-only、read-write、nonstrict-read-write与transactional,并不是每一个第三方缓存实现都支持所有的选项,每一个选项的使用时机与支持的产品,可以直接参考Hibernate官方参考手册的20.2.TheSecondLevelCache
只读缓存(Strategy:readonly)
如果你的应用程序只需读取一个持久化类的实例,而无需对其修改,那么就可以对其进行只读缓存。这是最简单,也是实用性最好的方法。甚至在集群中,它也能完美地运作。
<classname="eg.Immutable"mutable="false">
<cacheusage="read-only"/>
....
</class>
读/写缓存(Strategy:read/write)
如果应用程序需要更新数据,那么使用读/写缓存比较合适。如果应用程序要求“序列化事务”的隔离级别(serializabletransactionisolationlevel),那么就决不能使用这种缓存策略。如果在JTA环境中使用缓存,你必须指定hibernate.transaction.manager_lookup_class属性的值,通过它,Hibernate才能知道该应用程序中JTA的TransactionManager的具体策略。在其它环境中,你必须保证在Session.close()、或Session.disconnect()调用前,整个事务已经结束。如果你想在集群环境中使用此策略,你必须保证底层的缓存实现支持锁定(locking)。Hibernate内置的缓存策略并不支持锁定功能。
<classname="eg.Cat"....>
<cacheusage="read-write"/>
....
<setname="kittens"...>
<cacheusage="read-write"/>
....
</set>
</class>
非严格读/写缓存(Strategy:nonstrictread/write)
如果应用程序只偶尔需要更新数据(也就是说,两个事务同时更新同一记录的情况很不常见),也不需要十分严格的事务隔离,那么比较适合使用非严格读/写缓存策略。如果在JTA环境中使用该策略,你必须为其指定hibernate.transaction.manager_lookup_class属性的值,在其它环境中,你必须保证在Session.close()、或Session.disconnect()调用前,整个事务已经结束。
事务缓存(transactional)
Hibernate的事务缓存策略提供了全事务的缓存支持,例如对JBossTreeCache的支持。这样的缓存只能用于JTA环境中,你必须指定为其hibernate.transaction.manager_lookup_class属性。
没有一种缓存提供商能够支持上列的所有缓存并发策略。下表中列出了各种提供器、及其各自适用的并发策略。
Query查询缓存
Hibernate的Sessionlevel缓存会在使用Session的load()方法时起作用,在设定条件进行查询时,无法使用缓存的功能,现在考虑一种情况,您的数据库表格中的数据很少变动,在使用Query查询数据时,如果表格内容没有变动,您希望能重用上一次查询的结果,除非表格内容有变动才向数据库查询。
您可以开启Query的缓存功能,因为要使用Query的缓存功能必须在两次查询时所使用的SQL相同,且两次查询之间表格没有任何数据变动下才有意义,所以Hibernate预设是关闭这个功能的,如果您觉得符合这两个条件,那么可以试着开启Query缓存功能来看看效能上有无改进。
先来看看下面的查询程序片段:
Sessionsession=sessionFactory.openSession();
Stringhql="fromUser";
Queryquery=session.createQuery(hql);
Listusers=query.list();
for(inti=0;i<users.size();i++){
Useruser=(User)users.get(i);
System.out.println(user.getName());
}
query=session.createQuery(hql);
users=query.list();
for(inti=0;i<users.size();i++){
Useruser=(User)users.get(i);
System.out.println(user.getName());
}
session.close();
在不启用Query缓存的情况下,Hibernate会使用两次SQL向数据库查询数据:
Hibernate:selectuser0_.idasid,user0_.nameasname0_,user0_.ageasage0_fromuseruser0_
zhangy
hhp
Hibernate:selectuser0_.idasid,user0_.nameasname0_,user0_.ageasage0_fromuseruser0_
zhangy
hhp
如果打算启用Query缓存功能,首先在hibernate.cfg.xml中设定hibernate.cache.use_query_cache属性:
hibernate.cfg.xml
<hibernate-configuration>
<session-factory>
....
<propertyname="hibernate.cache.use_query_cache">true</property>
....
</session-factory>
</hibernate-configuration>
然后在每次建立Query实例时,执行setCacheable(true):
Sessionsession=sessionFactory.openSession();
Stringhql="fromUser";
Queryquery=session.createQuery(hql);
//使用Query缓存
query.setCacheable(true);
Listusers=query.list();
for(inti=0;i<users.size();i++){
Useruser=(User)users.get(i);
System.out.println(user.getName());
}
query=session.createQuery(hql);
//使用Query缓存
query.setCacheable(true);
users=query.list();
for(inti=0;i<users.size();i++){
Useruser=(User)users.get(i);
System.out.println(user.getName());
}
session.close();
Hibernate在启用Query缓存后,会保留执行过的查询SQL与查询结果,在下一次查询时会看看SQL是否相同,并看看对应的数据库表格是否有变动(Update/Delete/Insert),如果SQL相同且数据库也没有变动,则将Query缓存中的查询结果返回,上面的程序片段将使用一次SQL查询,第二次查询时直接返回缓存中的结果.
Query上有list()与iterate()方法,两者的差别在于开启Query缓存之后,list()方法在读取数据时,会利用到Query缓存,而iterate()则不会使用到Query缓存功能,而是直接从数据库中再查询数据。
来看看下面的程序:
Sessionsession=sessionFactory.openSession();
Queryquery=session.createQuery("fromUser");
query.setCacheable(true);