Hibernate 学习笔记(6)

本文探讨了Hibernate框架中的缓存管理和事务处理机制,详细介绍了Session级别的缓存、二级缓存的不同实现方式及其配置方法,同时还讲解了如何利用Query缓存优化查询性能。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

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);

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值