Hibernate専用キャッシュを自作してみる

要件

最低限の要件を考えてみる。

  • Hibernateの Cache 機構を使用する。
  • キャッシュ利用中に、キャッシュ対象テーブルの内容が変更されることは無い。
  • readonlyモードでのみ使用される。
  • キャッシュ内のデータは蓄積され続ける。
  • キャッシュ内のデータは永遠に有効。
  • キャッシュ内のデータは OutOfMemory の原因になるような量にならないことが保証されている。
  • ディスクへの永続化は行わない。
  • メモリ上のサイズの計算はサポートせず、常に-1を返す。
  • CacheProvider#isMinimalPutsEnabledByDefault() の戻り値は false に固定。
  • 分散キャッシュは対応しない。

実装

CacheProvider実装

ソース

package jp.objectfanatics.ofcache.hibernate;

import java.util.Properties;

import org.hibernate.cache.Cache;
import org.hibernate.cache.CacheException;
import org.hibernate.cache.CacheProvider;
import org.hibernate.cache.Timestamper;

/**
 * OfCacheProvider
 * 
 * @author beyondseeker
 * 
 * $Id: OfCacheProvider.java 29 2008-05-08 14:54:18Z beyondseeker $
 * 
 */
public class OfCacheProvider implements CacheProvider {
	
	@Override
	public Cache buildCache(String regionName, Properties properties) throws CacheException {
		return new OfCache(regionName);
	}
	
	@Override
	public boolean isMinimalPutsEnabledByDefault() {
		return false;
	}
	
	@Override
	public long nextTimestamp() {
		return Timestamper.next();
	}
	
	@Override
	public void start(Properties properties) throws CacheException {
		// do nothing.
	}
	
	@Override
	public void stop() {
		// do nothing.
	}
	
}
Cache実装

ソース

package jp.objectfanatics.ofcache.hibernate;

import java.util.Collections;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;

import org.hibernate.cache.Cache;
import org.hibernate.cache.CacheException;
import org.hibernate.cache.Timestamper;

/**
 * OfCache
 * 
 * @author beyondseeker
 * 
 * $Id: OfCache.java 28 2008-05-08 14:52:50Z beyondseeker $
 * 
 */
public class OfCache implements Cache {
	
	private final Map<Object, Object> map = new ConcurrentHashMap<Object, Object>();
	
	private final Map<Object, Object> unmodifiableMap = Collections.unmodifiableMap(map);
	
	private final String regionName;
	
	public OfCache(String regionName) {
		this.regionName = regionName;
	}
	
	@Override
	public void clear() throws CacheException {
		this.map.clear();
	}
	
	@Override
	public void destroy() throws CacheException {
		// do nothing.
	}
	
	@Override
	public Object get(Object key) throws CacheException {
		return map.get(key);
	}
	
	@Override
	public long getElementCountInMemory() {
		return map.size();
	}
	
	@Override
	public long getElementCountOnDisk() {
		// Disk persistence is not supported.
		return 0;
	}
	
	@Override
	public String getRegionName() {
		return this.regionName;
	}
	
	@Override
	public long getSizeInMemory() {
		// not supported because this operation is very expensive.
		return -1;
	}
	
	@Override
	public int getTimeout() {
		return Timestamper.ONE_MS * 60000;
	}
	
	@Override
	public void lock(Object key) throws CacheException {
		throw new UnsupportedOperationException("Locking is not supported.");
	}
	
	@Override
	public long nextTimestamp() {
		return Timestamper.next();
	}
	
	@Override
	public void put(Object key, Object value) throws CacheException {
		this.map.put(key, value);
	}
	
	@Override
	public Object read(Object key) throws CacheException {
		return get(key);
	}
	
	@Override
	public void remove(Object key) throws CacheException {
		this.map.remove(key);
	}
	
	@Override
	public Map<Object, Object> toMap() {
		return unmodifiableMap;
	}
	
	@Override
	public void unlock(Object key) throws CacheException {
		throw new UnsupportedOperationException("Locking is not supported.");
	}
	
	@Override
	public void update(Object key, Object value) throws CacheException {
		put(key, value);
	}
	
}

考察

  • スゲーなんちゃって実装だが、とりあえず動くw
  • 要件を満たす最低限の機能を持つ実装。
  • 余計な機能が一切なくシンプルな実装のため、実は性能高いかもw
  • 実運用において、「このデータ変更する時ゃ間違いなく全マシン再起動だな」というようなシチュエーションでは、この程度のキャッシュでも十分かも。

追記

  • 分散キャッシュを対応しないのが前提なら、lockとunlockで何もしないようにしておけば、read-writeにも対応できますね。


Hibernate専用キャッシュを自作してみる(その2)