`
高级java工程师
  • 浏览: 395837 次
  • 性别: Icon_minigender_1
  • 来自: 北京
社区版块
存档分类
最新评论

通过2级缓存加速程序

阅读更多
二级缓存来加快你的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缓存
分享到:
评论

相关推荐

    WordPress无名氏全站缓存加速插件

    WordPress加速插件秘笈:五款加速插件让您的网站飞起来,这是一款WordPress程序通用的缓存插件,可以将你的网站提升至毫秒级加载速度。 安装说明: 1:将缓存代码的PHP文件上传至网站根目录。 2:下面这行代码,放到...

    WordPress网站加速/无名氏全站缓存加速插件

    这款WordPress无名氏全站缓存加速插件,是在我的网站正在使用的,亲测无错,喜欢的朋友们可以下载使用! 这是一款所有程序通用的缓存插件,可以将你的网站提升至毫秒级加载速度。 不管你是WordPress程序还是其它...

    WordPress无名氏全站缓存加速插件.rar

    WordPress加速插件秘笈:五款加速插件让您的网站飞起来,这是一款WordPress程序通用的缓存插件,可以将你的网站提升至毫秒级加载速度。 安装说明: 1:将缓存代码的PHP文件上传至网站根目录。 2:下面这行代码,放...

    首页缓存加速插件(可用于支持asp网站) v1.0.rar

    但是相对于中小站长来说,他们很多人根本不懂的程序如何修改,如何去写,这就面临着一个问题:能否最简单的用上缓存或者静态。 昌邑网站长为大家提供一个精简的代码,简简单单的实现了首页缓存。缓存是数据写在...

    PHP缓存加速工具 eAccelerator v0.9.6.1 开源版.rar

    eAccelerator专门为PHP开发,是目前较为主流的可使用在PHP之中的缓存加速工具. eAccelerator的主要功能: 1. 缓存PHP文件的执行代码:在被缓存的代码再次被调用时,将直接从内存读取,从而在很大程度了PHP运行的...

    WordPress全站缓存加速插件,可以将你的网站提升至毫秒级加载速度,

    Wordpress无名氏全站缓存插件+让你网站快到飞起!WordPress加速插件秘笈:五款加速插件让您的网站飞起来,这是一款WordPress程序通用的缓存插件,可以将你的网站提升至毫秒级加载速度。

    磁盘高速缓存程序

    SMARTDRV.EXE 工具可以加速将文件从CD复制到硬盘驱动器的过程,要使用SMARTDRV.EXE 工具,请确保SMARTDRV.EXE文件位于启动盘上.

    laravel-responsecache, 通过缓存整个响应加快 Laravel 应用程序的速度.zip

    laravel-responsecache, 通过缓存整个响应加快 Laravel 应用程序的速度 通过缓存整个响应来加速应用程序 这个 Laravel 5.5包可以缓存整个响应。 默认情况下,它将缓存一个星期的所有成功的获取请求。 这可能会极大地...

    PHP程序加速探索之缓存输出

    缓存技术,这也是本文的重点部份。首先我们使用PEAR中的cache包。PEAR可以将内容缓存于文件,数据库或者内存中。

    硬盘缓存增强软件 PrimoCache Desktop Edition 3.0.2 中文多语免费版.zip

    PrimoCache基于双级缓存系统架构设计,该架构由一个一级缓存(level-1 cache)和一个二级缓存(level-2 cache)组成。一级缓存使用物理内存作为缓存设备,而二级缓存则通常使用SSD固态硬盘、闪存盘或其它永续性存储...

    Android 一键加速(内存清理、缓存清理) 源码

    http://download.csdn.net/detail/yyh352091626/9196107 基于 LeBron_Six 源码修改,先前的代码只能在Android studio上运行,这几天发了点时间移植到eclipse ,只留下了... // packageName是需要强制停止的应用程序包名

    learnzf2-route-cache:通过将缓存添加到路由匹配中来加快ZF2应用程序的速度

    简单的模块,可以使用缓存来加速路由匹配。 该模块是“学习ZF2”书( )的一部分,该书可以帮助您学习Zend Framework 2(ZF2)并提高ZF2应用程序的性能。 我们已经注意到,在实际应用中,路由可能需要300毫秒或更...

    bootscale:通过缓存要求调用来加速应用程序启动

    加速应用程序通过在需求调用期间缓存文件位置来启动。 速度增益取决于宝石的数量。 在100个以下的宝石中,您可能看不到差异,但是对于较大的应用程序,每使用100个宝石,它可以节省1-3秒的启动时间。 安装 # ...

    phpFastCache是一种高性能分布式的对象缓存系统

    phpFastCache是一种高性能,分布式的对象缓存系统,旨在用于通过缓存减轻数据库负载来加速动态Web应用程序

    laravel-responsecache:通过缓存整个响应来加速Laravel应用

    通过缓存整个响应来加速应用程序 Laravel软件包可以缓存整个响应。 默认情况下,它将缓存所有返回基于文本的内容(例如html和json)的成功的get请求一周。 这可能会大大加快响应速度。 因此,请求第一次进入程序包...

    开源HTTP反向代理缓存和时间序列仪表板加速器-Golang开发

    Delta Proxy Mo Trickster是HTTP应用程序的HTTP反向代理/缓存,以及时间序列数据库的仪表板查询加速器。 在下面了解更多信息,并查看我们的路线图以了解还有哪些工作要做。 HTTP反向代理缓存Trickster是功能齐全的...

    WIN下Nginx缓存加速配置方法

    Nginx.conf 程序代码 代码如下: worker_processes 1; events { worker_connections 1024; } http { include mime.types; include proxy.conf; default_type application/octet-stream; sendfile on; keepalive_time...

    PHP程序加速探索之加速工具软件

    PHP加速的另一个领域是缓存工具软件。这类软件都是从优化PHP运行环境来提速的,不需要改变任何代码。我们可以大概地将它们称为“执行码优化/缓存工具”,可以理解为它们用来实现比较底层的优化/缓存。本文列出目前...

    加速SSD硬盘-FlashPoint beta3

    我们知道EeePC类Netbook所用的Mini Pci-e接口的SSD固态硬盘是不带缓存的,这款软件软件的基本原理是通过在内存中为硬盘开辟一块24M或者32MB的缓存,从而实现加速的作用。 安装 直接在install.inf文件上单击右键...

Global site tag (gtag.js) - Google Analytics