Ehcache(3系)のメモ

# Ehcaheとは
* Javaにおける定番のキャッシュライブラリ
* key-value型のデータ構造
* データの持ち先としてメモリ（ヒープ、ヒープ外）やファイルを選ぶことができる
* この記事の執筆時点の最新版は3.9.1
  * 「Ehcache」でググって上位に出てくる日本語の記事はメジャーバージョンが古いことが多い…（APIは大きく変わっている）

# 使い方
## インストール
MavenなりGradleなり、お好きなビルドツールで依存を設定しましょう。
https://mvnrepository.com/artifact/org.ehcache/ehcache/3.9.1

## 基本的な使い方
とりあえず公式ドキュメントをもとに適当に書いたソースをペタリ。

```java
import org.ehcache.Cache;
import org.ehcache.CacheManager;

import static org.ehcache.config.builders.CacheConfigurationBuilder.newCacheConfigurationBuilder;
import static org.ehcache.config.builders.CacheManagerBuilder.newCacheManagerBuilder;
import static org.ehcache.config.builders.ResourcePoolsBuilder.heap;

public class Main {
    public static void main(String[] args) {
        // ①CacheManagerの生成
        try(CacheManager cacheManager = newCacheManagerBuilder()
                .withCache("preConfigured", newCacheConfigurationBuilder(Long.class, String.class, heap(10)))
                .build(true)) {

            // ②Cacheの取得
            Cache<Long, String> myCache = cacheManager.getCache("preConfigured", Long.class, String.class);

            // ③Cacheの使用
            myCache.put(1L, "da one!");
            String value = myCache.get(1L);
            System.out.println(value);

            // ④Cacheの破棄
            cacheManager.removeCache("preConfigured");
        }
    }
}
```

ソースを少しずつ見ていきましょう。

### ①CacheManagerの生成
最初にすべきことはCacheManagerの生成です。CacheManagerは使い終わったら閉じる必要があり、またAutoClosable実装のためtry-with-resourcesを使っています。

CacheManagerの生成にはBuilderパターンが使用されています。まず`newCacheManagerBuilder()`でBuilderのインスタンスを生成し、それに対してメソッドチェーンで各種設定を行うメソッド呼び出しをつなげていき、最後に`build(true)`を呼び出すことでCacheManagerのインスタンスを得られます。引数の`true`は生成と同時に初期化を行うことを示しています。`false`を渡すか引数なしの`build()`を呼び出した場合は初期化されないため、後で明示的に`init()`を呼ぶ必要があります。まあ通常は`build(true)`の呼び出しでいいと思います。

どのような設定を行なっているかについて見ていきます。このサンプルだと設定は以下の一行だけですが、その中で複数の設定を行なっています。

```java
.withCache("preConfigured", newCacheConfigurationBuilder(Long.class, String.class, heap(10)))
```

`"preConfigured"`はキャッシュのエイリアスで、後でCacheを取得する際に使用します。設定値は任意の文字列でよいので、`"🥺"`とかでもいいです。
`withCache()`の第二引数はCacheConfigurationのBuilderです。通常は`newCacheConfigurationBuilder()`で生成するようです。この場合は引数が３つありますね。第一引数はキャッシュのキーの型、第二引数はキャッシュの値の型です。
第三引数はキャッシュの保存先です。この場合はヒープを指定しています。それ以外だとヒープ外メモリやディスクを指定できます。

`heap()`の引数の数値はキャッシュ保存できる要素数です。この場合は１０なので、１０個だけ要素が保持され、それを超えると既存の何らかの要素が自動的に破棄されます。必ずしも最初に追加した要素が消えるわけではなく、破棄する値の選択アルゴリズムはLRUっぽいです。アルゴリズムを変更できるかどうかはよくわかりませんでした。
要素数ではなく容量を指定することも可能ですが、その場合はこんな感じの書き方になります。

```java
.withCache("preConfigured", newCacheConfigurationBuilder(Long.class, String.class,
        newResourcePoolsBuilder().heap(10, MB)))
```
なお、この例だとこれまで出てきたメソッドはstatic importで参照しています。そのほうが短くなってよいと思います。
また設定にはXMLを使うことも可能です。

### ②Cacheの取得
CacheManagerを生成できたらCacheを取得します。CacheはCacheManagerの`getCache()`メソッドから取得します。

```java
Cache<Long, String> myCache = cacheManager.getCache("preConfigured", Long.class, String.class);
```
第一引数はエイリアス、第二引数はキーの型、第三引数は値の型で、いずれも①で設定したのと同じものを指定します。

### ③Cacheの使用
CacheはMap実装ではないですが、似たようなメソッドが用意されていて同じ感覚で使用できます。`put()`で追加、`get()`で取得です。
https://www.ehcache.org/apidocs/3.8.1/org/ehcache/Cache.html

なお、Map実装ではありませんがIterable実装なので拡張for文や`forEach()`を使用できます。

### ④Cacheの破棄
最後にCacheManagerの`removeCache()`でキャッシュを破棄します。引数は①で設定したエイリアスです。破棄すると当然Cacheは使えなくなります。破棄した後のCacheを使おうとするとIllegalStateExceptionが投げられます。

## ディスクにキャッシュを保存する
全ての機能を紹介することはできないですが、もう少し見てみます。前述の例はキャッシュをヒープに保存しましたが、ヒープ外メモリやディスクに保存することもできるし、複数指定することもできます。ヒープ外メモリよりもディスクのほうがわかりやすいと思うのでディスクについて見てみます。

サンプルコードはこちらです。

```java
import org.ehcache.Cache;
import org.ehcache.PersistentCacheManager;
import org.ehcache.config.builders.CacheManagerBuilder;

import java.io.File;

import static org.ehcache.config.builders.CacheConfigurationBuilder.newCacheConfigurationBuilder;
import static org.ehcache.config.builders.CacheManagerBuilder.newCacheManagerBuilder;
import static org.ehcache.config.builders.ResourcePoolsBuilder.newResourcePoolsBuilder;
import static org.ehcache.config.units.MemoryUnit.MB;

public class Main {
    public static void main(String[] args) {
        // ① PersistentCacheManagerの生成
        try(PersistentCacheManager cacheManager = newCacheManagerBuilder()
                .with(CacheManagerBuilder.persistence(new File(System.getProperty("java.io.tmpdir"), "myData")))
                .withCache("persistent-cache", newCacheConfigurationBuilder(Long.class, String.class,
                        newResourcePoolsBuilder().disk(10, MB, true))
                )
                .build(true)) {

            // ②Cacheの取得
            Cache<Long, String> myCache = cacheManager.getCache("persistent-cache", Long.class, String.class);

            // ③Cacheの使用
            myCache.put(1L, "da one!");
            String value = myCache.get(1L);
            System.out.println(value);

            // ④Cacheの破棄
            cacheManager.removeCache("persistent-cache");
        }
    }
}
```

### ①PersistentCacheManagerの生成
ディスクに保存する場合は、通常のCacheManagerではなくPersistentCacheManagerになります。`newCacheManagerBuilder()`から取得したBuilderで設定するのは同じです。
大きく異なるのは以下の一行ですね。

```java
with(CacheManagerBuilder.persistence(new File(System.getProperty("java.io.tmpdir"), "myData")))
```
ファイルに保存するわけなので、そのパスを指定する必要があります。Fileクラスで指定します。任意のパスを指定できますが、このサンプルではとりあえずシステムのtmp領域に保存するようにしています。`"myData"`はファイル名です。

`withCache()`も呼び出します。だいたい同じですが、`heap()`が`disk()`になっているのが異なります。メソッド名の通り、これがキャッシュをディスクに保存するための設定です。

```java
.withCache("persistent-cache", newCacheConfigurationBuilder(Long.class, String.class,
        newResourcePoolsBuilder().disk(10, MB, true))
)
```
`disk()`の引数ですが、第一引数と第二引数は見て想像できる通り、１０MB分をキャッシュに保存することを示しています。第三引数のboolean値ですが、これは永続化するかどうかのフラグです。`true`だと永続化し、`false`を指定するかもしくは第三引数のない`disk()`を呼び出した場合は永続化しません。

## キャッシュ保存先を複数指定する
保存先を複数指定することも可能です。複数指定は`newResourcePoolsBuilder()`の呼び出しに続けて保存先を指定するメソッドをつなげて書くだけで可能です。

```java
newResourcePoolsBuilder()
        .heap(10, EntryUnit.ENTRIES)
        .disk(10, MB))
```
複数指定するとどうなるのかですが、ドキュメントのシーケンス図を見るとわかりやすいかなと思います。
https://www.ehcache.org/documentation/3.8/tiering.html#multi-tier-sequence-flow

複数指定時はCaching Tierは現状必ずヒープであり、Authoritative tierは場合によりますが今回の例ではディスクです。
つまりキャッシュへのputはヒープではなくディスクに対して行われ、同じキーに対応する値がヒープにあれば破棄されます。
getはデータがヒープにあればそれを返してディスクにアクセスせず、ヒープになければディスクから読んで、次回のためにヒープに保存してから返します。

まあキャッシュですからね、当然そうなります。

# おわりに
ほんのさわりだけですが、今回は以上です。もっと詳細に知りたい方は頑張って英語を読みましょう🥺

# 参考
* https://www.ehcache.org/documentation/3.8/getting-started.html
* https://www.ehcache.org/documentation/3.8/tiering.html