- 浏览: 93419 次
- 性别:
- 来自: baga
最新评论
-
nicegege:
getContextPath、getServletPath、getRequestURI的区别 -
w156445045:
请问下博主,现在最新的iReport也不能调用存储过程嘛?
i ...
用ireport调用oracle存储过程 -
how:
function传入参数可以吗?
用ireport调用oracle存储过程 -
axxxx2000:
一个事物一个版本记录下
求教,上海怎么拿到7k以上薪水 -
holan:
axxxx2000 写道wl95421 写道今天刚面试完人,随 ...
求教,上海怎么拿到7k以上薪水
由于本人也是边翻边学,有什么翻译不当的地方在所难免,欢迎大家批评指正
原文题目:Speed Up Your Hibernate Application with Second-Level Caching
原文来源:http://www.devx.com/dbzone/Article/29685/1954
作者简介:John Ferguson Smart,参与过很多企业和政府大型的的J2EE项目,他的专长包括J2EE的架构,开发和IT项目管理。他也有很多的在JAVA的开源技术方面的经验。这是他技术blog的链接www.jroller.com/page/wakaleo
原文翻译如下:
通过二级缓存来加快你的hibernate应用程序
新的hibernate开发人员有时并不知道hibernate的缓存结果并没有充分的使用它,尽管如此,当我们正确使用缓存的时候,它能够变为加速hibernate应用程序最有力的武器之一。
在web应用程序中和大数据量的数据库交互经常导致性能问题。hibernate是一种高性能的,提供对象/关系持久化和查询的服务,但是如果没有帮助就不会解决所有性能上的问题。在很多情况下,二级缓存就是hibernate潜在的需要来实现所有的性能上的处理。这篇文章将研究hibernate的缓存功能并且展现怎么使用才能明显的提升应用程序的性能。
缓存介绍
缓存被广泛用于数据库应用领域。缓存的设计就是为了通过存储已经从数据库读取的数据来减少应用程序和数据库之间的数据流量,而数据库的访问只在检索的数据不在当前缓存的时候才需要。如果数据库以一些方式进行更新和修改的话,那么应用程序可能需要每隔一段时间清空缓存,因为它没有方法知道缓存里的数据是不是最新的。
Hibernate缓存
从对象来说,hibernate用了两种缓存的方法:一级缓存和二级缓存。一级缓存和Session对象关联,二级缓存和Session Factory对象关联。默认情况下,hibernate使用一级缓存来作为一次事务预处理的基础。hibernate使用它主要是为了减少在一次给定的事务中需要被生成的SQL查询语句。例如,一个对象在同一个事务中被修改好几次,hibernate会在事务结束的时候只生成一条SQL更新语句,它包含了所有修改内容。这篇文章主要介绍二级缓存。为了减少和数据库的交互,二级缓存保存已经读出的对象在各个事务之间的Session Factory级别。这些对象在整个程序中都可以得到,而不仅是用户运行查询的时候。这种方式,每次查询返回的对象都已经被载入了缓存,一次或多次潜在的数据库事务可以避免。
除此之外,你可以使用查询级别缓存如果你需要缓存的实际的查询结果,而不仅仅是持久化对象。
缓存的实现
缓存是软件中复杂的部分,市场上也提供了相当数量的选择,包括基于开源的和商业的。hibernate提供了以下开源的缓存实现,如下:
* EHCache (org.hibernate.cache.EhCacheProvider)
* OSCache (org.hibernate.cache.OSCacheProvider)
* SwarmCache (org.hibernate.cache.SwarmCacheProvider)
* JBoss TreeCache (org.hibernate.cache.TreeCacheProvider)
不同的缓存提供了在性能,内存使用和配置的可扩展性方面不同的能力
EHCache是一种快速的,轻量级的,容易上手的缓存。它支持只读和读写缓存,基于内存和硬盘的数据储存。但是,它不支持簇。
OSCache是另一个开源的缓存解决方案,它是一个也为jsp页面和任意对象提供缓存功能的大的开发包的一部分。它本身也是一个强大和灵活的开发包,像EHCache一样,也支持只读和读写缓存,基于内存和硬盘的数据储存。它通过JavaGroups或者JMS也提供了对簇的基本支持。
SwarmCache是一种基于簇的解决方案,它本身也基于集群服务实体间通信的通信协议。它提供了只读和没有限制的读写缓存(下一部分将解释这个名词)。这种类型的缓存对那些典型的读操作比写操作多的多的应用程序是适合的。
JBoss TreeCache是一种强大的两重性(同步的或异步的)和事务性的缓存。使用此实现如果你确实需要一种事务能力的缓存架构。
另一种值得提及的缓存实现是商业化的Tangosol Coherence cache。
缓存策略
一旦你选择了你的缓存实现,你需要指定你的缓存策略。以下是四种缓存策略:
只读:这种策略对那种数据被频繁读取但是不更新是有效的,这是到目前为止最简单,表现最好的缓存策略。
读写:读写缓存对假设你的数据需要更新是必要的。但是读写需要比只读缓存花费更多的资源。在非JTA的事务环境中,每一个事务需要完成在Session.close() 或Session.disconnect()被调用的时候。
没有限制的读写:这个策略不能保证2个事务不会同时的修改相同的数据。因此,它可能对那些数据经常被读取但只会偶尔进行写操作的最适合。
事务性:这是个完全事务性的缓存,它可能只能被用在JTA环境中。
对每一个缓存实现来说,支持策略不是唯一的。图一展现了对
缓存实现可供的选择。
文章余下的部分用来展示一个使用EHCache单一的JVM缓存。
缓存配置
为了启用二级缓存,我们需要在hibernate.cfg.xml配置文件中定义hibernate.cache.provider_class属性,如下
为了在hibernate3中测试的目的,你还要使用hibernate.cache.use_second_level_cache属性,它可以让你启用(和关闭)二级缓存。默认情况下,二级缓存是启用的同时使用EHCache。
一个实际应用
这个例子演示的程序包含四张简单的表:国家列表,机场列表,员工列表,语言列表。每个员工被分配了一个国家,并且能说很多语言。每个国家能有任意数量的机场。
图一展现了UML类图
图二展现了数据库架构
这个例子的源代码(http://assets.devx.com/sourcecode/14239.tgz)包括了以下的SQL脚本,你需要用它创建和实例化数据库。
* src/sql/create.sql: 创建数据库的SQL脚本
* src/sql/init.sql: 测试数据
安装Maven2
在写的时候,Maven2安装目录看来好像缺少了一些jars。为了解决这个问题,在应用程序源码的根目录里找到那些缺少的jars。把它们安装到Maven2的文件里,到应用程序的目录下,执行以下的命令
$ mvn install:install-file -DgroupId=javax.security -DartifactId=jacc
-Dversion=1.0
-Dpackaging=jar -Dfile=jacc-1.0.jar
$ mvn install:install-file -DgroupId=javax.transaction -DartifactId=jta -Dversion=1.0.1B
-Dpackaging=jar -Dfile=jta-1.0.1B.jar
建立一个只读缓存
从简单的开始,下面是country类的hibernate映射。
假设你要显示国家列表。你可以通过CountryDAO类中一个简单的方法实现,如下
因为这个方法经常被调用,所以你要知道它在压力下的行为。写一个简单的单元测试来模拟5次连续的调用。
你能够运行这个测试通过自己喜欢的IDE或者Maven2的命令行(演示程序提供了2个Maven2的工程文件)。这个演示程序通过本地的mysql来测试。当你运行这个测试的时候,应该得到类似以下的一些信息:
$mvn test -Dtest=CountryDAOTest
...
testGetCountries: 521 ms.
testGetCountries: 357 ms.
testGetCountries: 249 ms.
testGetCountries: 257 ms.
testGetCountries: 355 ms.
[surefire] Running com.wakaleo.articles.caching.dao.CountryDAOTest
[surefire] Tests run: 1, Failures: 0, Errors: 0, Time elapsed: 3,504 sec
可以看出每次调用大概花费半秒的时间,对大多数标准来说还是有点迟缓的。国家的列表很可能不是经常的改变,所以这个类可以作为只读缓存一个好的候选。所以加上去
你可以启用二级缓存用以下两种方法的任意一种
1.你可以在*.hbm.xml里启用它,使用cache的属性
2.你可以存储所有缓存信息在hibernate.cfg.xml文件中,使用class-cache属性
下一步,你需要为这个类设置缓存规则,这些规则决定了缓存怎么表现的细节。这个例子的演示是使用EHCache,但是记住每一种缓存实现是不一样的。
EHCache需要一个配置文件(通常叫做ehcache.xml)在类的根目录。EHCache配置文件的详细文档可以看这里(http://ehcache.sourceforge.net/documentation)。基本上,你要为每个需要缓存的类定义规则,以及一个defaultCache在你没有明确指明任何规则给一个类的时候使用。
对第一个例子来说,你可以使用下面简单的EHCache配置文件
这个文件为countries类建立一个基于内存最多300单位的缓存(countries类包含了229个国家)。注意缓存不会过期('eternal=true'属性)。现在通过返回的结果看下缓存的表现
$mvn test -Dtest=CompanyDAOTest
...
testGetCountries: 412 ms.
testGetCountries: 98 ms.
testGetCountries: 92 ms.
testGetCountries: 82 ms.
testGetCountries: 93 ms.
[surefire] Running com.wakaleo.articles.caching.dao.CountryDAOTest
[surefire] Tests run: 1, Failures: 0, Errors: 0, Time elapsed: 2,823 sec
正如你期盼的那样,第一次查询没有改变因为需要加载数据。但是,随后的几次查询就快多了。
后台
在我们继续之前,看下后台发生了什么非常有用。一件事情你需要知道的是hibernate缓存不储存对象实例,代替的是它储存对象“脱水”的形式(hibernate的术语),也就是作为一系列属性值。以下是一个countries缓存例子的内容
{
30 => [bw,Botswana,30],
214 => [uy,Uruguay,214],
158 => [pa,Panama,158],
31 => [by,Belarus,31]
95 => [in,India,95]
...
}
注意每个ID是怎么样映射到拥有属性值的数组的。你可能也注意到了只有主要的属性被储存了,而没有airports属性,这是因为airports属性只是一个关联:对其他持久化对象一系列的引用。
默认情况下,hibernate不缓存关联。而由你决定来缓存哪个关联,哪个关联需要被重载当缓存对象从二级缓存获得的时候。
关联缓存是一个非常强大的功能。下一部分我们将介绍更多的内容。
和关联缓存一起工作
假设你需要显示一个给定的国家的所有的员工(包括员工的名字,使用的语言等)。以下是employee类的hibernate映射
假设你每次使用employee对象的时候需要得到一个员工会说的语言。强逼hibernate自动载入关联的languages集合,你设置了lazy属性为false()。你还需要一个DAO类来得到所有employee,以下的代码来帮你实现
下一步,写一些简单的单元测试来看它怎么表现。正如前面的例子一样,你需要知道它被重复调用时候的性能
如果你运行上面的代码,你会得到类似以下的一些数据
$mvn test -Dtest=EmployeeDAOTest
...
testGetNZEmployees: 1227 ms.
testGetAUEmployees: 883 ms.
testGetNZEmployees: 907 ms.
testGetAUEmployees: 873 ms.
testGetNZEmployees: 987 ms.
testGetAUEmployees: 916 ms.
[surefire] Running com.wakaleo.articles.caching.dao.EmployeeDAOTest
[surefire] Tests run: 3, Failures: 0, Errors: 0, Time elapsed: 3,684 sec
所以对一个国家载入大约50个员工需要花费大约一秒的时间。这种方法显然太慢了。这是典型的N+1的查询问题。如果你启用SQL日志,你会发现对employee表的一次查询,紧跟着对language表几百次的查询,无论什么时候hibernate从缓存里得到一个employee对象,它都会重载所有关联的language。那怎么提升它的性能呢?第一件要做的事就是对employee启用读写缓存,如下
你还应该对language类启用缓存。只读缓存如下
然后你需要配置缓存的规则通过加入以下的内容到ehcache.xml文件中
但是还是没有解决N+1的查询问题:当你载入一个employee对象的时候大约50次的额外查询还是会执行。这里你就需要在Employee.hbm.xml映射文件里关联的language启用缓存,如下
通过这个配置,你就能得到几近最优的性能
$mvn test -Dtest=EmployeeDAOTest
...
testGetNZEmployees: 1477 ms.
testGetAUEmployees: 940 ms.
testGetNZEmployees: 65 ms.
testGetAUEmployees: 65 ms.
testGetNZEmployees: 76 ms.
testGetAUEmployees: 52 ms.
[surefire] Running com.wakaleo.articles.caching.dao.EmployeeDAOTest
[surefire] Tests run: 3, Failures: 0, Errors: 0, Time elapsed: 0,228 sec
查询缓存
在确定的情况下,缓存一次查询的正确结果是非常有用的,不仅是只是个确定的对象。例如,getCountries()方法在每次调用的时候或许会返回相同的国家列表。所以,除了缓存country类之外,你也应该缓存查询结果本身。
为了实现这个目标,你需要在hibernate.cfg.xml文件中设置hibernate.cache.use_query_cache属性为true,如下
<property name="hibernate.cache.use_query_cache">true</property>
然后需要在对任何查询缓存的时候使用setCacheable()方法,如下
但是,它不能预知到其他程序对数据库任何的改变。所以你不应该使用任何二级缓存(或为类和集合的缓存设置简短的过期时间)如果你的数据总是要保证最新的状态。
正确的使用hibernate缓存
缓存是一种强大的技术,hibernate提供了一种强大的,灵活的,不显眼的方式来实现它。即使对很多简单的例子来说默认的设置能使实际的性能得到提升。但是,像很多强大工具一样,hibernate还是需要一些思考和微调来得到最优的结果,而缓存像其他的优化技术一样,应该使用一种可扩展,测试驱动的方法所实现。当正确使用的时候,少量的缓存实现,能最大程度的提高你的程序运行。
原文题目:Speed Up Your Hibernate Application with Second-Level Caching
原文来源:http://www.devx.com/dbzone/Article/29685/1954
作者简介:John Ferguson Smart,参与过很多企业和政府大型的的J2EE项目,他的专长包括J2EE的架构,开发和IT项目管理。他也有很多的在JAVA的开源技术方面的经验。这是他技术blog的链接www.jroller.com/page/wakaleo
原文翻译如下:
通过二级缓存来加快你的hibernate应用程序
新的hibernate开发人员有时并不知道hibernate的缓存结果并没有充分的使用它,尽管如此,当我们正确使用缓存的时候,它能够变为加速hibernate应用程序最有力的武器之一。
在web应用程序中和大数据量的数据库交互经常导致性能问题。hibernate是一种高性能的,提供对象/关系持久化和查询的服务,但是如果没有帮助就不会解决所有性能上的问题。在很多情况下,二级缓存就是hibernate潜在的需要来实现所有的性能上的处理。这篇文章将研究hibernate的缓存功能并且展现怎么使用才能明显的提升应用程序的性能。
缓存介绍
缓存被广泛用于数据库应用领域。缓存的设计就是为了通过存储已经从数据库读取的数据来减少应用程序和数据库之间的数据流量,而数据库的访问只在检索的数据不在当前缓存的时候才需要。如果数据库以一些方式进行更新和修改的话,那么应用程序可能需要每隔一段时间清空缓存,因为它没有方法知道缓存里的数据是不是最新的。
Hibernate缓存
从对象来说,hibernate用了两种缓存的方法:一级缓存和二级缓存。一级缓存和Session对象关联,二级缓存和Session Factory对象关联。默认情况下,hibernate使用一级缓存来作为一次事务预处理的基础。hibernate使用它主要是为了减少在一次给定的事务中需要被生成的SQL查询语句。例如,一个对象在同一个事务中被修改好几次,hibernate会在事务结束的时候只生成一条SQL更新语句,它包含了所有修改内容。这篇文章主要介绍二级缓存。为了减少和数据库的交互,二级缓存保存已经读出的对象在各个事务之间的Session Factory级别。这些对象在整个程序中都可以得到,而不仅是用户运行查询的时候。这种方式,每次查询返回的对象都已经被载入了缓存,一次或多次潜在的数据库事务可以避免。
除此之外,你可以使用查询级别缓存如果你需要缓存的实际的查询结果,而不仅仅是持久化对象。
缓存的实现
缓存是软件中复杂的部分,市场上也提供了相当数量的选择,包括基于开源的和商业的。hibernate提供了以下开源的缓存实现,如下:
* EHCache (org.hibernate.cache.EhCacheProvider)
* OSCache (org.hibernate.cache.OSCacheProvider)
* SwarmCache (org.hibernate.cache.SwarmCacheProvider)
* JBoss TreeCache (org.hibernate.cache.TreeCacheProvider)
不同的缓存提供了在性能,内存使用和配置的可扩展性方面不同的能力
EHCache是一种快速的,轻量级的,容易上手的缓存。它支持只读和读写缓存,基于内存和硬盘的数据储存。但是,它不支持簇。
OSCache是另一个开源的缓存解决方案,它是一个也为jsp页面和任意对象提供缓存功能的大的开发包的一部分。它本身也是一个强大和灵活的开发包,像EHCache一样,也支持只读和读写缓存,基于内存和硬盘的数据储存。它通过JavaGroups或者JMS也提供了对簇的基本支持。
SwarmCache是一种基于簇的解决方案,它本身也基于集群服务实体间通信的通信协议。它提供了只读和没有限制的读写缓存(下一部分将解释这个名词)。这种类型的缓存对那些典型的读操作比写操作多的多的应用程序是适合的。
JBoss TreeCache是一种强大的两重性(同步的或异步的)和事务性的缓存。使用此实现如果你确实需要一种事务能力的缓存架构。
另一种值得提及的缓存实现是商业化的Tangosol Coherence cache。
缓存策略
一旦你选择了你的缓存实现,你需要指定你的缓存策略。以下是四种缓存策略:
只读:这种策略对那种数据被频繁读取但是不更新是有效的,这是到目前为止最简单,表现最好的缓存策略。
读写:读写缓存对假设你的数据需要更新是必要的。但是读写需要比只读缓存花费更多的资源。在非JTA的事务环境中,每一个事务需要完成在Session.close() 或Session.disconnect()被调用的时候。
没有限制的读写:这个策略不能保证2个事务不会同时的修改相同的数据。因此,它可能对那些数据经常被读取但只会偶尔进行写操作的最适合。
事务性:这是个完全事务性的缓存,它可能只能被用在JTA环境中。
对每一个缓存实现来说,支持策略不是唯一的。图一展现了对
缓存实现可供的选择。
文章余下的部分用来展示一个使用EHCache单一的JVM缓存。
缓存配置
为了启用二级缓存,我们需要在hibernate.cfg.xml配置文件中定义hibernate.cache.provider_class属性,如下
<hibernate-configuration> <session-factory> ... <property name="hibernate.cache.provider_class"> org.hibernate.cache.EHCacheProvider </property> ... </session-factory> </hibernate-configuration>
为了在hibernate3中测试的目的,你还要使用hibernate.cache.use_second_level_cache属性,它可以让你启用(和关闭)二级缓存。默认情况下,二级缓存是启用的同时使用EHCache。
一个实际应用
这个例子演示的程序包含四张简单的表:国家列表,机场列表,员工列表,语言列表。每个员工被分配了一个国家,并且能说很多语言。每个国家能有任意数量的机场。
图一展现了UML类图
图二展现了数据库架构
这个例子的源代码(http://assets.devx.com/sourcecode/14239.tgz)包括了以下的SQL脚本,你需要用它创建和实例化数据库。
* src/sql/create.sql: 创建数据库的SQL脚本
* src/sql/init.sql: 测试数据
安装Maven2
在写的时候,Maven2安装目录看来好像缺少了一些jars。为了解决这个问题,在应用程序源码的根目录里找到那些缺少的jars。把它们安装到Maven2的文件里,到应用程序的目录下,执行以下的命令
$ mvn install:install-file -DgroupId=javax.security -DartifactId=jacc
-Dversion=1.0
-Dpackaging=jar -Dfile=jacc-1.0.jar
$ mvn install:install-file -DgroupId=javax.transaction -DartifactId=jta -Dversion=1.0.1B
-Dpackaging=jar -Dfile=jta-1.0.1B.jar
建立一个只读缓存
从简单的开始,下面是country类的hibernate映射。
<hibernate-mapping package="com.wakaleo.articles.caching.businessobjects"> <class name="Country" table="COUNTRY" dynamic-update="true"> <meta attribute="implement-equals">true</meta> <cache usage="read-only"/> <id name="id" type="long" unsaved-value="null" > <column name="cn_id" not-null="true"/> <generator class="increment"/> </id> <property column="cn_code" name="code" type="string"/> <property column="cn_name" name="name" type="string"/> <set name="airports"> <key column="cn_id"/> <one-to-many class="Airport"/> </set> </class> </hibernate-mapping>
假设你要显示国家列表。你可以通过CountryDAO类中一个简单的方法实现,如下
public class CountryDAO { ... public List getCountries() { return SessionManager.currentSession() .createQuery( "from Country as c order by c.name") .list(); } }
因为这个方法经常被调用,所以你要知道它在压力下的行为。写一个简单的单元测试来模拟5次连续的调用。
public void testGetCountries() { CountryDAO dao = new CountryDAO(); for(int i = 1; i <= 5; i++) { Transaction tx = SessionManager.getSession().beginTransaction(); TestTimer timer = new TestTimer("testGetCountries"); List countries = dao.getCountries(); tx.commit(); SessionManager.closeSession(); timer.done(); assertNotNull(countries); assertEquals(countries.size(),229); } }
你能够运行这个测试通过自己喜欢的IDE或者Maven2的命令行(演示程序提供了2个Maven2的工程文件)。这个演示程序通过本地的mysql来测试。当你运行这个测试的时候,应该得到类似以下的一些信息:
$mvn test -Dtest=CountryDAOTest
...
testGetCountries: 521 ms.
testGetCountries: 357 ms.
testGetCountries: 249 ms.
testGetCountries: 257 ms.
testGetCountries: 355 ms.
[surefire] Running com.wakaleo.articles.caching.dao.CountryDAOTest
[surefire] Tests run: 1, Failures: 0, Errors: 0, Time elapsed: 3,504 sec
可以看出每次调用大概花费半秒的时间,对大多数标准来说还是有点迟缓的。国家的列表很可能不是经常的改变,所以这个类可以作为只读缓存一个好的候选。所以加上去
你可以启用二级缓存用以下两种方法的任意一种
1.你可以在*.hbm.xml里启用它,使用cache的属性
<hibernate-mapping package="com.wakaleo.articles.caching.businessobjects"> <class name="Country" table="COUNTRY" dynamic-update="true"> <meta attribute="implement-equals">true</meta> <cache usage="read-only"/> ... </class> </hibernate-mapping>
2.你可以存储所有缓存信息在hibernate.cfg.xml文件中,使用class-cache属性
<hibernate-configuration> <session-factory> ... <property name="hibernate.cache.provider_class"> org.hibernate.cache.EHCacheProvider </property> ... <class-cache class="com.wakaleo.articles.caching.businessobjects.Country" usage="read-only" /> </session-factory> </hibernate-configuration>
下一步,你需要为这个类设置缓存规则,这些规则决定了缓存怎么表现的细节。这个例子的演示是使用EHCache,但是记住每一种缓存实现是不一样的。
EHCache需要一个配置文件(通常叫做ehcache.xml)在类的根目录。EHCache配置文件的详细文档可以看这里(http://ehcache.sourceforge.net/documentation)。基本上,你要为每个需要缓存的类定义规则,以及一个defaultCache在你没有明确指明任何规则给一个类的时候使用。
对第一个例子来说,你可以使用下面简单的EHCache配置文件
<ehcache> <diskStore path="java.io.tmpdir"/> <defaultCache maxElementsInMemory="10000" eternal="false" timeToIdleSeconds="120" timeToLiveSeconds="120" overflowToDisk="true" diskPersistent="false" diskExpiryThreadIntervalSeconds="120" memoryStoreEvictionPolicy="LRU" /> <cache name="com.wakaleo.articles.caching.businessobjects.Country" maxElementsInMemory="300" eternal="true" overflowToDisk="false" /> </ehcache>
这个文件为countries类建立一个基于内存最多300单位的缓存(countries类包含了229个国家)。注意缓存不会过期('eternal=true'属性)。现在通过返回的结果看下缓存的表现
$mvn test -Dtest=CompanyDAOTest
...
testGetCountries: 412 ms.
testGetCountries: 98 ms.
testGetCountries: 92 ms.
testGetCountries: 82 ms.
testGetCountries: 93 ms.
[surefire] Running com.wakaleo.articles.caching.dao.CountryDAOTest
[surefire] Tests run: 1, Failures: 0, Errors: 0, Time elapsed: 2,823 sec
正如你期盼的那样,第一次查询没有改变因为需要加载数据。但是,随后的几次查询就快多了。
后台
在我们继续之前,看下后台发生了什么非常有用。一件事情你需要知道的是hibernate缓存不储存对象实例,代替的是它储存对象“脱水”的形式(hibernate的术语),也就是作为一系列属性值。以下是一个countries缓存例子的内容
{
30 => [bw,Botswana,30],
214 => [uy,Uruguay,214],
158 => [pa,Panama,158],
31 => [by,Belarus,31]
95 => [in,India,95]
...
}
注意每个ID是怎么样映射到拥有属性值的数组的。你可能也注意到了只有主要的属性被储存了,而没有airports属性,这是因为airports属性只是一个关联:对其他持久化对象一系列的引用。
默认情况下,hibernate不缓存关联。而由你决定来缓存哪个关联,哪个关联需要被重载当缓存对象从二级缓存获得的时候。
关联缓存是一个非常强大的功能。下一部分我们将介绍更多的内容。
和关联缓存一起工作
假设你需要显示一个给定的国家的所有的员工(包括员工的名字,使用的语言等)。以下是employee类的hibernate映射
<hibernate-mapping package="com.wakaleo.articles.caching.businessobjects"> <class name="Employee" table="EMPLOYEE" dynamic-update="true"> <meta attribute="implement-equals">true</meta> <id name="id" type="long" unsaved-value="null" > <column name="emp_id" not-null="true"/> <generator class="increment"/> </id> <property column="emp_surname" name="surname" type="string"/> <property column="emp_firstname" name="firstname" type="string"/> <many-to-one name="country" column="cn_id" class="com.wakaleo.articles.caching.businessobjects.Country" not-null="true" /> <!-- Lazy-loading is deactivated to demonstrate caching behavior --> <set name="languages" table="EMPLOYEE_SPEAKS_LANGUAGE" lazy="false"> <key column="emp_id"/> <many-to-many column="lan_id" class="Language"/> </set> </class> </hibernate-mapping>
假设你每次使用employee对象的时候需要得到一个员工会说的语言。强逼hibernate自动载入关联的languages集合,你设置了lazy属性为false()。你还需要一个DAO类来得到所有employee,以下的代码来帮你实现
public class EmployeeDAO { public List getEmployeesByCountry(Country country) { return SessionManager.currentSession() .createQuery( "from Employee as e where e.country = :country " + " order by e.surname, e.firstname") .setParameter("country",country) .list(); } }
下一步,写一些简单的单元测试来看它怎么表现。正如前面的例子一样,你需要知道它被重复调用时候的性能
public class EmployeeDAOTest extends TestCase { CountryDAO countryDao = new CountryDAO(); EmployeeDAO employeeDao = new EmployeeDAO(); /** * Ensure that the Hibernate session is available * to avoid the Hibernate initialisation interfering with * the benchmarks */ protected void setUp() throws Exception { super.setUp(); SessionManager.getSession(); } public void testGetNZEmployees() { TestTimer timer = new TestTimer("testGetNZEmployees"); Transaction tx = SessionManager.getSession().beginTransaction(); Country nz = countryDao.findCountryByCode("nz"); List kiwis = employeeDao.getEmployeesByCountry(nz); tx.commit(); SessionManager.closeSession(); timer.done(); } public void testGetAUEmployees() { TestTimer timer = new TestTimer("testGetAUEmployees"); Transaction tx = SessionManager.getSession().beginTransaction(); Country au = countryDao.findCountryByCode("au"); List aussis = employeeDao.getEmployeesByCountry(au); tx.commit(); SessionManager.closeSession(); timer.done(); } public void testRepeatedGetEmployees() { testGetNZEmployees(); testGetAUEmployees(); testGetNZEmployees(); testGetAUEmployees(); } }
如果你运行上面的代码,你会得到类似以下的一些数据
$mvn test -Dtest=EmployeeDAOTest
...
testGetNZEmployees: 1227 ms.
testGetAUEmployees: 883 ms.
testGetNZEmployees: 907 ms.
testGetAUEmployees: 873 ms.
testGetNZEmployees: 987 ms.
testGetAUEmployees: 916 ms.
[surefire] Running com.wakaleo.articles.caching.dao.EmployeeDAOTest
[surefire] Tests run: 3, Failures: 0, Errors: 0, Time elapsed: 3,684 sec
所以对一个国家载入大约50个员工需要花费大约一秒的时间。这种方法显然太慢了。这是典型的N+1的查询问题。如果你启用SQL日志,你会发现对employee表的一次查询,紧跟着对language表几百次的查询,无论什么时候hibernate从缓存里得到一个employee对象,它都会重载所有关联的language。那怎么提升它的性能呢?第一件要做的事就是对employee启用读写缓存,如下
<hibernate-mapping package="com.wakaleo.articles.caching.businessobjects"> <class name="Employee" table="EMPLOYEE" dynamic-update="true"> <meta attribute="implement-equals">true</meta> <cache usage="read-write"/> ... </class> </hibernate-mapping>
你还应该对language类启用缓存。只读缓存如下
<hibernate-mapping package="com.wakaleo.articles.caching.businessobjects"> <class name="Language" table="SPOKEN_LANGUAGE" dynamic-update="true"> <meta attribute="implement-equals">true</meta> <cache usage="read-only"/> ... </class> </hibernate-mapping>
然后你需要配置缓存的规则通过加入以下的内容到ehcache.xml文件中
<cache name="com.wakaleo.articles.caching.businessobjects.Employee" maxElementsInMemory="5000" eternal="false" overflowToDisk="false" timeToIdleSeconds="300" timeToLiveSeconds="600" /> <cache name="com.wakaleo.articles.caching.businessobjects.Language" maxElementsInMemory="100" eternal="true" overflowToDisk="false" />
但是还是没有解决N+1的查询问题:当你载入一个employee对象的时候大约50次的额外查询还是会执行。这里你就需要在Employee.hbm.xml映射文件里关联的language启用缓存,如下
<hibernate-mapping package="com.wakaleo.articles.caching.businessobjects"> <class name="Employee" table="EMPLOYEE" dynamic-update="true"> <meta attribute="implement-equals">true</meta> <id name="id" type="long" unsaved-value="null" > <column name="emp_id" not-null="true"/> <generator class="increment"/> </id> <property column="emp_surname" name="surname" type="string"/> <property column="emp_firstname" name="firstname" type="string"/> <many-to-one name="country" column="cn_id" class="com.wakaleo.articles.caching.businessobjects.Country" not-null="true" /> <!-- Lazy-loading is deactivated to demonstrate caching behavior --> <set name="languages" table="EMPLOYEE_SPEAKS_LANGUAGE" lazy="false"> <cache usage="read-write"/> <key column="emp_id"/> <many-to-many column="lan_id" class="Language"/> </set> </class> </hibernate-mapping>
通过这个配置,你就能得到几近最优的性能
$mvn test -Dtest=EmployeeDAOTest
...
testGetNZEmployees: 1477 ms.
testGetAUEmployees: 940 ms.
testGetNZEmployees: 65 ms.
testGetAUEmployees: 65 ms.
testGetNZEmployees: 76 ms.
testGetAUEmployees: 52 ms.
[surefire] Running com.wakaleo.articles.caching.dao.EmployeeDAOTest
[surefire] Tests run: 3, Failures: 0, Errors: 0, Time elapsed: 0,228 sec
查询缓存
在确定的情况下,缓存一次查询的正确结果是非常有用的,不仅是只是个确定的对象。例如,getCountries()方法在每次调用的时候或许会返回相同的国家列表。所以,除了缓存country类之外,你也应该缓存查询结果本身。
为了实现这个目标,你需要在hibernate.cfg.xml文件中设置hibernate.cache.use_query_cache属性为true,如下
<property name="hibernate.cache.use_query_cache">true</property>
然后需要在对任何查询缓存的时候使用setCacheable()方法,如下
public class CountryDAO { public List getCountries() { return SessionManager.currentSession() .createQuery("from Country as c order by c.name") .setCacheable(true) .list(); } }
但是,它不能预知到其他程序对数据库任何的改变。所以你不应该使用任何二级缓存(或为类和集合的缓存设置简短的过期时间)如果你的数据总是要保证最新的状态。
正确的使用hibernate缓存
缓存是一种强大的技术,hibernate提供了一种强大的,灵活的,不显眼的方式来实现它。即使对很多简单的例子来说默认的设置能使实际的性能得到提升。但是,像很多强大工具一样,hibernate还是需要一些思考和微调来得到最优的结果,而缓存像其他的优化技术一样,应该使用一种可扩展,测试驱动的方法所实现。当正确使用的时候,少量的缓存实现,能最大程度的提高你的程序运行。
发表评论
-
BPEL(1)
2009-02-03 17:37 2648随着Web Servcie技术日益成熟和流行,许多企业的很多部 ... -
常见设计模式的简介(一)
2008-09-23 22:00 0本文将主要介绍单态模 ... -
getContextPath、getServletPath、getRequestURI的区别
2008-09-09 16:29 50082工程图见附件: 假定你的web application 名称为 ... -
分页技术的原理及其实现
2008-07-24 15:40 2755分页问题是一个非常普 ... -
web会话状态维持
2008-07-23 22:01 1661http://blog.csdn.net/treeroot/a ...
相关推荐
在实际开发中,可以结合二级缓存(如 Ehcache 或 Redis)进一步优化数据访问效率,实现更高效的缓存策略。 总结,Hibernate的一级缓存是提升应用程序性能的重要工具。它在Session内部提供了一种内存级别的缓存机制...
Hibernate提供了两层缓存机制:第一级缓存和第二级缓存。 - **第一级缓存**:也称为Session缓存,是Hibernate内置并自动管理的缓存。每当Session开启,就会创建一个与之关联的第一级缓存。此缓存用于存储当前...
- **Hibernate二级缓存**:通过第三方缓存提供者如EHCache或OSCache实现,提高Web应用性能。 2. **查询缓存**:Hibernate查询缓存通过缓存查询结果,避免重复执行相同的查询。 #### 八、页面缓存与动态页面静态化 ...
本文将深入探讨Hibernate中的缓存机制,包括一级缓存、二级缓存以及查询缓存,旨在帮助开发者更好地理解和利用这些功能来优化应用性能。 首先,我们要了解缓存的基本概念。缓存是一种存储技术,它位于应用程序和...
Hibernate是一个开源的对象关系映射(ORM)框架,它允许Java开发者在Java应用程序中使用数据库时,不必编写大量的SQL代码,而是通过对象的方式来操作数据。这个压缩包文件包含了Hibernate开发所需的大部分核心库,...
**hibernate-redis** 是一个Hibernate的第二级缓存插件,它实现了Hibernate的CacheProvider接口,将数据缓存在Redis中。通过这个插件,开发者可以将经常访问但不经常改变的数据存储在Redis中,从而减少对数据库的...
1. **缓存策略配置**:理解并合理配置第一级和第二级缓存,可以显著提高应用程序的响应速度,但需注意缓存一致性问题。 2. **懒加载与Eager加载**:根据业务场景选择合适的加载策略,避免N+1查询问题,提高性能。 ...
通过本文对Hibernate的深入剖析,我们不仅了解了它的基本原理和核心概念,还学习了如何有效地使用其提供的各种API来构建高性能的应用程序。Hibernate作为一种强大的ORM框架,在简化Java应用开发的同时,也带来了诸如...
### Hibernate性能优化方案详解 #### 一、引言 Hibernate作为Java领域中广泛使用的对象关系映射(ORM)...通过对这些方面的综合考量和优化,可以极大地提升Hibernate应用程序的性能表现,从而更好地满足业务需求。
- **缓存机制**:内置一级缓存,可配置二级缓存,提高了数据访问效率。 #### 三、整合框架的可行性分析 将 Struts、Spring 和 Hibernate 进行整合,可以构建出一个高度模块化、灵活且高效的 J2EE 架构。这种整合的...
2. 第二级缓存:Hibernate3.6.8增强了第二级缓存的性能和可配置性,使得数据可以在多个Session之间共享,提高应用的响应速度。 3. 改进的性能:优化了查询处理和对象状态管理,减少了内存占用并提高了整体性能。 4...
- **Hibernate二级缓存**:SessionFactory级别,跨Session,可配置为进程内或集群间共享,缓存查询结果和集合数据。 3. **分布式系统中的缓存(Memcached、Xmemcached)** - **分布式缓存**:在分布式环境中,...
为了提高性能,Hibernate提供了二级缓存机制,可以缓存查询结果,避免重复的数据库访问。同时,Hibernate还支持缓存的区域划分,便于控制不同数据的缓存策略。 ### Hibernate的高级特性 #### 批量操作与性能优化 ...
- **缓存机制**:为了提高性能,Hibernate支持一级缓存和二级缓存,能够有效地减少数据库访问次数。 3. **Spring与Hibernate集成的最佳实践** - **整合配置**:通过Spring配置文件或注解来管理和配置Hibernate...
- **ORM(Hibernate,Toplink)缓存**:ORM框架通常内置了一级缓存和二级缓存机制,以减少对数据库的访问次数。 - **数据库层的缓存**:在数据库级别进行缓存,如使用MySQL的Query Cache。 - **业务对象的缓存**:...
- **Hibernate**:作为默认的二级缓存提供者。 - **Spring**:结合Spring框架,实现数据的缓存管理。 - **其他开源系统**:如Cocoon等。 #### 三、Ehcache的主要特性 1. **快速**:Ehcache采用高效的数据结构和...
EHCache 可以作为 Hibernate 的二级缓存插件,提高基于 Hibernate 构建的应用程序性能。 - **配置 Hibernate**: 在 `hibernate.cfg.xml` 文件中配置: ```xml <property name="hibernate.cache.provider_...