91
81

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 5 years have passed since last update.

Ehcache 使い方メモ

Last updated at Posted at 2015-06-25

#Ehcache とは
キャッシュ機能を提供する Java のライブラリ。読みは「いーえいちきゃっしゅ」。

Java におけるキャッシュライブラリのデファクトスタンダードらしく、 JSR-107(JCache) の実装でもある(ただし、ver2 を JCache として利用する場合は追加モジュールが必要になるそうです1)。
オープンソースで、ライセンスは Apache ライセンス ver 2。

元は Greg Luck という人が開発していたが、後に Terracotta, Inc. という会社がプロジェクトを買収し、以後はこの会社が開発を進めている。
Terracotta, Inc. は Software AG という会社の子会社で、 Terracotta BigMemory というデータストアの製品を開発している。
このため Ehcache は Terracotta BigMemory 向けの有償機能を多数用意している。

基本はメモリ上に保存しておきつつ、ローカルディスクに退避させることもできる。
レプリケーションやトランザクションの機能とかも提供してくれる。

2015/06/26 現在、 ver 3 の開発が GitHub で行われている。
(ver 3 からは、 JCache の機能がデフォルトで組み込まれる予定らしい)

#インストール

build.gradle
    compile 'net.sf.ehcache:ehcache:2.10.0'

Maven Repository: net.sf.ehcache » ehcache » 2.10.0

#Hello World

Main.java
package sample.ehcache;

import net.sf.ehcache.Cache;
import net.sf.ehcache.CacheManager;
import net.sf.ehcache.Element;

public class Main {
    
    public static void main(String[] args) {
        CacheManager manager = CacheManager.getInstance();
        
        try {
            manager.addCache("myCache");
            Cache myCache = manager.getCache("myCache");
            myCache.put(new Element("hoge", "HOGE"));
            
            myCache = manager.getCache("myCache");
            Element element = myCache.get("hoge");
            System.out.println("myCache.hoge = " + element.getObjectValue());
        } finally {
            manager.shutdown();
        }
    }
}
実行結果
myCache.hoge = HOGE
  • まず、 CacheManager#getInstance() で、 CacheManager のインスタンスを取得する。
    • CacheManager のインスタンスはシングルトンで管理されていて、 getInstance() は常に同じインスタンスを返す。
  • CacheManager#addCache(String) で、キャッシュを追加できる。
  • CacheManager#getCache(String) で、キャッシュを取得できる。
  • キャッシュ(Cache)には Element と呼ばれるキーバリューを持つオブジェクトを登録できる。
    • 登録には、 Cache#put(Element) を使う。
  • Cache#get(String) で、該当するキーを持つ Element を取得できる。
  • Element#getObjectValue() で、 Element が持つ値を取得できる。
  • CacheManager は、最後に shutdown() する。

#ストレージ
Ehcache では、次の3つのストレージを利用できる。

  • Memory store
    • Java のヒープメモリ。通常はここが使用される。
    • シリアライズできないオブジェクトでも保存できる。
  • Off-heap store
    • メモリ上だが、ここに保存されたデータは GC の対象にならない。
    • デフォルトでは使用できず、 Terracotta BigMemory を組み合わせる必要がある。
    • シリアライズ可能なオブジェクトのみ保存できる。
  • Disk store
    • ファイルなどの、メモリ外のストレージ。
    • メモリに収まりきらないときなどに、退避場所として利用できる。
    • シリアライズ可能なオブジェクトのみ保存できる。

#設定ファイル
キャッシュのサイズなどの設定は、 xml で記述する。

xml は ehcache.xml という名前で作成し、クラスパスの直下に配置する。

##キャッシュを定義する

ehcache.xml
<?xml version="1.0" encoding="UTF-8"?>
<ehcache xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:noNamespaceSchemaLocation="http://ehcache.org/ehcache.xsd">
  
  <cache name="myCache"
         maxEntriesLocalHeap="0">
  </cache>
  
</ehcache>
Main.java
package sample.ehcache;

import net.sf.ehcache.Cache;
import net.sf.ehcache.CacheManager;

public class Main {
    
    public static void main(String[] args) {
        CacheManager manager = CacheManager.getInstance();
        
        try {
            Cache cache = manager.getCache("myCache");
            System.out.println(cache);
        } finally {
            manager.shutdown();
        }
    }
}
コンソール出力
[ name = myCache status = STATUS_ALIVE eternal = false overflowToDisk = false maxEntriesLocalHeap = 0 maxEntriesLocalDisk = 0 memoryStoreEvictionPolicy = LRU timeToLiveSeconds = 0 timeToIdleSeconds = 0 persistence = none diskExpiryThreadIntervalSeconds = 120 cacheEventListeners: ; orderedCacheEventListeners:  maxBytesLocalHeap = 0 overflowToOffHeap = false maxBytesLocalOffHeap = 0 maxBytesLocalDisk = 0 pinned = false ]
  • xml は、トップレベルに <ehcache> タグを宣言する。
  • キャッシュの定義には、 <cache> タグを使用する。
    • name 属性でキャッシュの名前を定義する。
      • 一意な名前を付ける必要がある。
    • maxEntriesLocalHeap 属性で、ヒープに保存できる Element の数を指定する。
      • 0 を指定したら、無制限になる。

##ヒープに保存できる Element の個数を指定する

ehcache.xml
<?xml version="1.0" encoding="UTF-8"?>
<ehcache xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:noNamespaceSchemaLocation="http://ehcache.org/ehcache.xsd">

  <cache name="myCache"
         maxEntriesLocalHeap="3">
  </cache>
  
</ehcache>
Main.java
package sample.ehcache;

import net.sf.ehcache.Cache;
import net.sf.ehcache.CacheManager;
import net.sf.ehcache.Element;

public class Main {
    
    public static void main(String[] args) {
        CacheManager manager = CacheManager.getInstance();
        
        try {
            Cache cache = manager.getCache("myCache");
            
            cache.put(new Element("one", "ONE"));
            cache.put(new Element("two", "TWO"));
            cache.put(new Element("three", "THREE"));
            cache.put(new Element("four", "FOUR"));
            
            System.out.println(cache.get("one"));
            System.out.println(cache.get("two"));
            System.out.println(cache.get("three"));
            System.out.println(cache.get("four"));
        } finally {
            manager.shutdown();
        }
    }
}
実行結果
null
[ key = two, value=TWO, version=1, hitCount=1, CreationTime = 1434633777588, LastAccessTime = 1434633777588 ]
[ key = three, value=THREE, version=1, hitCount=1, CreationTime = 1434633777588, LastAccessTime = 1434633777588 ]
[ key = four, value=FOUR, version=1, hitCount=1, CreationTime = 1434633777588, LastAccessTime = 1434633777588 ]
  • maxEntriesLocalHeap で、ヒープに保存できる Element の個数を指定できる。
  • 指定した個数をオーバーした場合は、保存済みの Element のどれかが破棄される。
    • どれが破棄されるかは、キャッシュアルゴリズムの設定による(詳細後述)。

##登録してから一定時間過ぎたら破棄されるようにする

ehcache.xml
<?xml version="1.0" encoding="UTF-8"?>
<ehcache xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:noNamespaceSchemaLocation="http://ehcache.org/ehcache.xsd">

  <cache name="myCache"
         maxEntriesLocalHeap="0"
         timeToLiveSeconds="1">
  </cache>
  
</ehcache>
Main.java
package sample.ehcache;

import net.sf.ehcache.Cache;
import net.sf.ehcache.CacheManager;
import net.sf.ehcache.Element;

public class Main {
    
    public static void main(String[] args) {
        CacheManager manager = CacheManager.getInstance();
        
        try {
            Cache cache = manager.getCache("myCache");
            
            put(cache, "hoge", "HOGE");
            
            sleep(500);
            
            print(cache, "hoge");
            
            sleep(600);
            
            print(cache, "hoge");
        } finally {
            manager.shutdown();
        }
    }
    
    private static void put(Cache cache, String key, Object value) {
        cache.put(new Element(key, value));
    }
    
    private static void print(Cache cache, String key) {
        Element e = cache.get(key);
        System.out.println(key + " : " + (e == null ? "<none>" : e.getObjectValue()));
    }
    
    private static void sleep(long ms) {
        try {
            Thread.sleep(ms);
            System.out.println("> " + ms + " ms...");
        } catch (InterruptedException e) {}
    }
}
実行結果
> 500 ms...
hoge : HOGE
> 600 ms...
hoge : <none>
  • timeToLiveSeconds で、 Element をキャッシュに登録してから破棄されるまでの生存時間を指定できる(秒指定)。
  • 0 なら無制限。

##最後にアクセスしてから一定時間過ぎたら破棄されるようにする

ehcache.xml
<?xml version="1.0" encoding="UTF-8"?>
<ehcache xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:noNamespaceSchemaLocation="http://ehcache.org/ehcache.xsd">

  <cache name="myCache"
         maxEntriesLocalHeap="0"
         timeToIdleSeconds="1">
  </cache>
  
</ehcache>
Main.java
package sample.ehcache;

import net.sf.ehcache.Cache;
import net.sf.ehcache.CacheManager;
import net.sf.ehcache.Element;

public class Main {
    
    public static void main(String[] args) {
        CacheManager manager = CacheManager.getInstance();
        
        try {
            Cache cache = manager.getCache("myCache");
            
            put(cache, "hoge", "HOGE");
            
            sleep(500);
            
            print(cache, "hoge");
            
            sleep(600);
            
            print(cache, "hoge");
            
            sleep(1010);
            
            print(cache, "hoge");
        } finally {
            manager.shutdown();
        }
    }
    
    private static void put(Cache cache, String key, Object value) {
        cache.put(new Element(key, value));
    }
    
    private static void print(Cache cache, String key) {
        Element e = cache.get(key);
        System.out.println(key + " : " + (e == null ? "<none>" : e.getObjectValue()));
    }
    
    private static void sleep(long ms) {
        try {
            Thread.sleep(ms);
            System.out.println("> " + ms + " ms...");
        } catch (InterruptedException e) {}
    }
}
実行結果
> 500 ms...
hoge : HOGE
> 600 ms...
hoge : HOGE
> 1010 ms...
hoge : <none>
  • timeToIdleSeconds で、最後にアクセスしてから破棄されるまでの生存時間を指定できる(秒指定)。
  • 0 なら無制限。

##ローカルのテンポラリファイルに保存する
###基本

ehcache.xml
<?xml version="1.0" encoding="UTF-8"?>
<ehcache xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:noNamespaceSchemaLocation="http://ehcache.org/ehcache.xsd">
  
  <cache name="myCache"
         maxEntriesLocalHeap="0">
    <persistence strategy="localTempSwap" />
  </cache>
  
</ehcache>
Main.java
package sample.ehcache;

import net.sf.ehcache.Cache;
import net.sf.ehcache.CacheManager;
import net.sf.ehcache.Element;
import net.sf.ehcache.statistics.StatisticsGateway;

public class Main {
    
    public static void main(String[] args) {
        CacheManager manager = CacheManager.getInstance();
        
        try {
            Cache cache = manager.getCache("myCache");
            
            for (int i=0; i<1000; i++) {
                put(cache, "key-" + i, "value-" + i);
            }

            printCacheSize(cache);
            
            sleep(5);

            printCacheSize(cache);
            
            sleep(5);

            printCacheSize(cache);
            
            sleep(500);
            
            printCacheSize(cache);
            
        } finally {
            manager.shutdown();
        }
    }

    private static void printCacheSize(Cache cache) {
        StatisticsGateway statistics = cache.getStatistics();
        
        System.out.println(
            "======================\n" + 
            "heap size = " + statistics.getLocalHeapSize() + "\n" +
            "disk size = " + statistics.getLocalDiskSize() + "\n"
        );
    }
    
    private static void put(Cache cache, String key, Object value) {
        cache.put(new Element(key, value));
    }
    
    private static void sleep(long ms) {
        try {
            Thread.sleep(ms);
            System.out.println("> " + ms + " ms...");
        } catch (InterruptedException e) {}
    }
}
実行結果
======================
heap size = 1000
disk size = 279

> 5 ms...
======================
heap size = 1000
disk size = 344

> 5 ms...
======================
heap size = 1000
disk size = 424

> 500 ms...
======================
heap size = 1000
disk size = 1000
  • <cache> タグの子供に <persistence> タグを追加し、 strategy="localTempSwap" と属性を指定する。
  • localTempSwap を指定すると、ローカルにテンポラリファイルが作成され、そちらにもキャッシュが保存されるようになる。
  • テンポラリファイルは、 JVM が終了すると削除される(永続化はされない)。
  • storategy には、他にも localRestartable とか distributed を指定できる。
    • localRestartable は、キャッシュ情報が永続化されるため、再起動してもキャッシュが生き残るっぽい(たぶん)。
    • ただし、この属性はエンタープライズ版でないと利用できない。
  • 保存先を明示しない場合、 System.getProperty("java.io.tmpdir") で取得されたフォルダ配下にテンポラリファイルが作成される。
  • キャッシュに保存した Element は、全てテンポラリファイルにも出力される。
    • 「メモリストアがいっぱいになったら」ではないっぽい。
    • でも、 ehcache.xml のサンプル とかには Swaps cache entries to disk when the cache is full. (キャッシュがいっぱいになったら、ディスクにキャッシュのエントリをスワップする) とか書いてる。
    • 何か勘違いしているのか。それとも、どこか設定が間違っているのか。。。 わからん。。。
  • テンポラリファイルへの出力は 非同期 で行われる。
    • <persistence> タグには synchronousWrites という属性が設定できる。
    • しかし、 strategy="localTempSwap" としている場合は false しか指定できない。

###メモリストアの上限値を設定した場合

ehcache.xml
<?xml version="1.0" encoding="UTF-8"?>
<ehcache xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:noNamespaceSchemaLocation="http://ehcache.org/ehcache.xsd">
    
  <cache name="myCache"
         maxEntriesLocalHeap="120">
    <persistence strategy="localTempSwap" />
  </cache>
  
</ehcache>
Main.java
package sample.ehcache;

import java.util.function.Supplier;

import net.sf.ehcache.Cache;
import net.sf.ehcache.CacheManager;
import net.sf.ehcache.Element;
import net.sf.ehcache.statistics.StatisticsGateway;

public class Main {
    
    public static void main(String[] args) {
        CacheManager manager = CacheManager.getInstance();
        
        try {
            Cache cache = manager.getCache("myCache");
            
            for (int i=0; i<1000; i++) {
                put(cache, "key-" + i, "value-" + i);
            }
            
            sleep(100);

            printStatistics(cache);
            
            print(cache, "key-0");

            printStatistics(cache);
            
            print(cache, "key-900");

            printStatistics(cache);
            
            print(cache, "key-900");

            printStatistics(cache);
            
        } finally {
            manager.shutdown();
        }
    }

    private static void printStatistics(Cache cache) {
        StatisticsGateway statistics = cache.getStatistics();
        
        System.out.println(
            "======================\n" + 
            "heap size = " + statistics.getLocalHeapSize() + "\n" +
            "heap hit  = " + statistics.localHeapHitCount() + "\n" +
            "disk size = " + statistics.getLocalDiskSize() + "\n" +
            "disk hit  = " + statistics.localDiskHitCount() + "\n"
        );
    }
    
    private static void print(Cache cache, String key) {
        printWithWhereFrom(cache, () -> {
            Element e = cache.get(key);
            return key + " : " + (e == null ? "<none>" : e.getObjectValue());
        });
    }
    
    private static void printWithWhereFrom(Cache cache, Supplier<String> sup) {
        StatisticsGateway statistics = cache.getStatistics();
        long heapHit = statistics.localHeapHitCount();
        
        String msg = sup.get();
        
        boolean isHeapHit = heapHit != statistics.localHeapHitCount();
        
        System.out.println(msg + " (from " + (isHeapHit ? "heap" : "disk") + ")");
    }
    
    private static void put(Cache cache, String key, Object value) {
        cache.put(new Element(key, value));
    }
    
    private static void sleep(long ms) {
        try {
            Thread.sleep(ms);
            System.out.println("> " + ms + " ms...");
        } catch (InterruptedException e) {}
    }
}
実行結果
> 100 ms...
======================
heap size = 120
heap hit  = 0
disk size = 1000
disk hit  = 0

key-0 : value-0 (from heap)
======================
heap size = 120
heap hit  = 1
disk size = 1000
disk hit  = 0

key-900 : value-900 (from disk)
======================
heap size = 120
heap hit  = 1
disk size = 1000
disk hit  = 1

key-900 : value-900 (from heap)
======================
heap size = 120
heap hit  = 2
disk size = 1000
disk hit  = 1
  • メモリストアには、指定した上限値までだけが保存されている。
  • テンポラリファイルには、全てのデータが保存されている。
  • ファイルから読み取ったデータはメモリストアに格納され、次回アクセス時はメモリストアから取り出すようになる。

###ディスクに保存できる Element の数を指定する

ehcache.xml
<?xml version="1.0" encoding="UTF-8"?>
<ehcache xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:noNamespaceSchemaLocation="http://ehcache.org/ehcache.xsd">
    
  <cache name="myCache"
         maxEntriesLocalHeap="120"
         maxEntriesLocalDisk="500">
    <persistence strategy="localTempSwap" />
  </cache>
  
</ehcache>
Main.java
package sample.ehcache;

import net.sf.ehcache.Cache;
import net.sf.ehcache.CacheManager;
import net.sf.ehcache.Element;
import net.sf.ehcache.statistics.StatisticsGateway;

public class Main {
    
    public static void main(String[] args) {
        CacheManager manager = CacheManager.getInstance();
        
        try {
            Cache cache = manager.getCache("myCache");
            
            for (int i=0; i<1000; i++) {
                put(cache, "key-" + i, "value-" + i);
            }
            
            sleep(1000);
            
            printCacheSize(cache);
            
        } finally {
            manager.shutdown();
        }
    }

    private static void printCacheSize(Cache cache) {
        StatisticsGateway statistics = cache.getStatistics();
        
        System.out.println(
            "======================\n" + 
            "heap size = " + statistics.getLocalHeapSize() + "\n" +
            "disk size = " + statistics.getLocalDiskSize() + "\n"
        );
    }
    
    private static void put(Cache cache, String key, Object value) {
        cache.put(new Element(key, value));
    }
    
    private static void sleep(long ms) {
        try {
            Thread.sleep(ms);
            System.out.println("> " + ms + " ms...");
        } catch (InterruptedException e) {}
    }
}
実行結果
> 1000 ms...
======================
heap size = 120
disk size = 500
  • maxEntriesLocalDisk 属性で、ディスクに保存できる Element の上限を指定できる。

###テンポラリファイルの作成先を指定する

ehcache.xml
<?xml version="1.0" encoding="UTF-8"?>
<ehcache xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:noNamespaceSchemaLocation="http://ehcache.org/ehcache.xsd">
  
  <diskStore path="F:\tmp\ehcache"/>
  
  <cache name="myCache"
         maxEntriesLocalHeap="120"
         maxEntriesLocalDisk="500">
    <persistence strategy="localTempSwap" />
  </cache>
  
</ehcache>
  • <diskStore> タグの path 属性でテンポラリファイルの保存先を指定できる。

##キャッシュアルゴリズムを指定する
キャッシュの容量がいっぱいになった状態で新しい Element が追加された場合、既に登録されている Element のうちどれかが破棄されることになる。
どの Element を破棄するかを決めるルールとして、キャッシュアルゴリズムを指定することができる。

次の実装を使って、 Ehcache がサポートしている3種類のキャッシュアルゴリズムの動作を確認してみる。

Main.java
package sample.ehcache;

import net.sf.ehcache.Cache;
import net.sf.ehcache.CacheManager;
import net.sf.ehcache.Element;

public class Main {
    
    public static void main(String[] args) {
        CacheManager manager = CacheManager.getInstance();
        
        try {
            Cache cache = manager.getCache("myCache");
            
            putEach(cache, 3);
            
            printEach(cache, 4);
            
            print(cache, "key-1"); print(cache, "key-1");
            print(cache, "key-0"); print(cache, "key-0");
            print(cache, "key-2");
            
            put(cache, "key-3", "value-3");
            
            printEach(cache, 4);
            
        } finally {
            manager.shutdown();
        }
    }
    
    private static void put(Cache cache, String key, Object value) {
        cache.put(new Element(key, value));
    }
    
    private static void putEach(Cache cache, int size) {
        for (int i = 0; i < size; i++) {
            cache.put(new Element("key-" + i, "value-" + i));
        }
    }
    
    private static void print(Cache cache, String key) {
        Element e = cache.get(key);
        
        String value =
            (e == null) ? "<none>"
                        : "{ value=" + e.getObjectValue() +
                          ", LastAccessTime=" + e.getLastAccessTime() +
                          ", HitCount=" + e.getHitCount() + " }";
        
        System.out.println(key + " : " + value);
        sleep(1);
    }
    
    private static void printEach(Cache cache, int size) {
        System.out.println("\n================");
        for (int i = 0; i < size; i++) {
            print(cache, "key-" + i);
        }
        System.out.println();
    }
    
    private static void sleep(long ms) {
        try {
            Thread.sleep(ms);
        } catch (InterruptedException e) {}
    }
}

###LRU (Least Recently Used)

ehcache.xml
<?xml version="1.0" encoding="UTF-8"?>
<ehcache xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:noNamespaceSchemaLocation="http://ehcache.org/ehcache.xsd">
    
  <cache name="myCache"
         maxEntriesLocalHeap="3"
         memoryStoreEvictionPolicy="LRU">
  </cache>
  
</ehcache>
実行結果
================
key-0 : { value=value-0, LastAccessTime=1434806550091, HitCount=1 }
key-1 : { value=value-1, LastAccessTime=1434806550093, HitCount=1 }
key-2 : { value=value-2, LastAccessTime=1434806550094, HitCount=1 }
key-3 : <none>

key-1 : { value=value-1, LastAccessTime=1434806550096, HitCount=2 }
key-1 : { value=value-1, LastAccessTime=1434806550097, HitCount=3 }
key-0 : { value=value-0, LastAccessTime=1434806550098, HitCount=2 }
key-0 : { value=value-0, LastAccessTime=1434806550099, HitCount=3 }
key-2 : { value=value-2, LastAccessTime=1434806550100, HitCount=2 }

================
key-0 : { value=value-0, LastAccessTime=1434806550101, HitCount=4 }
key-1 : <none>
key-2 : { value=value-2, LastAccessTime=1434806550103, HitCount=3 }
key-3 : { value=value-3, LastAccessTime=1434806550104, HitCount=1 }
  • memoryStoreEvictionPolicy 属性で、キャッシュアルゴリズムを指定できる。
  • LRU は、参照されてから最も時間が経過している Element が削除対象になる。
  • 未指定の場合は、デフォルトで LRU になる。

###FIFO (First In First Out)

ehcache.xml
<?xml version="1.0" encoding="UTF-8"?>
<ehcache xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:noNamespaceSchemaLocation="http://ehcache.org/ehcache.xsd">
    
  <cache name="myCache"
         maxEntriesLocalHeap="3"
         memoryStoreEvictionPolicy="FIFO">
  </cache>
  
</ehcache>
実行結果
================
key-0 : { value=value-0, LastAccessTime=1434806573077, HitCount=1 }
key-1 : { value=value-1, LastAccessTime=1434806573078, HitCount=1 }
key-2 : { value=value-2, LastAccessTime=1434806573079, HitCount=1 }
key-3 : <none>

key-1 : { value=value-1, LastAccessTime=1434806573081, HitCount=2 }
key-1 : { value=value-1, LastAccessTime=1434806573082, HitCount=3 }
key-0 : { value=value-0, LastAccessTime=1434806573083, HitCount=2 }
key-0 : { value=value-0, LastAccessTime=1434806573084, HitCount=3 }
key-2 : { value=value-2, LastAccessTime=1434806573085, HitCount=2 }

================
key-0 : <none>
key-1 : { value=value-1, LastAccessTime=1434806573087, HitCount=4 }
key-2 : { value=value-2, LastAccessTime=1434806573088, HitCount=3 }
key-3 : { value=value-3, LastAccessTime=1434806573089, HitCount=1 }
  • FIFO は、先に保存したものから順番に削除されていく。
  • 先入れ先出し。

###LFU (Least Frequently Used)

ehcache.xml
<?xml version="1.0" encoding="UTF-8"?>
<ehcache xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:noNamespaceSchemaLocation="http://ehcache.org/ehcache.xsd">
    
  <cache name="myCache"
         maxEntriesLocalHeap="3"
         memoryStoreEvictionPolicy="LFU">
  </cache>
  
</ehcache>
実行結果
================
key-0 : { value=value-0, LastAccessTime=1434806629835, HitCount=1 }
key-1 : { value=value-1, LastAccessTime=1434806629836, HitCount=1 }
key-2 : { value=value-2, LastAccessTime=1434806629837, HitCount=1 }
key-3 : <none>

key-1 : { value=value-1, LastAccessTime=1434806629839, HitCount=2 }
key-1 : { value=value-1, LastAccessTime=1434806629840, HitCount=3 }
key-0 : { value=value-0, LastAccessTime=1434806629841, HitCount=2 }
key-0 : { value=value-0, LastAccessTime=1434806629842, HitCount=3 }
key-2 : { value=value-2, LastAccessTime=1434806629843, HitCount=2 }

================
key-0 : { value=value-0, LastAccessTime=1434806629844, HitCount=4 }
key-1 : { value=value-1, LastAccessTime=1434806629845, HitCount=4 }
key-2 : <none>
key-3 : { value=value-3, LastAccessTime=1434806629847, HitCount=1 }
  • LFU は、最も参照頻度の低いモノから先に削除される。

##設定ファイルを指定して読み込む

my-ehcache.xml
<?xml version="1.0" encoding="UTF-8"?>
<ehcache xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:noNamespaceSchemaLocation="http://ehcache.org/ehcache.xsd">
      
  <cache name="myCache"
         maxEntriesLocalHeap="0"
         timeToIdleSeconds="10">
  </cache>
</ehcache>
Main.java
package sample.ehcache;

import net.sf.ehcache.Cache;
import net.sf.ehcache.CacheManager;
import net.sf.ehcache.config.CacheConfiguration;

public class Main {
    
    public static void main(String[] args) {
        CacheManager manager = CacheManager.create("./my-ehcache.xml");
        
        try {
            Cache cache = manager.getCache("myCache");
            CacheConfiguration config = cache.getCacheConfiguration();
            
            System.out.println(
                "MaxEntriesLocalHeap : " + config.getMaxEntriesLocalHeap() + "\n" +
                "TimeToIdleSeconds : " + config.getTimeToIdleSeconds()
            );
            
        } finally {
            manager.shutdown();
        }
    }
}
実行結果
MaxEntriesLocalHeap : 0
TimeToIdleSeconds : 10
  • CacheManager#create(String) で、ローカルに置いている任意の設定ファイルをパス指定で読み込むことができる。
  • InputStream を受け取ることもできる。

#Cache の操作
##Element を上書きする

Main.java
package sample.ehcache;

import net.sf.ehcache.Cache;
import net.sf.ehcache.CacheManager;
import net.sf.ehcache.Element;

public class Main {
    
    public static void main(String[] args) {
        CacheManager manager = CacheManager.getInstance();
        
        try {
            manager.addCache("myCache");
            
            Cache myCache = manager.getCache("myCache");
            myCache.put(new Element("hoge", "HOGE"));
            
            String value = (String)myCache.get("hoge").getObjectValue();
            System.out.println("myCache.hoge = " + value);
            
            myCache.put(new Element("hoge", "hogeee"));
            
            System.out.println("myCache.hoge = " + myCache.get("hoge").getObjectValue());
        } finally {
            manager.shutdown();
        }
    }
}
実行結果
myCache.hoge = HOGE
myCache.hoge = hogeee
  • 同じキーの Elementput() すれば、 Element を上書きできる。

##Element を削除する

Main.java
package sample.ehcache;

import net.sf.ehcache.Cache;
import net.sf.ehcache.CacheManager;
import net.sf.ehcache.Element;

public class Main {
    
    public static void main(String[] args) {
        CacheManager manager = CacheManager.getInstance();
        
        try {
            manager.addCache("myCache");
            
            Cache myCache = manager.getCache("myCache");
            myCache.put(new Element("hoge", "HOGE"));
            
            String value = (String)myCache.get("hoge").getObjectValue();
            System.out.println("myCache.hoge = " + value);
            
            myCache.remove("hoge");
            
            System.out.println("myCache.hoge = " + myCache.get("hoge"));
        } finally {
            manager.shutdown();
        }
    }
}
実行結果
myCache.hoge = HOGE
myCache.hoge = null
  • Cache#remove() で、指定したキーの Element を削除できる。

#Read Overload
##Thundering Herd 問題
例えば、データベース検索の結果をキャッシュに保存しておき、二回目以降同じキーで検索があれば、キャッシュに保存されたデータを返すようにしたとする。

実装にすると、以下のようなイメージ。

    private static String search(String key) {
        Element e = cache.get(key);
        
        if (e != null) {
            return (String)e.getObjectKey();
        }
        
        String value = database.get(key);
        cache.put(new Element(key, value));
        
        return value;
    }

この方法は、マルチスレッドで複数のアクセスがあった場合に問題が発生する。
もし複数のスレッドがこのメソッドを実行すると、タイミングによっては複数のスレッドが「まだキャッシュにデータが保存されていない」と判断してしまい、データベースアクセスが必要以上に発生してしまう可能性がある。

この問題を「Thundering Herd 問題」と言う。

synchronized で同期する回避策も考えられるが、後述するレプリケーションを使っていたりすると複数の JVM に跨った同期が必要になるので、 synchronized では対応しきれなくなる。

この問題の解決策として、 Ehcache には BlockingCache というデコレータクラスが用意されている。

##まずは問題があるケースの実装

Main.java
package sample.ehcache;

import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.stream.IntStream;

import net.sf.ehcache.Cache;
import net.sf.ehcache.CacheManager;
import net.sf.ehcache.Element;

public class Main {

    private static Cache cache = CacheManager.getInstance().getCache("myCache");
    private static Map<String, String> dummyDatabase = new ConcurrentHashMap<>();
    static {
        dummyDatabase.put("hoge", "HOGE");
    }
    
    public static void main(String[] args) {
        CacheManager manager = CacheManager.getInstance();
        
        try {
            IntStream.range(0, 3)
                     .parallel()
                     .forEach(i -> search("hoge"));
        } finally {
            manager.shutdown();
        }
    }
    
    private static String search(String key) {
        Element e = cache.get(key);
        
        if (e != null) {
            printKeyValue(key, e.getObjectValue(), "cache");
            return (String)e.getObjectValue();
        }
        
        String value = dummyDatabase.get(key);
        cache.put(new Element(key, value));
        
        printKeyValue(key, value, "database");
        return value;
    }
    
    private static void printKeyValue(String key, Object value, String from) {
        System.out.println("key=" + key + ", value=" + value + " (from " + from + ")");
    }
}
実行結果
key=hoge, value=HOGE (from database)
key=hoge, value=HOGE (from database)
key=hoge, value=HOGE (from database)

全てデータベースからデータを取得してしまっている。
(データベースはダミー実装として Map を使用)

##BlockingCache を使う
###デコレーターファクトリを作成する

MyDecoratorFactory.java
package sample.ehcache;

import java.util.Properties;

import net.sf.ehcache.Ehcache;
import net.sf.ehcache.constructs.CacheDecoratorFactory;
import net.sf.ehcache.constructs.blocking.BlockingCache;

public class MyDecoratorFactory extends CacheDecoratorFactory {

    @Override
    public Ehcache createDecoratedEhcache(Ehcache cache, Properties properties) {
        System.out.println("create decoreated cache");
        return new BlockingCache(cache);
    }

    @Override
    public Ehcache createDefaultDecoratedEhcache(Ehcache cache, Properties properties) {
        return new BlockingCache(cache);
    }
}
  • まず、 CacheDecoratorFactory を継承したファクトリクラスを作成する。
  • とりあえず、それぞれのメソッドで BlockingCache のインスタンスを返すように実装する。

###設定ファイルでファクトリを指定する

ehcache.xml
<?xml version="1.0" encoding="UTF-8"?>
<ehcache xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:noNamespaceSchemaLocation="http://ehcache.org/ehcache.xsd">
    
  <cache name="myCache"
         maxEntriesLocalHeap="0">
    <cacheDecoratorFactory class="sample.ehcache.MyDecoratorFactory" />
  </cache>
  
</ehcache>
  • ファクトリを作成したら、設定ファイルで <cacheDecoratorFactory> タグを使ってファクトリを設定する。

###Ehcache に切り替え

Main.java
package sample.ehcache;

import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.stream.IntStream;

- import net.sf.ehcache.Cache;
+ import net.sf.ehcache.Ehcache;
import net.sf.ehcache.CacheManager;
import net.sf.ehcache.Element;

public class Main {

-   private static Cache cache = CacheManager.getInstance().getCache("myCache");
+   private static Ehcache cache = CacheManager.getInstance().getEhcache("myCache");
    private static Map<String, String> dummyDatabase = new ConcurrentHashMap<>();
    static {
        dummyDatabase.put("hoge", "HOGE");
    }
    
    ...
  • 利用する側は、 CacheManager#getEhcache()Ehcache オブジェクトを取得するようにする。

###動作確認

実行結果
create decoreated cache
key=hoge, value=HOGE (from database)
key=hoge, value=HOGE (from cache)
key=hoge, value=HOGE (from cache)
  • 初回だけデータベースアクセスになり、2回目以降はキャッシュからデータが取得されている。

#Write Overload
Read Overload のように永続化されているデータとアプリケーションの間にキャッシュを挟んだ場合、キャッシュが更新されたときに永続化されているデータにも変更を反映しなければならない。

永続化層に変更を反映するタイミングは、大きく分けて次の2パターンが考えられる。

  1. キャッシュへの書き込みと同時に永続化層への反映を行う。
  2. キャッシュへの書き込みの後で、しばらくしてから非同期で反映を行う。

前者は単純で分かりやすいが、永続化層への書き込みがキャッシュへの書き込みと同じスレッドで実行されてしまう。
そのため、せっかくキャッシュを使って高速化を図っているのに、結局永続化層への書き込みがボトルネックとなり得てしまう。

後者の場合、キャッシュへの書き込みが終わると処理はすぐに呼び出し元に返される。
書き込まれたデータは少し間を置いてから、別のスレッドで永続化層にまとめて書き出される。

永続化層への書き込みは別スレッドで非同期で実行されるので、呼び出し元が待たされることはない。
また、キャッシュへの複数回の書き込みを後でまとめて永続化層に出力するので、無駄なアクセスを減らすこともできるメリットがある。

Ehcache は、どちらの方法もサポートしている。

##基本
###CacheWriter を実装する

MyCacheWriter.java
package sample.ehcache;

import java.util.Arrays;
import java.util.Collection;
import java.util.stream.Collectors;

import net.sf.ehcache.CacheException;
import net.sf.ehcache.Element;
import net.sf.ehcache.writer.AbstractCacheWriter;

public class MyCacheWriter extends AbstractCacheWriter {

    @Override
    public void init() {
        print("init");
    }

    @Override
    public void dispose() throws CacheException {
        print("dispose");
    }

    @Override
    public void write(Element element) throws CacheException {
        print("write", Arrays.asList(element));
    }

    @Override
    public void writeAll(Collection<Element> elements) throws CacheException {
        print("writeAll", elements);
    }
    
    private void print(String method) {
        System.out.println("[" + Thread.currentThread().getName() + "] " + method);
    }
    
    private void print(String method, Collection<Element> elements) {
        print(method, elements.stream().map(this::toString).collect(Collectors.joining(", ", "[", "]")));
    }
    
    private String toString(Element e) {
        return "{key=" + e.getObjectKey() + ", value=" + e.getObjectValue() + "}";
    }
    
    private void print(String method, String msg) {
        System.out.println("[" + Thread.currentThread().getName() + "] " + method + " : " + msg);
    }
}
  • CacheWriter というインターフェースを実装する。
  • AbstractCacheWriter という抽象クラスが用意されている。
  • 実装は標準出力に情報を書き出しているだけだが、実際に利用するときは永続化層への書き込みを実装することになる。

###CacheWriterFactory を実装する

CacheWriterFactory.java
package sample.ehcache;

import java.util.Properties;

import net.sf.ehcache.Ehcache;
import net.sf.ehcache.writer.CacheWriter;
import net.sf.ehcache.writer.CacheWriterFactory;

public class MyCacheWriterFactory extends CacheWriterFactory {

    @Override
    public CacheWriter createCacheWriter(Ehcache cache, Properties properties) {
        return new MyCacheWriter();
    }
}
  • CacheWriterFactory を継承して作成する。
  • createCacheWriter() で、 CacheWriter のインスタンスを返すようにする。

###設定ファイルで CacheWriterFactory を登録する

ehcache.xml
<?xml version="1.0" encoding="UTF-8"?>
<ehcache xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:noNamespaceSchemaLocation="http://ehcache.org/ehcache.xsd">
  
  <cache name="myCache"
         maxEntriesLocalHeap="0">
    <cacheWriter>
      <cacheWriterFactory class="sample.ehcache.MyCacheWriterFactory" />
    </cacheWriter>
  </cache>
</ehcache>
  • <cache> タグの中に、 <cacheWriter><cacheWriterFactory> タグで指定する。

###動作確認

Main.java
package sample.ehcache;

import net.sf.ehcache.Cache;
import net.sf.ehcache.CacheManager;
import net.sf.ehcache.Element;

public class Main {
    
    public static void main(String[] args) {
        CacheManager manager = CacheManager.getInstance();
        
        try {
            Cache cache = manager.getCache("myCache");
            
            print("begin");
            
            put(cache, "one", "ONE");
            put(cache, "two", "TWO");
            put(cache, "three", "THREE");
            
            print("end");
        } finally {
            manager.shutdown();
        }
    }

    private static void put(Cache cache, String key, Object value) {
        cache.putWithWriter(new Element(key, value));
    }
    
    private static void print(String msg) {
        System.out.println("[" + Thread.currentThread().getName() + "] " + msg);
    }
}
実行結果
[main] init
[main] begin
[main] write : [{key=one, value=ONE}]
[main] write : [{key=two, value=TWO}]
[main] write : [{key=three, value=THREE}]
[main] end
[main] dispose
  • デフォルトは、呼び出し元と同じスレッドで、即座に CacheWriter のメソッドが呼び出される。
  • CacheWriter を使う場合は、キャッシュへの書き込みに Cache#putWithWriter() メソッドを使用する。

##非同期で書き込む

ehcach.xml
<?xml version="1.0" encoding="UTF-8"?>
<ehcache xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:noNamespaceSchemaLocation="http://ehcache.org/ehcache.xsd">
  
  <cache name="myCache"
         maxEntriesLocalHeap="0">
    <cacheWriter writeMode="write-behind">
      <cacheWriterFactory class="sample.ehcache.MyCacheWriterFactory" />
    </cacheWriter>
  </cache>
</ehcache>
実行結果
[main] init
[main] begin
[main] end
[myCache write-behind] write : [{key=one, value=ONE}]
[myCache write-behind] write : [{key=two, value=TWO}]
[myCache write-behind] write : [{key=three, value=THREE}]
[main] dispose
  • <cacheWriter> タグの writeMode 属性で write-behind を指定する。
  • すると、 CacheWriter のメソッドの呼び出しが非同期になる。

##いくつかの Element をまとめて書き出す

ehcache.xml
<?xml version="1.0" encoding="UTF-8"?>
<ehcache xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:noNamespaceSchemaLocation="http://ehcache.org/ehcache.xsd">
  
  <cache name="myCache"
         maxEntriesLocalHeap="0">
    <cacheWriter writeMode="write-behind"
                 writeBatching="true"
                 writeBatchSize="2">
      <cacheWriterFactory class="sample.ehcache.MyCacheWriterFactory" />
    </cacheWriter>
  </cache>
</ehcache>
実行結果
[main] init
[main] begin
[main] end
[myCache write-behind] writeAll : [{key=one, value=ONE}, {key=two, value=TWO}]
[myCache write-behind] writeAll : [{key=three, value=THREE}]
[main] dispose
  • writeBatching 属性で true を指定すると、 CacheWriterwriteAll() メソッドが呼び出されるようになり、更新のあった複数の Element がまとめて渡されるようになる。
  • writeBatchSize 属性で、いくつの Element を同時に処理するかを指定する。デフォルトは 1

##最後の更新だけを処理する

ehcache.xml
<?xml version="1.0" encoding="UTF-8"?>
<ehcache xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:noNamespaceSchemaLocation="http://ehcache.org/ehcache.xsd">
  
  <cache name="myCache"
         maxEntriesLocalHeap="0">
    <cacheWriter writeMode="write-behind"
                 writeCoalescing="true">
      <cacheWriterFactory class="sample.ehcache.MyCacheWriterFactory" />
    </cacheWriter>
  </cache>
</ehcache>
Main.java
package sample.ehcache;

import net.sf.ehcache.Cache;
import net.sf.ehcache.CacheManager;
import net.sf.ehcache.Element;

public class Main {
    
    public static void main(String[] args) {
        CacheManager manager = CacheManager.getInstance();
        
        try {
            Cache cache = manager.getCache("myCache");
            
            print("begin");
            
            put(cache, "one", "ONE1");
            put(cache, "one", "ONE2");
            put(cache, "one", "ONE3");
            
            print("end");
        } finally {
            manager.shutdown();
        }
    }

    private static void put(Cache cache, String key, Object value) {
        cache.putWithWriter(new Element(key, value));
    }
    
    private static void print(String msg) {
        System.out.println("[" + Thread.currentThread().getName() + "] " + msg);
    }
}
実行結果
[main] init
[main] begin
[main] end
[myCache write-behind] write : [{key=one, value=ONE3}]
[main] dispose
  • writeCoalescing 属性に true を指定すると、同じキーに対する更新は、最後の更新分だけが CacheWriter に渡されるようになる。

#レプリケーション
Ehcache には、標準で RMI を利用したレプリケーション機能が用意されている。
(RMI 以外にも、 JMS と JGroups のサポートもあるらしい)

RMI は、 Remote Method Invocation の略。
別の VM 上にいるオブジェクトのメソッドを遠隔実行するための仕組みで、 Java の標準 API の1つとして提供されている。
TCP の上で動作する。

##手順概略
RMI を利用したレプリケーションを使用する場合は、次の手順を踏むことになる。

  • Peer Provider の Factory を作成・設定する。
  • Peer Listener の Factory を作成・設定する。
  • Cache の EventListener に RMI レプリケーション用のリスナーを登録する。

##Peer Provider
連携相手を設定する。

マルチキャストを使った方法と、ホストを明示する方法の2つがある。
ここでは、ホストを明示する方法を試す。

ehcach.xml
<?xml version="1.0" encoding="UTF-8"?>
<ehcache xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:noNamespaceSchemaLocation="http://ehcache.org/ehcache.xsd">
      
  <cacheManagerPeerProviderFactory
      class="net.sf.ehcache.distribution.RMICacheManagerPeerProviderFactory"
      properties="peerDiscovery=manual,
                  rmiUrls=//localhost:5002/myCache"
      />
  
  <cache name="myCache"
         maxEntriesLocalHeap="0">
  </cache>
</ehcache>
  • <cacheManagerPeerProviderFactory> タグを使用する。
  • class には、組み込みクラスである RMICacheManagerPeerProviderFactory を指定する。
  • properties で、連携相手についての設定を記述する。
    • peerDiscovery は、 automaticmanual のいずれかを指定する。
      • ここではホストを明示するので、 manual を指定する。
    • rmiUrls には、連携相手のホストを特定するための設定を記述する。
      • //<host>:<port>/<cache name>
      • 縦線 | で複数指定可能。

##Peer Listener
連携の入り口を定義する。

ehcache.xml
<?xml version="1.0" encoding="UTF-8"?>
<ehcache xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:noNamespaceSchemaLocation="http://ehcache.org/ehcache.xsd">
      
  <cacheManagerPeerProviderFactory
      class="net.sf.ehcache.distribution.RMICacheManagerPeerProviderFactory"
      properties="peerDiscovery=manual,
                  rmiUrls=//localhost:5002/myCache"
      />
  
+ <cacheManagerPeerListenerFactory
+     class="net.sf.ehcache.distribution.RMICacheManagerPeerListenerFactory"
+     properties="hostName=localhost, port=5001"
+     />
  
  <cache name="myCache"
         maxEntriesLocalHeap="0">
  </cache>
</ehcache>
  • <cacheManagerPeerListenerFactory> タグで定義する。
  • class には、組み込みクラスである RMICacheManagerPeerListenerFactory を指定する。
  • properties で、連携の入り口を定義する。

##Event Listener を登録する
キャッシュイベントを監視して、変更があればレプリケーション相手に通知するためのリスナーを登録する。

ehcache.xml
<?xml version="1.0" encoding="UTF-8"?>
<ehcache xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:noNamespaceSchemaLocation="http://ehcache.org/ehcache.xsd">
      
  <cacheManagerPeerProviderFactory
      class="net.sf.ehcache.distribution.RMICacheManagerPeerProviderFactory"
      properties="peerDiscovery=manual,
                  rmiUrls=//localhost:5002/myCache"
      />
  
  <cacheManagerPeerListenerFactory
      class="net.sf.ehcache.distribution.RMICacheManagerPeerListenerFactory"
      properties="hostName=localhost, port=5001"
      />
  
  <cache name="myCache"
         maxEntriesLocalHeap="0">
+   <cacheEventListenerFactory
+       class="net.sf.ehcache.distribution.RMICacheReplicatorFactory" />
  </cache>
</ehcache>
  • <cache> タグの中に、 <cacheEventListenerFactory> タグで登録する。
  • class には、組み込みクラスである RMICacheReplicatorFactory を指定する。

##動作確認
以上の設定をするだけで、レプリケーションは可能になる。

試しに簡単なプログラムを作ったので、それで動作確認をする。

上記ページから ehrep.jar をダウンロードする。

###実行する
コマンドラインを2つ立ち上げて、それぞれで下記のように jar を実行する。

sub1
$ java -jar ehrep.jar sub1
SLF4J: Failed to load class "org.slf4j.impl.StaticLoggerBinder".
SLF4J: Defaulting to no-operation (NOP) logger implementation
SLF4J: See http://www.slf4j.org/codes.html#StaticLoggerBinder for further details.
> 1000 ms...
> 1000 ms...
> 1000 ms...
sub2
$ java -jar ehrep.jar sub2
SLF4J: Failed to load class "org.slf4j.impl.StaticLoggerBinder".
SLF4J: Defaulting to no-operation (NOP) logger implementation
SLF4J: See http://www.slf4j.org/codes.html#StaticLoggerBinder for further details.
> 1000 ms...
> 1000 ms...
> 1000 ms...

sub1 または sub2 を引数に渡して実行すると、 1 秒ごとにキャッシュにアクセスして、レプリケーションが実行されているか確認を始める。

次に、もう1つコマンドラインを立ち上げて、下記のように jar を実行する。

main
$ java -jar ehrep.jar main "Hello Replication!!"
SLF4J: Failed to load class "org.slf4j.impl.StaticLoggerBinder".
SLF4J: Defaulting to no-operation (NOP) logger implementation
SLF4J: See http://www.slf4j.org/codes.html#StaticLoggerBinder for further details.
[main] put msg("Hello Replication!!") into cache

第1引数に main を渡し、第2引数に文字列を渡す。
第2引数で指定した文字列は、 msg というキーでキャッシュに保存される。

すると、先に立ち上げていた2つのプログラムが、以下のように変化する。

sub1
...
> 1000 ms...
> 1000 ms...
[sub1] msg : Hello Replication!!
sub2
...
> 1000 ms...
> 1000 ms...
[sub2] msg : Hello Replication!!

main の方でキャッシュに保存した情報が sub1sub2 にレプリケーションされ、キャッシュから取得したメッセージが出力されている。

具体的な実装は こちら を参照。
設定ファイルは こっち

#キャッシュを動的に作成する

Main.java
package sample.ehcache;

import net.sf.ehcache.Cache;
import net.sf.ehcache.CacheManager;
import net.sf.ehcache.config.CacheConfiguration;

public class Main {
    
    public static void main(String[] args) {
        CacheManager manager = CacheManager.getInstance();
        
        try {
            manager.addCache("dynamicCache");
            
            Cache cache = manager.getCache("dynamicCache");
            CacheConfiguration config = cache.getCacheConfiguration();
            
            System.out.println(
                "MaxElementsInMemory = " + config.getMaxElementsInMemory() + "\n" +
                "TimeToIdleSeconds = " + config.getTimeToIdleSeconds() + "\n" +
                "MaxElementsOnDisk = " + config.getMaxElementsOnDisk()
            );
            
        } finally {
            manager.shutdown();
        }
    }
}
実行結果
MaxElementsInMemory = 10000
TimeToIdleSeconds = 120
MaxElementsOnDisk = 10000000
  • CacheManager#addCache(String) メソッドで、キャッシュを動的に追加することができる。
  • 自作の ehcache.xml がクラスパス上に存在しない場合、この方法で作成したキャッシュの設定値には、 ehcache-x.x.x.jar の中に入っている ehcache-failsafe.xml が使用される。
ehcache-failsafe.xml
<ehcache xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="../config/ehcache.xsd">
    <diskStore path="java.io.tmpdir"/>

    <defaultCache
            maxElementsInMemory="10000"
            eternal="false"
            timeToIdleSeconds="120"
            timeToLiveSeconds="120"
            maxElementsOnDisk="10000000"
            diskExpiryThreadIntervalSeconds="120"
            memoryStoreEvictionPolicy="LRU">
        <persistence strategy="localTempSwap"/>
    </defaultCache>
</ehcache>
  • この <defaultCache> の設定が適用されている。

##動的に作成したキャッシュのデフォルト値を指定する

ehcache.xml
<?xml version="1.0" encoding="UTF-8"?>
<ehcache xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:noNamespaceSchemaLocation="http://ehcache.org/ehcache.xsd">
  
  <defaultCache maxEntriesLocalHeap="30">
  </defaultCache>

</ehcache>
Main.java
package sample.ehcache;

import net.sf.ehcache.Cache;
import net.sf.ehcache.CacheManager;
import net.sf.ehcache.config.CacheConfiguration;

public class Main {
    
    public static void main(String[] args) {
        CacheManager manager = CacheManager.getInstance();
        
        try {
            manager.addCache("dynamicCache");
            
            Cache cache = manager.getCache("dynamicCache");
            CacheConfiguration config = cache.getCacheConfiguration();
            
            System.out.println("MaxEntriesLocalHeap = " + config.getMaxEntriesLocalHeap());
            
        } finally {
            manager.shutdown();
        }
    }
}
実行結果
MaxEntriesLocalHeap = 30
  • ehcache.xml を作成して、 <defaultCache> タグを記述すれば、デフォルト値を設定できる。

##動的に設定値を指定する

Main.java
package sample.ehcache;

import net.sf.ehcache.Cache;
import net.sf.ehcache.CacheManager;
import net.sf.ehcache.config.CacheConfiguration;

public class Main {
    
    public static void main(String[] args) {
        CacheManager manager = CacheManager.getInstance();
        
        try {
            CacheConfiguration config = new CacheConfiguration();
            config.setName("dynamicCache");
            config.setMaxEntriesLocalHeap(12);
            
            manager.addCache(new Cache(config));
            
            
            Cache cache = manager.getCache("dynamicCache");
            CacheConfiguration conf = cache.getCacheConfiguration();
            
            System.out.println("MaxEntriesLocalHeap = " + conf.getMaxEntriesLocalHeap());
            
        } finally {
            manager.shutdown();
        }
    }
}
実行結果
MaxEntriesLocalHeap = 12
  • CacheConfiguration のインスタンスを作成することで、設定値を動的に指定できる。
  • CacheConfiguration インスタンスは、 Cache インスタンスを作成するときのコンストラクタ引数に渡す。

#トランザクション
Ehcache には、トランザクションのサポートがついている。
グローバルトランザクションにも対応している。

トランザクションの分離レベルは、 READ COMMITTED。

##ローカルトランザクション

ehcache.xml
<?xml version="1.0" encoding="UTF-8"?>
<ehcache xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:noNamespaceSchemaLocation="http://ehcache.org/ehcache.xsd">
  
  <cache name="myCache"
         maxEntriesLocalHeap="0"
         transactionalMode="local">
  </cache>
  
</ehcache>
Main.java
package sample.ehcache;

import net.sf.ehcache.Cache;
import net.sf.ehcache.CacheManager;
import net.sf.ehcache.Element;
import net.sf.ehcache.TransactionController;

public class Main {
    
    public static void main(String[] args) {
        CacheManager manager = CacheManager.getInstance();
        
        try {
            Cache cache = manager.getCache("myCache");
            
            withTransaction(() -> {
                put(cache, "hoge", "HOGE");
            });
            
            withTransaction(() -> {
                print(cache, "hoge");
            });

            withTransaction(() -> {
                put(cache, "fuga", "FUGA");
                throw new RuntimeException("test");
            });
            
            withTransaction(() -> {
                print(cache, "fuga");
            });
        } finally {
            manager.shutdown();
        }
    }
    
    private static void withTransaction(Runnable func) {
        TransactionController tx = CacheManager.getInstance().getTransactionController();
        try {
            tx.begin();
            
            func.run();
            
            tx.commit();
        } catch (Exception e) {
            tx.rollback();
        }
    }

    private static void put(Cache cache, String key, Object value) {
        cache.put(new Element(key, value));
    }

    private static void print(Cache cache, String key) {
        Element e = cache.get(key);
        System.out.println(key + " : " + (e == null ? "<none>" : e.getObjectValue()));
    }
}
実行結果
hoge : HOGE
fuga : <none>
  • 自前でトランザクションを制御する場合は、 <cache>transactionalMode 属性で local を指定する。
  • トランザクションの制御は、 TransactionController を使用する。
  • TransactionController のインスタンスあ、 CacheManager#getTransactionController() で取得できる。
  • TransactionController#begin() でトランザクションを開始する。
  • TransactionController#commit() でコミット、 rollback() でロールバックできる。
  • トランザクションが有効の場合、キャッシュから値を取得する場合もトランザクションが開始されている必要がある。
    • トランザクションが開始されていない状態でキャッシュから値を取得しようとすると、 TransactionException がスローされる。

##グローバルトランザクション
Ehcache はグローバルトランザクション(XA)をサポートしている。

また、特定の Java EE サーバー上で使用すれば、勝手に JNDI で TransactionManager を取得して、コンテナ管理のトランザクションに参加してくれる。

サポートしているサーバーは、 こちらのページ に記載されている。
GlassFish とか JBoss とか Weblogic とか、主要なサーバーはだいたいサポートされている。

###実装

ehcache.xml
<?xml version="1.0" encoding="UTF-8"?>
<ehcache xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:noNamespaceSchemaLocation="http://ehcache.org/ehcache.xsd">
  
  <cache name="myCache"
         maxEntriesLocalHeap="0"
         transactionalMode="xa_strict">
  </cache>
  
</ehcache>
  • transactionalModexa_strict を指定する。
SampleResource.java
package sample.ehcache.jta;

import javax.enterprise.context.RequestScoped;
import javax.inject.Inject;
import javax.ws.rs.GET;
import javax.ws.rs.Path;

@Path("/sample")
@RequestScoped
public class SampleResource {
    
    @Inject
    private SampleEjb ejb;
    
    @GET
    public String method() {
        this.ejb.withCommit("hoge", "HOGE");
        try {
            this.ejb.withRollback("fuga", "FUGA");
        } catch (Exception e) {/*ignore*/}
        
        return this.ejb.get("hoge") + "\n" + this.ejb.get("fuga");
    }
}
  • JAX-RS で入り口を作る。
  • /api/sample に GET でアクセスしたら、 SampleResource#method() が実行されるようにしている。
SampleEjb.java
package sample.ehcache.jta;

import javax.ejb.Stateless;

import net.sf.ehcache.Cache;
import net.sf.ehcache.CacheManager;
import net.sf.ehcache.Element;

@Stateless
public class SampleEjb {
    
    private Cache cache = CacheManager.getInstance().getCache("myCache");
    
    public void withCommit(String key, String value) {
        this.put(key, value);
    }
    
    public void withRollback(String key, String value) {
        this.put(key, value);
        throw new RuntimeException("test");
    }

    private void put(String key, Object value) {
        this.cache.put(new Element(key, value));
    }

    public void print(String key) {
        Element e = this.cache.get(key);
        System.out.println(key + " : " + (e == null ? "<none>" : e.getObjectValue()));
    }
}
  • withCommit() は単純にキャッシュに値を保存するだけ。
  • withRollback() は、キャッシュに値を保存した後に RuntimeException をスローする。

###実行結果
war に固めて、 GlassFish にデプロイする。
コンテキストパスは ehcache-jta になるようにデプロイする。

動作確認
$ curl http://localhost:8080/ehcache-jta/api/sample
hoge : HOGE
fuga : <none>

RuntimeException をスローした方はロールバックされており、 EJB の宣言的トランザクションが効いているのが分かる。

#参考

  1. JCache 1.0に対応した、EhcacheのJCacheモジュールを使ってみる - CLOVER

91
81
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
91
81

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?