从应用角度看Hibernate源码(二):Hibernate缓存 HibernateCache.netJBossApache
Hibernate3.0以前,Hibernate的性能不咋地。运行一段程序慢的要死,而且还不够稳定。但运行Hibernate3后发现性能改良了不少。其中一个主要的原因也是缓存的优化处理。Hibernate缓存的处理放在了源码的org.hibernate.cache目录下。下面我们就谈一谈这个目录:
(1)Hibernate支持多少种缓存。在一般的程序员眼里,恐怕只知道,Hibernate只支持EhCache。但打开
这个目录,你会发现不是那么回事。Hibernate支持大部分的缓存技术。如还有OSCache,TreeCache等等。如果有时间你可以点着每个类看看。只要符合下面的样子就是一种缓存。
- public class XXXCache implements Cache {
- 。。。。。。
- }
这些缓存有些是Hibernate特有的,有些是第3方的。不过从另外的角度来说,缓存的策略基本都是一样的。打开Ehcache和Oscache比较一下就会发现。内容实现基本上一致的。因为基本都一致,所以当时开发Hibernate人员,认为加一是加,加十也是加。所以就都给加上了。这个只是我估计的。
(2)Cache的实现策略。以EhCache为例。实现第三方的缓存需要干两件事情。
一件是实现Cache接口,代码如下所示:
- //$Id: EhCache.java 10716 2006-11-03 19:05:11Z max.andersen@jboss.com $
- /**
- * Copyright 2003-2006 Greg Luck, Jboss Inc
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
- package org.hibernate.cache;
- import java.io.IOException;
- import java.util.HashMap;
- import java.util.Iterator;
- import java.util.Map;
- import net.sf.ehcache.CacheManager;
- import net.sf.ehcache.Element;
- import org.apache.commons.logging.Log;
- import org.apache.commons.logging.LogFactory;
- /**
- * EHCache plugin for Hibernate
-
*
- * EHCache uses a {@link net.sf.ehcache.store.MemoryStore} and a
- * {@link net.sf.ehcache.store.DiskStore}.
- * The {@link net.sf.ehcache.store.DiskStore} requires that both keys and values be {@link java.io.Serializable}.
- * However the MemoryStore does not and in ehcache-1.2 nonSerializable Objects are permitted. They are discarded
- * if an attempt it made to overflow them to Disk or to replicate them to remote cache peers.
- *
- * @author Greg Luck
- * @author Emmanuel Bernard
- */
- public class EhCache implements Cache {
- private static final Log log = LogFactory.getLog( EhCache.class );
- private static final int SIXTY_THOUSAND_MS = 60000;
- private net.sf.ehcache.Cache cache;
- /**
- * Creates a new Hibernate pluggable cache based on a cache name.
-
*
- *
- * @param cache The underlying EhCache instance to use.
- */
- public EhCache(net.sf.ehcache.Cache cache) {
- this.cache = cache;
- }
- /**
- * Gets a value of an element which matches the given key.
- *
- * @param key the key of the element to return.
- * @return The value placed into the cache with an earlier put, or null if not found or expired
- * @throws CacheException
- */
- public Object get(Object key) throws CacheException {
- try {
- if ( log.isDebugEnabled() ) {
- log.debug( "key: " + key );
- }
- if ( key == null ) {
- return null;
- }
- else {
- Element element = cache.get( key );
- if ( element == null ) {
- if ( log.isDebugEnabled() ) {
- log.debug( "Element for " + key + " is null" );
- }
- return null;
- }
- else {
- return element.getObjectValue();
- }
- }
- }
- catch (net.sf.ehcache.CacheException e) {
- throw new CacheException( e );
- }
- }
- public Object read(Object key) throws CacheException {
- return get( key );
- }
- /**
- * Puts an object into the cache.
- *
- * @param key a key
- * @param value a value
- * @throws CacheException if the {@link CacheManager}
- * is shutdown or another {@link Exception} occurs.
- */
- public void update(Object key, Object value) throws CacheException {
- put( key, value );
- }
- /**
- * Puts an object into the cache.
- *
- * @param key a key
- * @param value a value
- * @throws CacheException if the {@link CacheManager}
- * is shutdown or another {@link Exception} occurs.
- */
- public void put(Object key, Object value) throws CacheException {
- try {
- Element element = new Element( key, value );
- cache.put( element );
- }
- catch (IllegalArgumentException e) {
- throw new CacheException( e );
- }
- catch (IllegalStateException e) {
- throw new CacheException( e );
- }
- catch (net.sf.ehcache.CacheException e) {
- throw new CacheException( e );
- }
- }
- /**
- * Removes the element which matches the key.
-
*
- * If no element matches, nothing is removed and no Exception is thrown.
- *
- * @param key the key of the element to remove
- * @throws CacheException
- */
- public void remove(Object key) throws CacheException {
- try {
- cache.remove( key );
- }
- catch (ClassCastException e) {
- throw new CacheException( e );
- }
- catch (IllegalStateException e) {
- throw new CacheException( e );
- }
- catch (net.sf.ehcache.CacheException e) {
- throw new CacheException( e );
- }
- }
- /**
- * Remove all elements in the cache, but leave the cache
- * in a useable state.
- *
- * @throws CacheException
- */
- public void clear() throws CacheException {
- try {
- cache.removeAll();
- }
- catch (IllegalStateException e) {
- throw new CacheException( e );
- }
- catch (net.sf.ehcache.CacheException e) {
- throw new CacheException( e );
- }
- }
- /**
- * Remove the cache and make it unuseable.
- *
- * @throws CacheException
- */
- public void destroy() throws CacheException {
- try {
- cache.getCacheManager().removeCache( cache.getName() );
- }
- catch (IllegalStateException e) {
- throw new CacheException( e );
- }
- catch (net.sf.ehcache.CacheException e) {
- throw new CacheException( e );
- }
- }
- /**
- * Calls to this method should perform there own synchronization.
- * It is provided for distributed caches. Because EHCache is not distributed
- * this method does nothing.
- */
- public void lock(Object key) throws CacheException {
- }
- /**
- * Calls to this method should perform there own synchronization.
- * It is provided for distributed caches. Because EHCache is not distributed
- * this method does nothing.
- */
- public void unlock(Object key) throws CacheException {
- }
- /**
- * Gets the next timestamp;
- */
- public long nextTimestamp() {
- return Timestamper.next();
- }
- /**
- * Returns the lock timeout for this cache.
- */
- public int getTimeout() {
- // 60 second lock timeout
- return Timestamper.ONE_MS * SIXTY_THOUSAND_MS;
- }
- public String getRegionName() {
- return cache.getName();
- }
- /**
- * Warning: This method can be very expensive to run. Allow approximately 1 second
- * per 1MB of entries. Running this method could create liveness problems
- * because the object lock is held for a long period
-
*
- *
- * @return the approximate size of memory ehcache is using for the MemoryStore for this cache
- */
- public long getSizeInMemory() {
- try {
- return cache.calculateInMemorySize();
- }
- catch (Throwable t) {
- return -1;
- }
- }
- public long getElementCountInMemory() {
- try {
- return cache.getMemoryStoreSize();
- }
- catch (net.sf.ehcache.CacheException ce) {
- throw new CacheException( ce );
- }
- }
- public long getElementCountOnDisk() {
- return cache.getDiskStoreSize();
- }
- public Map toMap() {
- try {
- Map result = new HashMap();
- Iterator iter = cache.getKeys().iterator();
- while ( iter.hasNext() ) {
- Object key = iter.next();
- result.put( key, cache.get( key ).getObjectValue() );
- }
- return result;
- }
- catch (Exception e) {
- throw new CacheException( e );
- }
- }
- public String toString() {
- return "EHCache(" + getRegionName() + ')';
- }
- }
另外一件事情就是实现缓存提供者,实现CacheProvider接口。实现代码如下:
-
public class EhCacheProvider implements CacheProvider {
private static final Log log = LogFactory.getLog(EhCacheProvider.class);
private CacheManager manager;
/**
* Builds a Cache.
*
* Even though this method provides properties, they are not used.
* Properties for EHCache are specified in the ehcache.xml file.
* Configuration will be read from ehcache.xml for a cache declaration
* where the name attribute matches the name parameter in this builder.
*
* @param name the name of the cache. Must match a cache configured in ehcache.xml
* @param properties not used
* @return a newly built cache will be built and initialised
* @throws CacheException inter alia, if a cache of the same name already exists
*/
public Cache buildCache(String name, Properties properties) throws CacheException {
try {
net.sf.ehcache.Cache cache = manager.getCache(name);
if (cache == null) {
log.warn("Could not find configuration [" + name + "]; using defaults.");
manager.addCache(name);
cache = manager.getCache(name);
log.debug("started EHCache region: " + name);
}
return new EhCache(cache);
}
catch (net.sf.ehcache.CacheException e) {
throw new CacheException(e);
}
}/**
* Returns the next timestamp.
*/
public long nextTimestamp() {
return Timestamper.next();
}/**
* Callback to perform any necessary initialization of the underlying cache implementation
* during SessionFactory construction.
*
* @param properties current configuration settings.
*/
public void start(Properties properties) throws CacheException {
if (manager != null) {
log.warn("Attempt to restart an already started EhCacheProvider. Use sessionFactory.close() " +
" between repeated calls to buildSessionFactory. Using previously created EhCacheProvider." +
" If this behaviour is required, consider using net.sf.ehcache.hibernate.SingletonEhCacheProvider.");
return;
}
try {
String configurationResourceName = null;
if (properties != null) {
configurationResourceName = (String) properties.get( Environment.CACHE_PROVIDER_CONFIG );
}
if ( StringHelper.isEmpty( configurationResourceName ) ) {
manager = new CacheManager();
} else {
URL url = loadResource(configurationResourceName);
manager = new CacheManager(url);
}
} catch (net.sf.ehcache.CacheException e) {
//yukky! Don't you have subclasses for that!
//TODO race conditions can happen here
if (e.getMessage().startsWith("Cannot parseConfiguration CacheManager. Attempt to create a new instance of " +
"CacheManager using the diskStorePath")) {
throw new CacheException("Attempt to restart an already started EhCacheProvider. Use sessionFactory.close() " +
" between repeated calls to buildSessionFactory. Consider using net.sf.ehcache.hibernate.SingletonEhCacheProvider."
, e );
} else {
throw e;
}
}
}private URL loadResource(String configurationResourceName) {
URL url = ConfigHelper.locateConfig( configurationResourceName );
if (log.isDebugEnabled()) {
log.debug("Creating EhCacheProvider from a specified resource: "
+ configurationResourceName + " Resolved to URL: " + url);
}
return url;
}/**
* Callback to perform any necessary cleanup of the underlying cache implementation
* during SessionFactory.close().
*/
public void stop() {
if (manager != null) {
manager.shutdown();
manager = null;
}
}public boolean isMinimalPutsEnabledByDefault() {
return false;
} -
}
实现这个接口就可以在配置文件中配置,表示Hibernate要采用这种类型的缓存。通过这个缓存我们也可以学到很多的东西。如start()方法。这个方法注释告诉我们,缓存的加载是在SessionFactory创建时加载的。为什么要在这个时候加载,把缓存和SessionFactory绑在一起。因为SessionFactory是二级缓存,Cache的处理级别也是二级缓存。 所以他们就搞到一块去了。再看看stop()方法,这个方法的注释也告诉我们一个很重要的信息,如果启用了Hibernate的二级缓存。在SessionFactory的创建期间是不会停止的。只有在SessionFactory关闭后,才可以启用停止方法。为什么呢?因为SessionFatory是线程安全的,Cache随便停止,对SessionFactory线程安全性是致命的。