Ehcache 使い方メモ

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

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

元は Greg Luck という人が開発していたが、後に Terracotta, Inc. という会社がプロジェクトを買収し、以後はこの会社が開発を進めている。
Terracotta, Inc. は [Software AG](http://www.softwareag.com/jp/) という会社の子会社で、 [Terracotta BigMemory](http://www.softwareag.com/jp/products/terracotta/in-memory_data_mgmt/overview/default.asp) というデータストアの製品を開発している。
このため Ehcache は Terracotta BigMemory 向けの有償機能を多数用意している。

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

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

https://github.com/ehcache/ehcache3

#インストール
```groovy:build.gradle
    compile 'net.sf.ehcache:ehcache:2.10.0'
```

[Maven Repository: net.sf.ehcache » ehcache » 2.10.0](http://mvnrepository.com/artifact/net.sf.ehcache/ehcache/2.10.0)

#Hello World
```java: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();
        }
    }
}
```

```text:実行結果
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 では、次の３つのストレージを利用できる。

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

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

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

##キャッシュを定義する
```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>
```

```java: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();
        }
    }
}
```

```text:コンソール出力
[ 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 の個数を指定する
```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="3">
  </cache>
  
</ehcache>
```

```java: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();
        }
    }
}
```

```text:実行結果
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` のどれかが破棄される。
    - どれが破棄されるかは、キャッシュアルゴリズムの設定による（[詳細後述](#キャッシュアルゴリズムを指定する)）。

##登録してから一定時間過ぎたら破棄されるようにする
```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"
         timeToLiveSeconds="1">
  </cache>
  
</ehcache>
```

```java: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) {}
    }
}
```

```text:実行結果
> 500 ms...
hoge : HOGE
> 600 ms...
hoge : <none>
```

- `timeToLiveSeconds` で、 `Element` をキャッシュに登録してから破棄されるまでの生存時間を指定できる（秒指定）。
- `0` なら無制限。

##最後にアクセスしてから一定時間過ぎたら破棄されるようにする
```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"
         timeToIdleSeconds="1">
  </cache>
  
</ehcache>
```

```java: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) {}
    }
}
```

```text:実行結果
> 500 ms...
hoge : HOGE
> 600 ms...
hoge : HOGE
> 1010 ms...
hoge : <none>
```

- `timeToIdleSeconds` で、最後にアクセスしてから破棄されるまでの生存時間を指定できる（秒指定）。
- `0` なら無制限。

##ローカルのテンポラリファイルに保存する
###基本
```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">
    <persistence strategy="localTempSwap" />
  </cache>
  
</ehcache>
```

```java: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) {}
    }
}
```

```text:実行結果
======================
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 のサンプル](http://ehcache.org/ehcache.xml) とかには `Swaps cache entries to disk when the cache is full.` （キャッシュがいっぱいになったら、ディスクにキャッシュのエントリをスワップする） とか書いてる。
    - 何か勘違いしているのか。それとも、どこか設定が間違っているのか。。。　わからん。。。
- テンポラリファイルへの出力は **非同期** で行われる。
    - `<persistence>` タグには `synchronousWrites` という属性が設定できる。
    - しかし、 `strategy="localTempSwap"` としている場合は `false` しか指定できない。

###メモリストアの上限値を設定した場合
```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="120">
    <persistence strategy="localTempSwap" />
  </cache>
  
</ehcache>
```

```java: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) {}
    }
}
```

```text:実行結果
> 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 の数を指定する
```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="120"
         maxEntriesLocalDisk="500">
    <persistence strategy="localTempSwap" />
  </cache>
  
</ehcache>
```

```java: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) {}
    }
}
```

```text:実行結果
> 1000 ms...
======================
heap size = 120
disk size = 500
```

- `maxEntriesLocalDisk` 属性で、ディスクに保存できる `Element` の上限を指定できる。

###テンポラリファイルの作成先を指定する
```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">
  
  <diskStore path="F:\tmp\ehcache"/>
  
  <cache name="myCache"
         maxEntriesLocalHeap="120"
         maxEntriesLocalDisk="500">
    <persistence strategy="localTempSwap" />
  </cache>
  
</ehcache>
```

- `<diskStore>` タグの `path` 属性でテンポラリファイルの保存先を指定できる。

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

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

```java: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)
```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="3"
         memoryStoreEvictionPolicy="LRU">
  </cache>
  
</ehcache>
```

```text:実行結果
================
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)
```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="3"
         memoryStoreEvictionPolicy="FIFO">
  </cache>
  
</ehcache>
```

```text:実行結果
================
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)
```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="3"
         memoryStoreEvictionPolicy="LFU">
  </cache>
  
</ehcache>
```

```text:実行結果
================
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` は、最も参照頻度の低いモノから先に削除される。

##設定ファイルを指定して読み込む
```xml: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>
```

```java: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();
        }
    }
}
```

```text:実行結果
MaxEntriesLocalHeap : 0
TimeToIdleSeconds : 10
```

- `CacheManager#create(String)` で、ローカルに置いている任意の設定ファイルをパス指定で読み込むことができる。
- `InputStream` を受け取ることもできる。

#Cache の操作
##Element を上書きする
```java: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();
        }
    }
}
```

```text:実行結果
myCache.hoge = HOGE
myCache.hoge = hogeee
```

- 同じキーの `Element` を `put()` すれば、 `Element` を上書きできる。

##Element を削除する
```java: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();
        }
    }
}
```

```text:実行結果
myCache.hoge = HOGE
myCache.hoge = null
```

- `Cache#remove()` で、指定したキーの `Element` を削除できる。

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

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

```java:
    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` というデコレータクラスが用意されている。

##まずは問題があるケースの実装
```java: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 + ")");
    }
}
```

```text:実行結果
key=hoge, value=HOGE (from database)
key=hoge, value=HOGE (from database)
key=hoge, value=HOGE (from database)
```

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

##BlockingCache を使う
###デコレーターファクトリを作成する
```java: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` のインスタンスを返すように実装する。

###設定ファイルでファクトリを指定する
```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">
    <cacheDecoratorFactory class="sample.ehcache.MyDecoratorFactory" />
  </cache>
  
</ehcache>
```

- ファクトリを作成したら、設定ファイルで `<cacheDecoratorFactory>` タグを使ってファクトリを設定する。

###Ehcache に切り替え
```diff: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` オブジェクトを取得するようにする。

###動作確認
```text:実行結果
create decoreated cache
key=hoge, value=HOGE (from database)
key=hoge, value=HOGE (from cache)
key=hoge, value=HOGE (from cache)
```

- 初回だけデータベースアクセスになり、２回目以降はキャッシュからデータが取得されている。

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

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

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

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

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

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

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

##基本
###CacheWriter を実装する
```java: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 を実装する
```java: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 を登録する
```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">
    <cacheWriter>
      <cacheWriterFactory class="sample.ehcache.MyCacheWriterFactory" />
    </cacheWriter>
  </cache>
</ehcache>
```

- `<cache>` タグの中に、 `<cacheWriter>` と `<cacheWriterFactory>` タグで指定する。

###動作確認
```java: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);
    }
}
```

```text:実行結果
[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()` メソッドを使用する。

##非同期で書き込む
```xml: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>
```

```text:実行結果
[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 をまとめて書き出す
```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">
    <cacheWriter writeMode="write-behind"
                 writeBatching="true"
                 writeBatchSize="2">
      <cacheWriterFactory class="sample.ehcache.MyCacheWriterFactory" />
    </cacheWriter>
  </cache>
</ehcache>
```

```text:実行結果
[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` を指定すると、 `CacheWriter` の `writeAll()` メソッドが呼び出されるようになり、更新のあった複数の `Element` がまとめて渡されるようになる。
- `writeBatchSize` 属性で、いくつの `Element` を同時に処理するかを指定する。デフォルトは `1` 。

##最後の更新だけを処理する
```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">
    <cacheWriter writeMode="write-behind"
                 writeCoalescing="true">
      <cacheWriterFactory class="sample.ehcache.MyCacheWriterFactory" />
    </cacheWriter>
  </cache>
</ehcache>
```

```java: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);
    }
}
```

```text:実行結果
[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 の１つとして提供されている。
TCP の上で動作する。

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

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

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

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

```xml: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` は、 `automatic` か `manual` のいずれかを指定する。
        - ここではホストを明示するので、 `manual` を指定する。
    - `rmiUrls` には、連携相手のホストを特定するための設定を記述する。
        - `//<host>:<port>/<cache name>`
        - 縦線 `|` で複数指定可能。

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

```diff: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 を登録する
キャッシュイベントを監視して、変更があればレプリケーション相手に通知するためのリスナーを登録する。

```diff: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` を指定する。

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

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

https://github.com/opengl-8080/ehcache-replication-sample/releases/tag/v1.1

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

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

```bash: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...
```

```bash: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 秒ごとにキャッシュにアクセスして、レプリケーションが実行されているか確認を始める。

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

```bash: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
```

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

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

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

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

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

具体的な実装は [こちら](https://github.com/opengl-8080/ehcache-replication-sample/blob/master/src/main/java/sample/ehcache/Main.java) を参照。
設定ファイルは [こっち](https://github.com/opengl-8080/ehcache-replication-sample/tree/master/src/main/resources) 。

#キャッシュを動的に作成する
```java: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();
        }
    }
}
```

```text:実行結果
MaxElementsInMemory = 10000
TimeToIdleSeconds = 120
MaxElementsOnDisk = 10000000
```

- `CacheManager#addCache(String)` メソッドで、キャッシュを動的に追加することができる。
- 自作の `ehcache.xml` がクラスパス上に存在しない場合、この方法で作成したキャッシュの設定値には、 `ehcache-x.x.x.jar` の中に入っている `ehcache-failsafe.xml` が使用される。

```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>` の設定が適用されている。

##動的に作成したキャッシュのデフォルト値を指定する
```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">
  
  <defaultCache maxEntriesLocalHeap="30">
  </defaultCache>

</ehcache>
```

```java: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();
        }
    }
}
```

```text:実行結果
MaxEntriesLocalHeap = 30
```

- `ehcache.xml` を作成して、 `<defaultCache>` タグを記述すれば、デフォルト値を設定できる。

##動的に設定値を指定する
```java: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();
        }
    }
}
```

```text:実行結果
MaxEntriesLocalHeap = 12
```

- `CacheConfiguration` のインスタンスを作成することで、設定値を動的に指定できる。
- `CacheConfiguration` インスタンスは、 `Cache` インスタンスを作成するときのコンストラクタ引数に渡す。

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

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

##ローカルトランザクション
```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"
         transactionalMode="local">
  </cache>
  
</ehcache>
```

```java: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()));
    }
}
```

```text:実行結果
hoge : HOGE
fuga : <none>
```

- 自前でトランザクションを制御する場合は、 `<cache>` の `transactionalMode` 属性で `local` を指定する。
- トランザクションの制御は、 `TransactionController` を使用する。
- `TransactionController` のインスタンスあ、 `CacheManager#getTransactionController()` で取得できる。
- `TransactionController#begin()` でトランザクションを開始する。
- `TransactionController#commit()` でコミット、 `rollback()` でロールバックできる。
- トランザクションが有効の場合、キャッシュから値を取得する場合もトランザクションが開始されている必要がある。
    - トランザクションが開始されていない状態でキャッシュから値を取得しようとすると、 `TransactionException` がスローされる。

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

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

サポートしているサーバーは、 [こちらのページ](http://ehcache.org/generated/2.10.0/html/ehc-all/#page/Ehcache_Documentation_Set%2Fco-tx_transaction_managers.html%23) に記載されている。
GlassFish とか JBoss とか Weblogic とか、主要なサーバーはだいたいサポートされている。

###実装
```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"
         transactionalMode="xa_strict">
  </cache>
  
</ehcache>
```

- `transactionalMode` に `xa_strict` を指定する。

```java: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()` が実行されるようにしている。

```java: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` になるようにデプロイする。

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

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

#参考
- [Ehcache](http://ehcache.org/generated/2.10.0/html/ehc-all/)
- [Ehcache | オープンソース | IT用語辞典 | 日立ソリューションズ](http://it-words.jp/w/Ehcache.html)
- [Javaで使えるオープンソース・キャッシュライブラリ - CLOVER](http://d.hatena.ne.jp/Kazuhira/20130723/1374587549)
- [JCACHEの仕様が完成](http://www.infoq.com/jp/news/2014/04/jcache-finalized)
- [untitled - Ehcache.pdf](http://www.bbreak.co.jp/technique/doc/ehcache/Ehcache.pdf)
- [20140325_Blog001_BigMemory](http://www.softwareag.com/jp/20140325_Blog001_BigMemory.asp)
- [Terracotta | 株式会社ユニリタ - しなやかなITでお客様のビジネスとワークスタイルの変革を応援します](http://www.unirita.co.jp/products/terracotta.html)
- [キャッシュアルゴリズム - Wikipedia](https://ja.wikipedia.org/wiki/%E3%82%AD%E3%83%A3%E3%83%83%E3%82%B7%E3%83%A5%E3%82%A2%E3%83%AB%E3%82%B4%E3%83%AA%E3%82%BA%E3%83%A0)
- [FIFO - Wikipedia](https://ja.wikipedia.org/wiki/FIFO)
- [Ehcache - Wikipedia](https://ja.wikipedia.org/wiki/Ehcache)

[^1]: [JCache 1.0に対応した、EhcacheのJCacheモジュールを使ってみる - CLOVER](http://d.hatena.ne.jp/Kazuhira/20140713/1405256198)