- 浏览: 322835 次
- 性别:
- 来自: 福建
文章分类
最新评论
-
wangyonglin1123:
完美解决 谢谢您
BufferedReader中文乱码解决 -
wangyonglin1123:
StringBuilder result = new Stri ...
BufferedReader中文乱码解决 -
u011110982:
楼主,代码呢?
PKI体系简易JAVA实现(一):时间戳服务器TSA -
WangSongYuan:
我也遇到楼主一样的问题 果然在<body>后面填一 ...
Extjs IE8 对象不支持此属性或方法的bug -
ocaicai:
https://gist.github.com/wendal/ ...
JAVA获取方法参数名的分析(二)
原文:http://www.devx.com/dbzone/article/29685/1954
作者:John Ferguson Smart
翻译及加工: 魏超
因为对hibernate缓存的不了解,新接触hiberante开发的人往往无法很好的使用它。然而,合理的使用缓存将成为加速hibernate程序的最有效途径。
频繁的数据库读写会影响web项目的性能表现。作为一个高性能的对象/关系持久化查询技术,单纯的使用hibernate可能还不能解决你所有的性能问题。很多时候,开启二级缓存将会很好的改变这种境况。下面的文章会让你对缓存有个初步的了解,同时告诉你怎么用缓存来提升性能。
什么是缓存(Cache)?
缓存被广泛应用的用于优化数据库。当一些数据被从数据库中读取出来的时候,我们可以把它们放到缓存里.这样,在再次用到这些数据的时候,我们就可以直接从缓存把他们取出来,而不是去连接数据库。当然,当数据库的记录被修改更新的时候,我们就需要把缓存清空掉。因为我们无从得知在数据库记录更新时,缓存中的记录是否还和数据库里的相同
Hibernate的缓存
Hibernate有一级和二级两种缓存对象。一级缓存关联session对象,而二级缓存关联着session工厂对象。默认情况下,一级缓存是在单个事务中使用的。举例来说,在同一个事务中,当一个对象在事务提交前,被修改了很多次,那么同过一级缓存,在事务提交的时候,我们就会把所有这些修改写在同一条SQL语句中传递给数据库,而不是每次修改都有一条语句。当然,我们这篇文章关注的是二级缓存。可以这么说,相比一级缓存,二级缓存是跨事务的,它将一个事务中产生的查询对象保存下来,在其他事务执行相同的查询的时候,这些被保存的对象就可以被直接拿出来使用,这样就能最大化的减少数据库操作。因此,对于同一个服务,只要有一个用户执行了某个查询,那么其他将要执行相同查询的用户都将从二级缓存中受益。
此外,相对于上面说的缓存持久化对象,你可以使用query-level的缓存来存储真正的查询结果集。
缓存的实现
市场上提供了相当多的缓存技术的选择,既有开源的也有收费的。 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,支持只读和读/写缓存,内存和基于磁盘的缓存。它还提供对JavaGroup或JMS集群的基础支持。
- SwarmCache 是一个基于JavaGroup的简单集群缓存解决方案。它支持只读或不严格的读/写缓存(接下来的部分解释这个词)。此缓存的适用于读操作远多于写操作的数据库应用。
- JBoss TreeCache的 是一个强大的复制(同步或异步)事务缓存。如果你需要一个真正有事务能力的缓存架构,选择这个缓存。
另一个值得一提的是商业缓存Tangosol: Tangosol的高速缓存的一致性.
缓存策略
一旦选择了缓存实现,你还需要指定你的访问策略。以下四个缓存策略可供选择:
- 只读:用于只读取而不写入的访问策略。这是迄今为止最简单,效果最好的缓存策略。
- 读/写:如果你有数据需要写入(更新),可以用这个缓存。当然,读/写缓存的开销要比只读缓存大。在非JTA的环境中,每次操作应在Session.close()或者Session.disconnect()被结束。
- 非严格的读/写:这种策略并不能保证两个操作修改同一个数据的安全。因此,它适用于经常查询,而只是偶尔的修改的数据。
- 事务性:这是一个完全的事务缓存,可用于JTA环境。
下表显示了可供选择的不同的缓存实现:
EHCache | 是的 | 是的 | 是的 | 没有 |
OSCache | 是的 | 是的 | 是的 | 没有 |
SwarmCache | 是的 | 是的 | 没有 | 没有 |
JBoss TreeCache | 是的 | 没有 | 没有 | 是的 |
下面的部分将展示在JVM中使用EHCache缓存。
缓存配置
要激活二级缓存,首先你需要定义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>
当你Hibernate版本是3以上时,你可能还需要使用hibernate.cache.use_second_level_cache属性。这个属性让你可以激活(或停止)二级缓存。默认情况下,二级缓存使用的是EHCache并且已经激活。
一个例子
这个例子由几个简单的表组成:Airport, Employee, Language,Country. 每个employee属于一个country,会说多国的language. 而每个country有许多的airport. 下面2幅图分别是这4个类的类图和数据库表图。
设置一个只读缓存
首先设置国家(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>
如果这时候你要取所有的country列表,你可以在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的命令行来运行上面的代码。在这里我们连接了一个本地的MySQL数据库。当这段代码被成功的运行,你应该会看到下面的输出:
testGetCountries: 521 ms. testGetCountries: 357 ms. testGetCountries: 249 ms. testGetCountries: 257 ms. testGetCountries: 355 ms.
每次操作基本上要用4分之1秒。在多数情况下,上面我们取得的国家列表不会被频繁的修改。因此这是一个很好的例子来引入只读缓存(read-only Cache).
有2种方法可以激活二级缓存的类:
1. 在要使用二级缓存的那个类对应的hbm.xml文件中添加下面的属性:
<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文件中:
<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>
接下来,你需要配置这个类的缓存规则。这些规则会决定缓存的一些细节。我们在这个例子中用的是EHChe缓存。当然,在其他不同的缓存中,配置规则的方式是不一样的。
EHCache需要一个配置文件(在类路径的根目录,一般称为ehcache.xml)。在下面这个站点有这个文件的模板: EHCache模板 。基本上,你要为每一个你想做二级缓存的类在这个文件中配置规则。如果你没有配置,程序将调用一个默认的规则。
对于这个例子,我们可以使用下面的简单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>
这个文件基本上建立了一个国家基于内存的缓存,最多300个元素(国家清单包含229个国家)。请注意,缓存永不过期(即'永恒=真正的财产)。
这个配置建立了一个基于内存的country缓存。里面最多可以保存300个元素(在我们这个例子中,查询出的结果集包含229个country).(注:这个缓存被配置为永远不过期。eternal=true)
现在,重新运行测试,结果如下:
testGetCountries: 412 ms. testGetCountries: 98 ms. testGetCountries: 92 ms. testGetCountries: 82 ms. testGetCountries: 93 ms.
可以明显的看出,操作时间提高到了10分之1秒左右(第一次用了400多ms是因为第一次真正和数据库交互了)
缓存内部的存储
在继续下去之前,我们有必要先来了解在上面的代码背后发生了什么。值得注意的是,hibernate缓存并不存储对象的实例(Instances).实际上它存储的是对象的“dehydrated”格式(hibernate术语),这种格式其实就是一系列的属性值。下面是country缓存中内容的一个例子:
{ 30 => [bw,Botswana,30], 214 => [uy,Uruguay,214], 158 => [pa,Panama,158], 31 => [by,Belarus,31] 95 => [in,India,95] ... }
注意,每一个Id映射到一系列的属性上。这里你可能会发现,只有原始的属性被缓存下来了,具体来说,airport属性没有被缓存。实际上,这是因为airport属性是一个association:是一系列指向其他持久化对象的引用。
默认情况下,hibernate不会对associations进行缓存。当然,你可以设置是否缓存association,同时可以设置当二级缓存中的对象被重新加载出来的时候哪些associations要被检索。
Association缓存是一个非常强大的功能。下面我们将对它进行更详细的研究。
使用Associations缓存
假设你需要获得一个country对应的所有employees的列表(包括employee名字, 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"> <key column="emp_id"/> <many-to-many column="lan_id" class="Language"/> </set> </class> </hibernate-mapping>
假如你真的需要在每次取得employee对象的时候都加载其对应的language信息,你需要把language属性的lazy设置为false.(不延迟加载)(这里只是为了举例的需要,在真实情况下,不建议取消延迟加载,因为这会影响性能)。同时,你需要添加下面的方法来获得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(); } }
运行这个单元测试,可以看到下面的结果:
testGetNZEmployees: 1227 ms. testGetAUEmployees: 883 ms. testGetNZEmployees: 907 ms. testGetAUEmployees: 873 ms. testGetNZEmployees: 987 ms. testGetAUEmployees: 916 ms.
可以看出,每次为一个country取得50几个employee都需要差不多1秒钟 。这样的速度太慢了。这是一个传统的"N+1"问题。如果启用SQL日志,我们可以看到,每次执行一个employee的查询语句后面,都跟着数百条language表的查询。每次我们从缓存中加载employee对象,其关联的全部language都会被重新检索。我们应该怎么改善这里的性能呢?首先,激活employee类的读写缓存(read/write cache):
<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上的只读缓存(read-only cache):
<class name="Language" table="SPOKEN_LANGUAGE" dynamic-update="true"> <meta attribute="implement-equals">true</meta> <cache usage="read-only"/> ... </class> </hibernate-mapping>
之后,在ehcache.xml文件中添加下面2个缓存规则:
<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的association.
<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>
这样配置之后,我们可以看到下面的结果(速度提高了10倍左右):
testGetNZEmployees: 1477 ms. testGetAUEmployees: 940 ms. testGetNZEmployees: 65 ms. testGetAUEmployees: 65 ms. testGetNZEmployees: 76 ms. testGetAUEmployees: 52 ms.
使用查询缓存(Query Caches)
在某些情况下,我们需要缓存的是确切的查询结果集,而不是某些对象。例如,每次调用getCountries()方法的时候,我们都会得到相同的country列表。因此,除了缓存country类,我们还需要缓存查询结果集本身。
为了实现这个,我们需要启用hibernate.cfg.xml文件中的hibernate.cache.use_query_cache属性:
<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却无法获悉本身应用之外的,其他应用直接去修改数据库数据的操作。因此,如果你使用的数据会频繁的处于提交更新的状态下,你就不应该使用任何的二级缓存。如果非要使用的话,至少,你应该将二级缓存的超时时间设置的足够短。
正确的Hibernate缓存
缓存是一个强大的技术,Hibernate提供了一个有效,灵活且过度缓和的方式来实现它。即使是默认的配置也可以在许多简单的应用中有效的提高性能。然而,如同其他强大的工具一样,hibernate需要更深入的思考和微调,来取得最佳的效果。而缓存——如同其他的优化技术——应该遵循增量的,测试驱动的方法。当合理使用的时候,少量的缓存就可以使你程序发挥出最大的效能。
评论
是我翻译及修改的,原文英文地址在本文开头的那个链接里
发表评论
-
事务相关总结记录
2011-09-21 16:35 1275查问题过程中,仅对一些东西做下记载。 在总体事务结束前 ... -
操作Clob时抛出Blob may not be manipulated from creating session.
2011-05-03 17:03 4375程序上需要将数据库Clob字段转换为String,传到前台显示 ... -
Hibernate+DWR构建应用过程中遇到的问题
2009-11-23 00:53 1490最近要写一个小的B/S工程,觉得往常的SSH框架实在太过沉重, ... -
HibernateSynchronizer添加Hibernate支持后测试出错
2009-10-12 14:46 1857工程环境: IDE: eclipse3.5 插件:Hibe ... -
HibernateSynchronizer中无法自动生成DAO的问题
2009-10-12 13:07 2220HibernateSynchronizer是当前优秀的ecli ...
相关推荐
总的来说,"hibernate二级缓存实例"是一个很好的学习资源,它可以帮助我们理解二级缓存的工作机制,掌握如何在项目中配置和使用,以及注意潜在的问题和优化策略。通过实践,我们可以更好地运用这一技术,提升Java...
总结来说,Hibernate 的一级缓存和二级缓存都是为了提高数据访问效率,但它们在范围和并发控制方面有所不同。一级缓存是事务级别的,保证了数据的强一致性,而二级缓存提供了更多的灵活性,可以跨事务共享,但需要...
综上所述,通过学习`hibernate二级缓存示例源码`,我们可以了解到如何在实际项目中配置和使用Hibernate二级缓存,从而提升系统的性能。在实际应用中,应结合具体场景选择合适的缓存策略,以达到最佳的性能优化效果。
Hibernate的一级缓存、二级缓存和查询缓存共同构建了一个层次化的缓存体系,有效地缓解了数据库的压力,提升了应用的运行效率。理解并掌握这些缓存机制,对于优化Hibernate应用至关重要。在实践中,合理配置和管理...
Hibernate二级缓存是Java开发中使用Hibernate框架进行数据持久化时优化性能的一种重要技术。它在一级缓存(Session级别的缓存)的基础上,提供了一个全局的、跨会话的数据存储层,可以显著减少对数据库的访问,从而...
总的来说,Hibernate的二级缓存是优化数据库访问的关键工具,通过合理配置和使用,能够显著提升应用的响应速度和并发处理能力。不过,需要注意的是,由于缓存可能会导致数据一致性问题,因此在设计缓存策略时,必须...
学习和掌握Hibernate的二级缓存机制对于优化大型应用的性能至关重要。通过合理配置和使用,可以在不牺牲数据一致性的情况下,大幅度减少对数据库的访问,提高系统响应速度。在实际项目中,可以根据业务需求和系统...
**二、Hibernate二级缓存** 二级缓存是SessionFactory级别的,跨越了多个Session,可以被多个线程共享。它通常由第三方插件如EhCache、Infinispan等提供。二级缓存分为以下几种类型: 1. **集合缓存**:用于存储...
通过理解和运用Hibernate的二级缓存,我们可以优化应用性能,减少数据库压力,但同时也需要注意缓存可能带来的问题,如数据一致性、内存管理和并发控制等。在实际项目中,结合业务需求和系统特点,合理配置和管理...
二级缓存是 Hibernate 缓存策略的一部分,它在应用程序的多个会话之间共享数据,进一步优化了数据库访问效率。 二级缓存分为以下关键知识点: 1. **一级缓存与二级缓存的区别**: - 一级缓存:每个 Hibernate ...
通过以上步骤,我们就成功地在Spring Boot 2.1.4.RELEASE项目中配置了使用Redis作为Hibernate二级缓存的环境。这将显著提升数据库查询效率,减少对数据库的压力,尤其在高并发场景下,效果尤为明显。记得在实际生产...
在企业级Java应用开发中,Spring和...总之,合理利用Hibernate的二级缓存机制,结合Spring的管理能力,可以有效地提升Java应用的性能。通过优化缓存配置和策略,可以在不牺牲数据一致性的情况下,达到良好的用户体验。
本篇将深入探讨Hibernate的一级缓存和二级缓存,以及查询缓存的配置和使用。 ### 一级缓存 一级缓存是Hibernate默认提供的缓存,它是Session级别的,每个Hibernate Session都有一个私有的、本地的一级缓存。当我们...
本篇将详细介绍Hibernate二级缓存的概念、作用以及所需jar包的作用。 一、Hibernate二级缓存概念 Hibernate的一级缓存是指Session级别的缓存,每个Session内部都有一个一级缓存,用于存储实体对象,当Session关闭时...
本篇文章将深入探讨Hibernate二级缓存的概念、工作原理以及如何在实际项目中设置和使用。 **一、二级缓存概念** 一级缓存是每个Hibernate Session内部的一个内存区域,用于存储Session期间的操作对象。当Session...
本压缩包提供的资源应该包含了实现Hibernate二级缓存所需的关键组件和库文件。 一级缓存是Hibernate Session内的缓存,它是每个Session实例独有的,当Session关闭时,一级缓存中的数据也会被清除。而二级缓存则是一...
本文将深入探讨Hibernate的一级缓存、二级缓存以及查询缓存,通过具体的实例来阐述它们的工作原理和使用方法。 首先,我们从一级缓存开始。一级缓存是Hibernate默认提供的缓存,它是每个Session级别的,也被称为...
标题“hibernate二级缓存(包括注解方式)”指出了本文将探讨的是Hibernate框架中的二级缓存机制,并且会涉及使用注解的方式进行配置。Hibernate是一个流行的对象关系映射(ORM)框架,它允许开发者在Java应用中使用...
这里它被用作Hibernate二级缓存的实现方式,这意味着当数据首次从数据库中读取后,会被存储在memcached中,后续请求可以直接从缓存中获取,避免了频繁的数据库交互,从而提高了系统的响应速度。 **知识点详解:** ...
Hibernate二级缓存是一个优化策略,它能提高数据访问性能,减少对数据库的直接访问。 首先,Struts2作为MVC(模型-视图-控制器)框架,负责处理用户请求和转发到相应的业务逻辑。Spring框架则提供了依赖注入和事务...