试用Hibernate二级缓存Ehcache

  Hibernate supports several third-party caching services.  After some research, the team has concluded that Ehcache would be suitable for our requirements, namely the caching of frequently-accessed, read-only tables.

 

Ehcache Evaluation

 

I spent the last couple of days evaluating Ehcache.  I installed the following products locally:

 

  • Hibernate 3.3.2 GA
  • Ehcache 1.2.3 (an optional JAR bundled with Hibernate 3.3.2)
  • Hibernate Annotations 3.4.0 GA

 

I was able to create and test a standalone application using Hibernate/Ehcache in conjunction with SQL Server 2005.  I populated the SQL server database with several thousand records.

 

My test application reads all of the records in the database in several ways:

 

1.  Directly by primary key.

2.  Using a single Query to return a large result set.

3.  Using a series of individual Queries that return small result sets.

 

I used the test program to analyze performance with and without caching.  Caching resulted in substantial improvement; however, I learned that optimal performance requires that:

 

1.  Entities referenced in the transaction are designated as cached.

2.  Associations that are traversed are designated as cached.

3.  Queries are cached.

 

The remainder of this document details the installation and configuration steps needed to enable caching in the Dialogs product.

 

Installation

It is necessary to add the Ehcache JAR to the classpath.   Ehcache JAR is bundled inside the Hibernate distribution JAR in the /optional/ehcache folder:

Ehcache Install

 

Ehcache requires additional third-party JARs; however, it is likely that these additional JARs are already on the Dialogs classpath because they are also required by Hibernate and/or Spring Framework. 

 

Here is the complete set of JARs I used for my local tests:

 


  Ehcache Libs


Configuration

 Successful implementation of Ehcache requires that:

 

  1. Hibernate is configured to enable caching.
  2. An ehcache.xml file is created and placed on the classpath.
  3. Model classes are updated to add @Cache annotations to entities and associations.
  4. Hibernate Query instances are explicitly designated as cacheable before execution.

 

Configuring Hibernate for Caching

Hibernate must be configured to enable caching.  This can be done either through parameters in hibernate.properties or through alternative approaches offered by Spring Framework.

 

Regardless of how they are defined, the following additional properties must be added to enable caching:

 

 

cache.provider_class=org.hibernate.cache.EhCacheProvider

hibernate.cache.use_query_cache=true 
 

 

The following properties are optional, but I recommend that we add them for testing:

 

 

hibernate.generate_statistics=true

hibernate.cache.use_structured_entries=true

show_sql=true 
 

 

In particular, property show_sql=true is helpful for tracing cache behavior.  This setting causes Hibernate to display all SQL statements on the system console.

 

Configuring ehcache.xml

Document ehcache.xml contains caching policies.  For example, it allows you to specify how many instances of a given entity will be retained in cache, the conditions that will cause those instances to be evicted.

 

To obtain optimal caching performance, this file may need to be tuned over time, and its contents may vary based on individual customers’ needs to balance memory utilization and performance.

 

Here is a minimal ehcache.xml file to start:

 

 

<ehcache>

    <defaultCache
            maxElementsInMemory="10000"
            eternal="true"
            overflowToDisk="false"
            memoryStoreEvictionPolicy="FIFO"
            />

</ehcache> 
 

 

This minimal setup will apply the same caching rules to all entities.  Depending on the results of our testing, we may later enhance the ehcache.xml file with one entry for each reference class to be cached to allow granular control 

 

Caching Entities

Model classes can be divided into two broad categories as depicted below:

 

 

  Database ralation

For the purpose of this initial exercise, caching should be limited to reference entities, which are read-only from the standpoint of the transaction being optimized.  All reference entities that are accessed directly or indirectly via method QuestionnarieManager findDisplayQuestion should be designated as cached, particularly the following entities:

 

  • Questionnaire (q_dialog)
  • QuestionSetInNaire (q_dialog_page)
  • QuestionSet (q_page)
  • QuestionInSet (q_page_question)
  • Question (q_question)

 

After reviewing the java logic and the imbedded queries, I believe that this is the complete set of reference entities addressed in method findDisplayQuestion.  If there are any others that have escaped my attention, please designate them as cached as well.

 

Some of the queries in findDisplayQuestion join reference and instance tables.  Instance tables that are joined in this manner include:

 

  • Response (q_response)
  • DisplayFlagRecord (q_displayflag)
  • Dialog (q_dialoginstance)

 

In this initial exercise, we intend to avoid caching instance entities .  We may at a later time decide to enable caching instance entities, but read-write caching will require more research and testing.

 

In order to designate an entity as cached, it is necessary to add annotation @Cache to the model class definition.  Example annotation for entity Questionnaire:

 

@Entity
@Table(name = "q_dialog", uniqueConstraints...)
@Cache(usage = CacheConcurrencyStrategy.READ_ONLY)
public class Questionnaire extends QuestionBase implements Serializable 
 

 

Usage CacheConcurrencyStrategy.READ_ONLY should be applied in all cases.  This annotation will cause Hibernate/Ehcache to cache the entity.

 

Caching Collections

Ehcache has the ability to cache association collections, namely those collections that implement 1-to-many or many-to-many associations between entities.  When associations are cached, the Ehcache will bypass the database when an association getter method is called; otherwise, invoking an association get method will trigger in a database SELECT.

 

I recommend that we designate the following associations as cached:

 

  • Questionnaire.getQuestionSetsInNaire()
  • QuestionSet.getQuestionsInSet()
  • QuestionSet.getQuestionsSetInNaires()
  • Question.getQuestionInSets()

 

To enable collection caching, it is necessary to add annotation @Cache to the “getter” method for the association collection.  Example for entity Questionnaire:

 

 

@OneToMany(cascade = CascadeType.ALL, fetch ...)
@OrderBy("sequence asc")
@Cache(usage = CacheConcurrencyStrategy.READ_ONLY)
public Set<QuestionSetInNaire> getQuestionSetsInNaire() 
 

 

This change will cause Ehcache to cache collection Questionnaire questionSetInNaire.

 

Caching Queries

For our initial effort, I recommend that we cache all queries and verify that there are no adverse effects.  In my testing, Query caching made a significant difference.

 

In order for Hibernate to cache a query it is necessary for the Hibernate Query instance to be flagged as cacheable via method Query.setCacheable(true).

 

I’ve learned that Spring Framework’s HibernateTemplate doesn’t enable caching by default.  Some Google searches yielded this solution:

 

 

<bean id="hibernateTemplate" class="org.springframework.orm.hibernate3.HibernateTemplate">
   <property name="sessionFactory">
      <ref bean="sessionFactory" />
   </property>
   <property name="cacheQueries">
      <value>true</value>
   </property>
</bean> 
 

 

I believe that these specifications will cause the HibernateTemplate class to generate cached Query instances This should effectively enable query caching systemwide. 

 

My knowledge of Spring Framework is limited, so please take this recommendation with a grain of salt.  There are a variety of approaches to solve this problem.  Whichever approach you ultimately use, please verify that Hibernate Query (i.e., QueryImpl) instances are in fact being created with the cacheable property set to true .  You should be able to verify this by single-stepping through the HibernateTemplate with a debugger.

 

Testing

You can verify that caching is taking effect in a number of ways.   When caching is enabled, you should notice a reduction in the number of SQL statements displayed on the server console via Hibernate property show_sql=true .

 

Alternatively, if you have a profiling tool such as YourKit, you can monitor the SQL that is sent to the database.  We’re planning to perform such testing over the wire once the caching enhancements are deployed on the US Dialogs Server.

 

You can also verify that caching is in effect by analyzing data in the performance log:

 

1.  Invoke a test questionnaire using the web client.

2.  Enter any response to the first question on the test questionnaire.

3.  Change the original response to something different.

4.  Reinstate the original response.

 

Check the log file.  If caching has been successfully enabled, there should be a measurable drop in the number of milliseconds required to find the display question (log entry [Finding display question]).  Our most recent tests, show in the range of 250-300 milliseconds.We hope that caching will cause this number to drop significantly.

Trial

1,hibernate.properties

#hibernate.dialect =org.hibernate.dialect.OracleDialect
hibernate.dialect = org.hibernate.dialect.PostgreSQLDialect
#hibernate.dialect = org.hibernate.dialect.SQLServerDialect
#hibernate.hbm2ddl.auto = update
#hibernate.generate_statistics = false
#hibernate.show_sql =false

hibernate.cache.provider_class=org.hibernate.cache.EhCacheProvider
hibernate.cache.use_query_cache=true

hibernate.generate_statistics=true
hibernate.show_sql =false
hibernate.cache.use_structured_entries=true
 2,ehcache.xml
<ehcache xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="ehcache.xsd" updateCheck="true" monitoring="autodetect">
    <!--
    Mandatory Default Cache configuration. These settings will be applied to caches
    created programmtically using CacheManager.add(String cacheName).

    The defaultCache has an implicit name "default" which is a reserved cache name.
    -->
    <defaultCache maxElementsInMemory="10000" eternal="true" overflowToDisk="false" memoryStoreEvictionPolicy="FIFO"/>
</ehcache>
 3,model example,Questionnaire.java
@NamedQueries( { @NamedQuery(name = "Questionnaire.findMaxRefId", query = "select max(qn.refId) from Questionnaire qn"),
		@NamedQuery(name = "Questionnaire.findMaxVersionByRefId", query = "select max(qn.version) from Questionnaire qn where qn.refId = :refId") })
@Entity
@Table(name = "q_dialog", uniqueConstraints = { @UniqueConstraint(columnNames = { "ID", "VERSION" }) })
@Cache(usage = CacheConcurrencyStrategy.READ_ONLY)
public class Questionnaire extends QuestionBase implements Serializable {
...............................................
}
 4,Query cache,
    Old:
public String getResponseLabel(String value) {
		List<ResponseEntry> lresponseEntity = this.getHibernateTemplate().find("from ResponseEntry as a");
		for (int i = 0; i < lresponseEntity.size(); i++) {
			ResponseEntry r = lresponseEntity.get(i);
			if (r.getValue().equals(value)) {
				return r.getLabel();
			}
		}
		return "";
	}
   New:
public String getResponseLabel(String value) {
		String sql = "from ResponseEntry as a";
		Query query = this.getSession().createQuery(sql);
		query.setCacheable(true);
		List<ResponseEntry> lresponseEntity = query.list();
		for (int i = 0; i < lresponseEntity.size(); i++) {
			ResponseEntry r = lresponseEntity.get(i);
			if (r.getValue().equals(value)) {
				return r.getLabel();
			}
		}
		return "";
	}
 5, Depth Optimization
    Old:
public DisplayFlagRecord findByPageQuestionAndDialogInstance(QuestionInSet pageQuestion, Dialog dialogInstance) {
		String hql = "from DisplayFlagRecord where pageQuestion = :pageQuestion and dialogInstance = :dialogInstance";
		Query query = this.getSession().createQuery(hql);
		query.setCacheable(true);
		
		
		List<DisplayFlagRecord> records = query.setEntity("pageQuestion", pageQuestion).setEntity("dialogInstance", dialogInstance).list();
		if (records != null && records.size()>0) {
			return records.get(0);
		}
		return null;
	}
    New:
public DisplayFlagRecord findByPageQuestionAndDialogInstance(QuestionInSet pageQuestion, Dialog dialogInstance) {
		Iterator listDisplayFlagRecords = dialogInstance.getDisplayFlagRecords().iterator();
		while (listDisplayFlagRecords.hasNext()) {
			DisplayFlagRecord lDisplayFlagRecord = (DisplayFlagRecord) listDisplayFlagRecords.next();
			if (lDisplayFlagRecord.getPageQuestion() == pageQuestion)
				return lDisplayFlagRecord;
		} // Response could not be found:
		return null;
}
private List<Response> findRespose(Long pageInQuestionId, Set aSetResponses,String responseValue) {
		List<Response> lResponses = new ArrayList();
		Iterator listResponses = aSetResponses.iterator();
		while (listResponses.hasNext()) {
			Response lResponse = (Response) listResponses.next();			
			if (lResponse.getQuestion().getId() == pageInQuestionId && lResponse.getValue().equals(responseValue)){
				lResponses.add(lResponse);
			}
				 
		} // Response could not be found:
		return lResponses;
	}
  
If any question I will put in this thread.
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值