- 浏览: 410537 次
- 性别:
- 来自: 北京
文章分类
- 全部博客 (347)
- java基础 (58)
- ajax (10)
- s2sh (10)
- 版本控制 (4)
- 数据库 (34)
- 服务器 (4)
- 开发工具 (8)
- javascript (15)
- soockte (5)
- ext (2)
- 环境搭建 (7)
- struts2 (9)
- 找工作中的面试技巧 (2)
- 承接网站零活 (0)
- JNI+JONSE+OGNL (8)
- 性能优化 (4)
- Android开发 (5)
- xul (8)
- jquery (2)
- 线程 (3)
- jsp+jdbc (7)
- servlet (2)
- java对xml操作 (1)
- IO流的操作 (10)
- 项目开发前配置 (1)
- css (0)
- 上传、下载 (2)
- 知识探讨 (2)
- html (2)
- HQL (0)
- 工作技巧 (1)
- IT (1)
- Hibernate杂谈 (10)
- Spring杂谈 (35)
- DWR (5)
- JUnit测试 (3)
- EasyMock测试web (1)
- ibatis (6)
- maysql (5)
- C++ (0)
- 正则表达式(解剖) (1)
- 密码安全 (2)
- 上传 (1)
- socket (1)
- jni(java与c++结合) (1)
- jdk版本问题 (0)
- tomcat版本问题 (5)
- linux基本命令(初学) (7)
- linux项目发布 (1)
- 3年的经验总结 (1)
- 加解密 (2)
- 高级java阶段 (2)
- java内存分区 (1)
- 浏览器 (1)
- 职业规划 (1)
- 管理 (5)
- java语音 (1)
- SSH (1)
- jsp (3)
- extjs (1)
- uml (2)
- 加密 (1)
- web (2)
- Ant (1)
- 自述 (1)
- Linux (1)
- ssh源码解剖 (1)
- 代码优化 (1)
- 设计模式 (0)
- xml (2)
- JOSN (1)
- scala (0)
- hadoop (0)
- spark (0)
- hana (1)
- shior (1)
- java Word (6)
- java PDF (4)
- java Excel (0)
最新评论
-
高级java工程师:
ztao2333 写道谢谢。收藏下这个总结。呵呵
温习jdk和tomcat -
ztao2333:
大写的,不是大学的
温习jdk和tomcat -
ztao2333:
谢谢。收藏下这个总结。
温习jdk和tomcat -
the_small_base_:
你好,可以提供调用方法吗?需要的Jar,能发下源码吗?谢谢
java实现语音 -
高级java工程师:
文思涌动 写道楼主新年好。可否再传一遍给我,我没有收到, 不清 ...
s2sh整合
二级缓存来加快你的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属性,如下
Java代码
1. <hibernate-configuration>
2. <session-factory>
3. ...
4. <property name="hibernate.cache.provider_class">
5. org.hibernate.cache.EHCacheProvider
6. </property>
7. ...
8. </session-factory>
9. </hibernate-configuration>
<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映射。
Java代码
1. <hibernate-mapping package="com.wakaleo.articles.caching.businessobjects">
2. <class name="Country" table="COUNTRY" dynamic-update="true">
3. <meta attribute="implement-equals">true</meta>
4. <cache usage="read-only"/>
5.
6. <id name="id" type="long" unsaved-value="null" >
7. <column name="cn_id" not-null="true"/>
8. <generator class="increment"/>
9. </id>
10.
11. <property column="cn_code" name="code" type="string"/>
12. <property column="cn_name" name="name" type="string"/>
13.
14. <set name="airports">
15. <key column="cn_id"/>
16. <one-to-many class="Airport"/>
17. </set>
18. </class>
19. </hibernate-mapping>
<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类中一个简单的方法实现,如下
Java代码
1. public class CountryDAO {
2. ...
3. public List getCountries() {
4. return SessionManager.currentSession()
5. .createQuery(
6. "from Country as c order by c.name")
7. .list();
8. }
9. }
public class CountryDAO {
...
public List getCountries() {
return SessionManager.currentSession()
.createQuery(
"from Country as c order by c.name")
.list();
}
}
因为这个方法经常被调用,所以你要知道它在压力下的行为。写一个简单的单元测试来模拟5次连续的调用。
Java代码
1. public void testGetCountries() {
2. CountryDAO dao = new CountryDAO();
3. for(int i = 1; i <= 5; i++) {
4. Transaction tx = SessionManager.getSession().beginTransaction();
5. TestTimer timer = new TestTimer("testGetCountries");
6. List countries = dao.getCountries();
7. tx.commit();
8. SessionManager.closeSession();
9. timer.done();
10. assertNotNull(countries);
11. assertEquals(countries.size(),229);
12. }
13. }
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的属性
Java代码
1. <hibernate-mapping package="com.wakaleo.articles.caching.businessobjects">
2. <class name="Country" table="COUNTRY" dynamic-update="true">
3. <meta attribute="implement-equals">true</meta>
4. <cache usage="read-only"/>
5. ...
6. </class>
7. </hibernate-mapping>
<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属性
Java代码
1. <hibernate-configuration>
2. <session-factory>
3. ...
4. <property name="hibernate.cache.provider_class">
5. org.hibernate.cache.EHCacheProvider
6. </property>
7. ...
8. <class-cache
9. class="com.wakaleo.articles.caching.businessobjects.Country"
10. usage="read-only"
11. />
12. </session-factory>
13. </hibernate-configuration>
<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配置文件
Java代码
1. <ehcache>
2.
3. <diskStore path="java.io.tmpdir"/>
4.
5. <defaultCache
6. maxElementsInMemory="10000"
7. eternal="false"
8. timeToIdleSeconds="120"
9. timeToLiveSeconds="120"
10. overflowToDisk="true"
11. diskPersistent="false"
12. diskExpiryThreadIntervalSeconds="120"
13. memoryStoreEvictionPolicy="LRU"
14. />
15.
16. <cache name="com.wakaleo.articles.caching.businessobjects.Country"
17. maxElementsInMemory="300"
18. eternal="true"
19. overflowToDisk="false"
20. />
21.
22. </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映射
Java代码
1. <hibernate-mapping package="com.wakaleo.articles.caching.businessobjects">
2. <class name="Employee" table="EMPLOYEE" dynamic-update="true">
3. <meta attribute="implement-equals">true</meta>
4.
5. <id name="id" type="long" unsaved-value="null" >
6. <column name="emp_id" not-null="true"/>
7. <generator class="increment"/>
8. </id>
9.
10. <property column="emp_surname" name="surname" type="string"/>
11. <property column="emp_firstname" name="firstname" type="string"/>
12.
13. <many-to-one name="country"
14. column="cn_id"
15. class="com.wakaleo.articles.caching.businessobjects.Country"
16. not-null="true" />
17.
18. <!-- Lazy-loading is deactivated to demonstrate caching behavior -->
19. <set name="languages" table="EMPLOYEE_SPEAKS_LANGUAGE" lazy="false">
20. <key column="emp_id"/>
21. <many-to-many column="lan_id" class="Language"/>
22. </set>
23. </class>
24. </hibernate-mapping>
<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,以下的代码来帮你实现
Java代码
1. public class EmployeeDAO {
2.
3. public List getEmployeesByCountry(Country country) {
4. return SessionManager.currentSession()
5. .createQuery(
6. "from Employee as e where e.country = :country "
7. + " order by e.surname, e.firstname")
8. .setParameter("country",country)
9. .list();
10. }
11. }
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();
}
}
下一步,写一些简单的单元测试来看它怎么表现。正如前面的例子一样,你需要知道它被重复调用时候的性能
Java代码
1. public class EmployeeDAOTest extends TestCase {
2.
3. CountryDAO countryDao = new CountryDAO();
4. EmployeeDAO employeeDao = new EmployeeDAO();
5.
6. /**
7. * Ensure that the Hibernate session is available
8. * to avoid the Hibernate initialisation interfering with
9. * the benchmarks
10. */
11. protected void setUp() throws Exception {
12. super.setUp();
13. SessionManager.getSession();
14. }
15.
16. public void testGetNZEmployees() {
17. TestTimer timer = new TestTimer("testGetNZEmployees");
18. Transaction tx = SessionManager.getSession().beginTransaction();
19. Country nz = countryDao.findCountryByCode("nz");
20. List kiwis = employeeDao.getEmployeesByCountry(nz);
21. tx.commit();
22. SessionManager.closeSession();
23. timer.done();
24. }
25.
26. public void testGetAUEmployees() {
27. TestTimer timer = new TestTimer("testGetAUEmployees");
28. Transaction tx = SessionManager.getSession().beginTransaction();
29. Country au = countryDao.findCountryByCode("au");
30. List aussis = employeeDao.getEmployeesByCountry(au);
31. tx.commit();
32. SessionManager.closeSession();
33. timer.done();
34. }
35.
36. public void testRepeatedGetEmployees() {
37. testGetNZEmployees();
38. testGetAUEmployees();
39. testGetNZEmployees();
40. testGetAUEmployees();
41. }
42. }
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启用读写缓存,如下
Java代码
1. <hibernate-mapping package="com.wakaleo.articles.caching.businessobjects">
2. <class name="Employee" table="EMPLOYEE" dynamic-update="true">
3. <meta attribute="implement-equals">true</meta>
4. <cache usage="read-write"/>
5. ...
6. </class>
7. </hibernate-mapping>
<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类启用缓存。只读缓存如下
Java代码
1. <hibernate-mapping package="com.wakaleo.articles.caching.businessobjects">
2. <class name="Language" table="SPOKEN_LANGUAGE" dynamic-update="true">
3. <meta attribute="implement-equals">true</meta>
4. <cache usage="read-only"/>
5. ...
6. </class>
7. </hibernate-mapping>
<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文件中
Java代码
1. <cache name="com.wakaleo.articles.caching.businessobjects.Employee"
2. maxElementsInMemory="5000"
3. eternal="false"
4. overflowToDisk="false"
5. timeToIdleSeconds="300"
6. timeToLiveSeconds="600"
7. />
8. <cache name="com.wakaleo.articles.caching.businessobjects.Language"
9. maxElementsInMemory="100"
10. eternal="true"
11. overflowToDisk="false"
12. />
<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启用缓存,如下
Java代码
1. <hibernate-mapping package="com.wakaleo.articles.caching.businessobjects">
2. <class name="Employee" table="EMPLOYEE" dynamic-update="true">
3. <meta attribute="implement-equals">true</meta>
4.
5. <id name="id" type="long" unsaved-value="null" >
6. <column name="emp_id" not-null="true"/>
7. <generator class="increment"/>
8. </id>
9.
10. <property column="emp_surname" name="surname" type="string"/>
11. <property column="emp_firstname" name="firstname" type="string"/>
12.
13. <many-to-one name="country"
14. column="cn_id"
15. class="com.wakaleo.articles.caching.businessobjects.Country"
16. not-null="true" />
17.
18. <!-- Lazy-loading is deactivated to demonstrate caching behavior -->
19. <set name="languages" table="EMPLOYEE_SPEAKS_LANGUAGE" lazy="false">
20. <cache usage="read-write"/>
21. <key column="emp_id"/>
22. <many-to-many column="lan_id" class="Language"/>
23. </set>
24. </class>
25. </hibernate-mapping>
<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()方法,如下
Java代码
1. public class CountryDAO {
2.
3. public List getCountries() {
4. return SessionManager.currentSession()
5. .createQuery("from Country as c order by c.name")
6. .setCacheable(true)
7. .list();
8. }
9. }
public class CountryDAO {
public List getCountries() {
return SessionManager.currentSession()
.createQuery("from Country as c order by c.name")
.setCacheable(true)
.list();
}
}
但是,它不能预知到其他程序对数据库任何的改变。所以你不应该使用任何二级缓存(或为类和集合的缓存设置简短的过期时间)如果你的数据总是要保证最新的状态。
正确的使用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属性,如下
Java代码
1. <hibernate-configuration>
2. <session-factory>
3. ...
4. <property name="hibernate.cache.provider_class">
5. org.hibernate.cache.EHCacheProvider
6. </property>
7. ...
8. </session-factory>
9. </hibernate-configuration>
<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映射。
Java代码
1. <hibernate-mapping package="com.wakaleo.articles.caching.businessobjects">
2. <class name="Country" table="COUNTRY" dynamic-update="true">
3. <meta attribute="implement-equals">true</meta>
4. <cache usage="read-only"/>
5.
6. <id name="id" type="long" unsaved-value="null" >
7. <column name="cn_id" not-null="true"/>
8. <generator class="increment"/>
9. </id>
10.
11. <property column="cn_code" name="code" type="string"/>
12. <property column="cn_name" name="name" type="string"/>
13.
14. <set name="airports">
15. <key column="cn_id"/>
16. <one-to-many class="Airport"/>
17. </set>
18. </class>
19. </hibernate-mapping>
<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类中一个简单的方法实现,如下
Java代码
1. public class CountryDAO {
2. ...
3. public List getCountries() {
4. return SessionManager.currentSession()
5. .createQuery(
6. "from Country as c order by c.name")
7. .list();
8. }
9. }
public class CountryDAO {
...
public List getCountries() {
return SessionManager.currentSession()
.createQuery(
"from Country as c order by c.name")
.list();
}
}
因为这个方法经常被调用,所以你要知道它在压力下的行为。写一个简单的单元测试来模拟5次连续的调用。
Java代码
1. public void testGetCountries() {
2. CountryDAO dao = new CountryDAO();
3. for(int i = 1; i <= 5; i++) {
4. Transaction tx = SessionManager.getSession().beginTransaction();
5. TestTimer timer = new TestTimer("testGetCountries");
6. List countries = dao.getCountries();
7. tx.commit();
8. SessionManager.closeSession();
9. timer.done();
10. assertNotNull(countries);
11. assertEquals(countries.size(),229);
12. }
13. }
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的属性
Java代码
1. <hibernate-mapping package="com.wakaleo.articles.caching.businessobjects">
2. <class name="Country" table="COUNTRY" dynamic-update="true">
3. <meta attribute="implement-equals">true</meta>
4. <cache usage="read-only"/>
5. ...
6. </class>
7. </hibernate-mapping>
<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属性
Java代码
1. <hibernate-configuration>
2. <session-factory>
3. ...
4. <property name="hibernate.cache.provider_class">
5. org.hibernate.cache.EHCacheProvider
6. </property>
7. ...
8. <class-cache
9. class="com.wakaleo.articles.caching.businessobjects.Country"
10. usage="read-only"
11. />
12. </session-factory>
13. </hibernate-configuration>
<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配置文件
Java代码
1. <ehcache>
2.
3. <diskStore path="java.io.tmpdir"/>
4.
5. <defaultCache
6. maxElementsInMemory="10000"
7. eternal="false"
8. timeToIdleSeconds="120"
9. timeToLiveSeconds="120"
10. overflowToDisk="true"
11. diskPersistent="false"
12. diskExpiryThreadIntervalSeconds="120"
13. memoryStoreEvictionPolicy="LRU"
14. />
15.
16. <cache name="com.wakaleo.articles.caching.businessobjects.Country"
17. maxElementsInMemory="300"
18. eternal="true"
19. overflowToDisk="false"
20. />
21.
22. </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映射
Java代码
1. <hibernate-mapping package="com.wakaleo.articles.caching.businessobjects">
2. <class name="Employee" table="EMPLOYEE" dynamic-update="true">
3. <meta attribute="implement-equals">true</meta>
4.
5. <id name="id" type="long" unsaved-value="null" >
6. <column name="emp_id" not-null="true"/>
7. <generator class="increment"/>
8. </id>
9.
10. <property column="emp_surname" name="surname" type="string"/>
11. <property column="emp_firstname" name="firstname" type="string"/>
12.
13. <many-to-one name="country"
14. column="cn_id"
15. class="com.wakaleo.articles.caching.businessobjects.Country"
16. not-null="true" />
17.
18. <!-- Lazy-loading is deactivated to demonstrate caching behavior -->
19. <set name="languages" table="EMPLOYEE_SPEAKS_LANGUAGE" lazy="false">
20. <key column="emp_id"/>
21. <many-to-many column="lan_id" class="Language"/>
22. </set>
23. </class>
24. </hibernate-mapping>
<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,以下的代码来帮你实现
Java代码
1. public class EmployeeDAO {
2.
3. public List getEmployeesByCountry(Country country) {
4. return SessionManager.currentSession()
5. .createQuery(
6. "from Employee as e where e.country = :country "
7. + " order by e.surname, e.firstname")
8. .setParameter("country",country)
9. .list();
10. }
11. }
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();
}
}
下一步,写一些简单的单元测试来看它怎么表现。正如前面的例子一样,你需要知道它被重复调用时候的性能
Java代码
1. public class EmployeeDAOTest extends TestCase {
2.
3. CountryDAO countryDao = new CountryDAO();
4. EmployeeDAO employeeDao = new EmployeeDAO();
5.
6. /**
7. * Ensure that the Hibernate session is available
8. * to avoid the Hibernate initialisation interfering with
9. * the benchmarks
10. */
11. protected void setUp() throws Exception {
12. super.setUp();
13. SessionManager.getSession();
14. }
15.
16. public void testGetNZEmployees() {
17. TestTimer timer = new TestTimer("testGetNZEmployees");
18. Transaction tx = SessionManager.getSession().beginTransaction();
19. Country nz = countryDao.findCountryByCode("nz");
20. List kiwis = employeeDao.getEmployeesByCountry(nz);
21. tx.commit();
22. SessionManager.closeSession();
23. timer.done();
24. }
25.
26. public void testGetAUEmployees() {
27. TestTimer timer = new TestTimer("testGetAUEmployees");
28. Transaction tx = SessionManager.getSession().beginTransaction();
29. Country au = countryDao.findCountryByCode("au");
30. List aussis = employeeDao.getEmployeesByCountry(au);
31. tx.commit();
32. SessionManager.closeSession();
33. timer.done();
34. }
35.
36. public void testRepeatedGetEmployees() {
37. testGetNZEmployees();
38. testGetAUEmployees();
39. testGetNZEmployees();
40. testGetAUEmployees();
41. }
42. }
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启用读写缓存,如下
Java代码
1. <hibernate-mapping package="com.wakaleo.articles.caching.businessobjects">
2. <class name="Employee" table="EMPLOYEE" dynamic-update="true">
3. <meta attribute="implement-equals">true</meta>
4. <cache usage="read-write"/>
5. ...
6. </class>
7. </hibernate-mapping>
<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类启用缓存。只读缓存如下
Java代码
1. <hibernate-mapping package="com.wakaleo.articles.caching.businessobjects">
2. <class name="Language" table="SPOKEN_LANGUAGE" dynamic-update="true">
3. <meta attribute="implement-equals">true</meta>
4. <cache usage="read-only"/>
5. ...
6. </class>
7. </hibernate-mapping>
<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文件中
Java代码
1. <cache name="com.wakaleo.articles.caching.businessobjects.Employee"
2. maxElementsInMemory="5000"
3. eternal="false"
4. overflowToDisk="false"
5. timeToIdleSeconds="300"
6. timeToLiveSeconds="600"
7. />
8. <cache name="com.wakaleo.articles.caching.businessobjects.Language"
9. maxElementsInMemory="100"
10. eternal="true"
11. overflowToDisk="false"
12. />
<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启用缓存,如下
Java代码
1. <hibernate-mapping package="com.wakaleo.articles.caching.businessobjects">
2. <class name="Employee" table="EMPLOYEE" dynamic-update="true">
3. <meta attribute="implement-equals">true</meta>
4.
5. <id name="id" type="long" unsaved-value="null" >
6. <column name="emp_id" not-null="true"/>
7. <generator class="increment"/>
8. </id>
9.
10. <property column="emp_surname" name="surname" type="string"/>
11. <property column="emp_firstname" name="firstname" type="string"/>
12.
13. <many-to-one name="country"
14. column="cn_id"
15. class="com.wakaleo.articles.caching.businessobjects.Country"
16. not-null="true" />
17.
18. <!-- Lazy-loading is deactivated to demonstrate caching behavior -->
19. <set name="languages" table="EMPLOYEE_SPEAKS_LANGUAGE" lazy="false">
20. <cache usage="read-write"/>
21. <key column="emp_id"/>
22. <many-to-many column="lan_id" class="Language"/>
23. </set>
24. </class>
25. </hibernate-mapping>
<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()方法,如下
Java代码
1. public class CountryDAO {
2.
3. public List getCountries() {
4. return SessionManager.currentSession()
5. .createQuery("from Country as c order by c.name")
6. .setCacheable(true)
7. .list();
8. }
9. }
public class CountryDAO {
public List getCountries() {
return SessionManager.currentSession()
.createQuery("from Country as c order by c.name")
.setCacheable(true)
.list();
}
}
但是,它不能预知到其他程序对数据库任何的改变。所以你不应该使用任何二级缓存(或为类和集合的缓存设置简短的过期时间)如果你的数据总是要保证最新的状态。
正确的使用hibernate缓存
发表评论
-
Hibernate配置详解
2014-05-08 15:53 686Hibernate配置文件详解 修改完环境变量之后 ... -
Hibernate 关系映射 总结整理
2014-03-03 13:20 796《Hibernate 关系映射 ... -
hibernate于ibatis如何处理updateUser?
2012-12-07 15:35 727hibernate? 当new一个user的时候,是处于 ... -
getHibernateTemplate()查询
2011-11-16 15:07 889一、find(String queryString); ... -
Hibernate 完善的操作
2011-11-16 11:33 1026HibernateTemplate提供非常多的常用方法 ... -
s2sh整合
2011-09-20 22:11 2948最近的struts2-hibernate -spring整合开 ... -
hibernate自动创建表的2中方式
2011-07-09 22:37 5251.创建学生类 package com.day02; p ... -
Hibernate保存数据
2011-05-05 08:37 766Hibernate数据保存——Session.save Se ... -
hibernate中lazy =true
2011-05-05 07:57 724Hibernate的检索策略包括类级别检索策略和关联级别检索策 ...
相关推荐
总结起来,多维缓存加速系统通过构建多层次的缓存结构,结合智能的分配、替换策略和一致性协议,有效利用了存储层次差异,提高了数据访问速度,进而提升了整个系统的性能。这种技术在现代计算机系统中扮演着至关重要...
2. **二级缓存配置** - 在Hibernate配置文件(hibernate.cfg.xml)中,需要启用二级缓存并指定实现二级缓存的提供商,如EhCache、Infinispan等。 - 对于特定实体类,需要在映射文件(.hbm.xml)中声明使用二级缓存...
【ssm+redis二级缓存】是一个基于SpringMVC、Spring和MyBatis的Web应用程序,结合了Redis作为二级缓存来提升系统性能。在这个项目中,开发人员利用了SSM(Spring、SpringMVC、MyBatis)的集成优势,结合Redis的高速...
这个程序能够有效地加速磁盘驱动器,特别是当从外部介质(如CD-ROM)复制大量文件到硬盘时,它能显著减少复制时间。SMARTDRV.EXE通过利用系统的RAM(随机存取内存)作为临时的高速缓存,预先读取并暂存即将被访问的...
PHP 缓存加速器是提高 PHP 应用程序性能的关键技术之一。它可以将编译后的操作码文件保存下来,并放到共享内存里,以便在下一次调用该 PHP 页面时重用它,避免了相同代码的重复编译,节省了 PHP 引擎重复编译的时间...
本文主要探讨的是使用PEAR(PHP Extension and Application Repository)库中的Cache组件进行内容缓存输出,以实现PHP程序的加速。 首先,了解缓存的基本原理:缓存是将频繁访问的数据暂存到高速存储设备中,以便...
"图片三级缓存"就是一种常用的优化策略,它通过构建多级缓存系统来加速图片的加载,减少网络请求,降低服务器压力,并在有限的设备资源下提供流畅的用户体验。 一、缓存原理 缓存是一种存储技术,用于临时存放频繁...
SmartDrv是微软推出的一款磁盘高速缓存驱动程序,它通过在内存中创建一个缓冲区来存储频繁访问的数据,从而减少对硬盘的直接读写次数,显著提升系统运行速度,尤其是在执行大型程序或进行文件复制、安装操作系统等高...
WordPress加速插件秘笈:五款加速插件让您的网站飞起来,这是一款WordPress程序通用的缓存插件,可以将你的网站提升至毫秒级加载速度。 安装说明: 1:将缓存代码的PHP文件上传至网站根目录。 2:下面这行代码,放到...
这款WordPress无名氏全站缓存加速插件,是在我的网站正在使用的,亲测无错,喜欢的朋友们可以下载使用! 这是一款所有程序通用的缓存插件,可以将你的网站提升至毫秒级加载速度。 不管你是WordPress程序还是其它...
安装操作系统时,SSD作为缓存不仅可以加速系统文件的写入和读取,还可以在系统启动和应用程序运行时提供更高的速度,大大缩短等待时间。 实现这一技术的关键在于一款名为ExpressCache的软件,这是一款专为Windows...
WordPress加速插件秘笈:五款加速插件让您的网站飞起来,这是一款WordPress程序通用的缓存插件,可以将你的网站提升至毫秒级加载速度。 安装说明: 1:将缓存代码的PHP文件上传至网站根目录。 2:下面这行代码,放...
3. 缓存系统作为 ORM 框架的二级缓存对外提供服务,减轻数据库的负载压力,加速应用访问并行处理,涉及大量中间计算结果需要共享等等。 分布式缓存 1. Redis。 2. Memcache。 本地缓存和分布式缓存都是缓存技术中...
Memcached是一种高性能、分布式内存对象缓存系统,它通过减轻数据库负担来加速动态Web应用程序的速度,提高可扩展性。其工作原理是将数据存储在内存中,提供快速的数据访问速度,尤其是在高并发场景下,能够显著提升...
缓存位于应用程序与永久性数据存储(例如硬盘上的文件或数据库)之间,通过减少直接对永久性存储的读写操作来加速数据处理过程。 在Hibernate框架中,缓存同样扮演着关键角色。通过合理的缓存策略,可以显著提升...
但是相对于中小站长来说,他们很多人根本不懂的程序如何修改,如何去写,这就面临着一个问题:能否最简单的用上缓存或者静态。 昌邑网站长为大家提供一个精简的代码,简简单单的实现了首页缓存。缓存是数据写在...
本文详细介绍了一个名为RedisBoostWeb缓存加速平台的设计和实现方案,涵盖产品的四大主要功能,即智能缓存策略配置、多层级缓存架构支持、实时监控与性能分析以及无缝集成与自动化部署,同时探讨了该平台的产品特点...
首页缓存加速插件的核心原理是通过将频繁访问的首页内容存储在服务器的内存中,当用户请求时,服务器可以直接从内存中读取并返回页面内容,而无需重新执行ASP脚本和数据库查询,从而显著提高了响应速度和用户体验。...
在标题“老硬盘使用内存做缓存的程序”中,我们可以理解到这种技术特别适用于老旧的计算机,它们可能拥有较慢的硬盘但内存容量尚可。在32位操作系统中,由于地址总线的限制,通常只能识别和利用大约3.25GB左右的内存...