Hibernate専用キャッシュを自作してみる
要件
最低限の要件を考えてみる。
- Hibernateの Cache 機構を使用する。
- Cache および CacheProvider インタフェースを実装する。
- キャッシュ利用中に、キャッシュ対象テーブルの内容が変更されることは無い。
- 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
- 実運用において、「このデータ変更する時ゃ間違いなく全マシン再起動だな」というようなシチュエーションでは、この程度のキャッシュでも十分かも。