ehcache
Scalaで使用可能なエンタープライズ向けキャッシュライブラリ。3層構造(ヒープ、オフヒープ、ディスク)とJSR-107準拠により、大規模アプリケーションに対応。
GitHub概要
ehcache/ehcache3
Ehcache 3.x line
スター2,070
ウォッチ154
フォーク584
作成日:2014年2月26日
言語:Java
ライセンス:Apache License 2.0
トピックス
なし
スター履歴
データ取得日時: 2025/10/22 10:04
概要
Ehcache 3は、ScalaアプリケーションでもJavaと同様に使用できる強力なキャッシュライブラリです。ヒープ、オフヒープ、ディスクの3層構造をサポートし、JSR-107(JCache)標準に準拠しています。大規模なエンタープライズアプリケーションに適した豊富な機能を提供します。
主な特徴
- 3層キャッシュ構造: ヒープ、オフヒープ、ディスクの階層型ストレージ
- JSR-107準拠: JCache標準APIのフル実装
- 型安全: Scalaの型システムと良好に統合
- 永続化: アプリケーション再起動後もデータを保持
- クラスタリング: Terracottaサーバーによる分散キャッシュ
- イベントリスナー: キャッシュイベントの監視と処理
インストール
sbt
// build.sbt
libraryDependencies ++= Seq(
"org.ehcache" % "ehcache" % "3.10.8",
"javax.cache" % "cache-api" % "1.1.1",
"org.slf4j" % "slf4j-simple" % "2.0.7", // ロギング実装
// オプション:クラスタリング
"org.ehcache" % "ehcache-clustered" % "3.10.8",
// オプション:トランザクション
"org.ehcache" % "ehcache-transactions" % "3.10.8"
)
基本的な使い方
シンプルなキャッシュの作成
import org.ehcache.config.builders._
import org.ehcache.{Cache, CacheManager}
import org.ehcache.config.units.{EntryUnit, MemoryUnit}
object BasicEhcacheExample {
// CacheManagerの作成
val cacheManager: CacheManager = CacheManagerBuilder
.newCacheManagerBuilder()
.build(true)
// キャッシュの作成
val userCache: Cache[Long, User] = cacheManager.createCache(
"userCache",
CacheConfigurationBuilder.newCacheConfigurationBuilder(
classOf[Long],
classOf[User],
ResourcePoolsBuilder.heap(100)
)
)
// 使用例
def example(): Unit = {
val user = User(1L, "Alice", "[email protected]")
// データの保存
userCache.put(1L, user)
// データの取得
Option(userCache.get(1L)) match {
case Some(u) => println(s"Found user: $u")
case None => println("User not found")
}
// データの削除
userCache.remove(1L)
}
// クリーンアップ
def cleanup(): Unit = {
cacheManager.close()
}
}
case class User(id: Long, name: String, email: String)
型安全なScalaラッパー
import org.ehcache.{Cache => EhCache}
import scala.util.Try
// Ehcache用のScalaラッパー
class ScalaCache[K, V](underlying: EhCache[K, V]) {
def get(key: K): Option[V] = Option(underlying.get(key))
def put(key: K, value: V): Unit = underlying.put(key, value)
def putIfAbsent(key: K, value: V): Option[V] =
Option(underlying.putIfAbsent(key, value))
def remove(key: K): Boolean = underlying.remove(key)
def removeIf(key: K)(condition: V => Boolean): Boolean = {
get(key) match {
case Some(value) if condition(value) => remove(key)
case _ => false
}
}
def update(key: K)(f: Option[V] => V): Unit = {
val newValue = f(get(key))
put(key, newValue)
}
def getOrElseUpdate(key: K)(default: => V): V = {
get(key) match {
case Some(value) => value
case None =>
val value = default
put(key, value)
value
}
}
def clear(): Unit = underlying.clear()
def containsKey(key: K): Boolean = underlying.containsKey(key)
}
object ScalaCache {
def apply[K, V](cache: EhCache[K, V]): ScalaCache[K, V] =
new ScalaCache(cache)
}
高度な使い方
3層キャッシュ構成
import org.ehcache.config.builders._
import org.ehcache.config.units.{EntryUnit, MemoryUnit}
import org.ehcache.impl.config.persistence.CacheManagerPersistenceConfiguration
import java.io.File
class ThreeTierCacheManager(cacheDirectory: File) {
private val cacheManager = CacheManagerBuilder
.newCacheManagerBuilder()
.with(CacheManagerPersistenceConfiguration(cacheDirectory))
.build(true)
// 3層構成のキャッシュを作成
def createThreeTierCache[K, V](
name: String,
keyType: Class[K],
valueType: Class[V],
heapEntries: Long,
offheapMB: Long,
diskMB: Long
): ScalaCache[K, V] = {
val ehcache = cacheManager.createCache(
name,
CacheConfigurationBuilder.newCacheConfigurationBuilder(
keyType,
valueType,
ResourcePoolsBuilder.newResourcePoolsBuilder()
.heap(heapEntries, EntryUnit.ENTRIES)
.offheap(offheapMB, MemoryUnit.MB)
.disk(diskMB, MemoryUnit.MB, true) // true = 永続化
)
)
ScalaCache(ehcache)
}
def close(): Unit = cacheManager.close()
}
// 使用例
object ThreeTierExample extends App {
val cacheDir = new File("/tmp/ehcache")
val manager = new ThreeTierCacheManager(cacheDir)
val cache = manager.createThreeTierCache(
"dataCache",
classOf[String],
classOf[Array[Byte]],
heapEntries = 1000,
offheapMB = 100,
diskMB = 1000
)
// 大きなデータの保存
val largeData = Array.fill(1024 * 1024)(0.toByte) // 1MB
cache.put("large-file", largeData)
// 取得
cache.get("large-file") match {
case Some(data) => println(s"Retrieved ${data.length} bytes")
case None => println("Data not found")
}
manager.close()
}
イベントリスナーの実装
import org.ehcache.event._
import org.ehcache.config.builders._
import scala.collection.JavaConverters._
trait CacheEventHandler[K, V] {
def onCreated(key: K, value: V): Unit
def onUpdated(key: K, oldValue: V, newValue: V): Unit
def onRemoved(key: K, value: V): Unit
def onExpired(key: K, value: V): Unit
def onEvicted(key: K, value: V): Unit
}
class ScalaCacheEventListener[K, V](handler: CacheEventHandler[K, V])
extends CacheEventListener[K, V] {
override def onEvent(event: CacheEvent[_ <: K, _ <: V]): Unit = {
event.getType match {
case EventType.CREATED =>
handler.onCreated(event.getKey, event.getNewValue)
case EventType.UPDATED =>
handler.onUpdated(event.getKey, event.getOldValue, event.getNewValue)
case EventType.REMOVED =>
handler.onRemoved(event.getKey, event.getOldValue)
case EventType.EXPIRED =>
handler.onExpired(event.getKey, event.getOldValue)
case EventType.EVICTED =>
handler.onEvicted(event.getKey, event.getOldValue)
}
}
}
// 使用例
val eventHandler = new CacheEventHandler[String, String] {
override def onCreated(key: String, value: String): Unit =
println(s"Created: $key -> $value")
override def onUpdated(key: String, oldValue: String, newValue: String): Unit =
println(s"Updated: $key from $oldValue to $newValue")
override def onRemoved(key: String, value: String): Unit =
println(s"Removed: $key -> $value")
override def onExpired(key: String, value: String): Unit =
println(s"Expired: $key -> $value")
override def onEvicted(key: String, value: String): Unit =
println(s"Evicted: $key -> $value")
}
// イベントリスナー付きキャッシュの作成
val cacheWithEvents = cacheManager.createCache(
"eventCache",
CacheConfigurationBuilder.newCacheConfigurationBuilder(
classOf[String],
classOf[String],
ResourcePoolsBuilder.heap(10)
).withEventListenersConfig(
EventListenersConfigurationBuilder.newEventListenersConfiguration()
.eventListener(
new ScalaCacheEventListener(eventHandler),
EventType.values().toSet.asJava
)
)
)
実践的な例
Akka統合
import akka.actor.{Actor, ActorLogging, ActorSystem, Props}
import org.ehcache.{Cache => EhCache}
import scala.concurrent.duration._
class CacheActor[K, V](cache: ScalaCache[K, V]) extends Actor with ActorLogging {
import CacheActor._
def receive: Receive = {
case Get(key: K) =>
sender() ! GetResponse(cache.get(key))
case Put(key: K, value: V) =>
cache.put(key, value)
sender() ! PutResponse(success = true)
case Update(key: K, f: (Option[V] => V)) =>
cache.update(key)(f)
sender() ! UpdateResponse(cache.get(key))
case Remove(key: K) =>
val removed = cache.remove(key)
sender() ! RemoveResponse(removed)
case Clear =>
cache.clear()
sender() ! ClearResponse
case GetStats =>
// Ehcacheの統計情報を収集
sender() ! StatsResponse(Map(
"size" -> "N/A" // Ehcacheは直接的なサイズ取得をサポートしない
))
}
}
object CacheActor {
// メッセージ定義
case class Get[K](key: K)
case class Put[K, V](key: K, value: V)
case class Update[K, V](key: K, f: Option[V] => V)
case class Remove[K](key: K)
case object Clear
case object GetStats
// レスポンス定義
case class GetResponse[V](value: Option[V])
case class PutResponse(success: Boolean)
case class UpdateResponse[V](newValue: Option[V])
case class RemoveResponse(removed: Boolean)
case object ClearResponse
case class StatsResponse(stats: Map[String, String])
def props[K, V](cache: ScalaCache[K, V]): Props =
Props(new CacheActor(cache))
}
// 使用例
object AkkaIntegrationExample extends App {
implicit val system = ActorSystem("cache-system")
import system.dispatcher
// Ehcacheの設定
val cacheManager = CacheManagerBuilder.newCacheManagerBuilder().build(true)
val ehcache = cacheManager.createCache(
"akkaCache",
CacheConfigurationBuilder.newCacheConfigurationBuilder(
classOf[String],
classOf[String],
ResourcePoolsBuilder.heap(1000)
)
)
val cache = ScalaCache(ehcache)
val cacheActor = system.actorOf(CacheActor.props(cache), "cache-actor")
// アクターを通じたキャッシュ操作
import akka.pattern.ask
import akka.util.Timeout
implicit val timeout = Timeout(5.seconds)
val future = for {
_ <- cacheActor ? CacheActor.Put("key1", "value1")
response <- cacheActor ? CacheActor.Get("key1")
} yield response
future.foreach(println)
system.terminate()
cacheManager.close()
}
JSR-107 (JCache) APIの使用
import javax.cache._
import javax.cache.configuration.MutableConfiguration
import javax.cache.expiry.{CreatedExpiryPolicy, Duration => JDuration}
import java.util.concurrent.TimeUnit
object JCacheScalaExample {
// JCache プロバイダーとしてEhcacheを使用
val provider = Caching.getCachingProvider("org.ehcache.jsr107.EhcacheCachingProvider")
val cacheManager = provider.getCacheManager()
// ScalaでのJCache設定
def createCache[K, V](
name: String,
keyType: Class[K],
valueType: Class[V],
expiryMinutes: Long
): Cache[K, V] = {
val config = new MutableConfiguration[K, V]()
.setTypes(keyType, valueType)
.setExpiryPolicyFactory(
CreatedExpiryPolicy.factoryOf(
new JDuration(TimeUnit.MINUTES, expiryMinutes)
)
)
.setStatisticsEnabled(true)
.setManagementEnabled(true)
cacheManager.createCache(name, config)
}
// Scala風のラッパー
implicit class ScalaJCache[K, V](cache: Cache[K, V]) {
def getOption(key: K): Option[V] = Option(cache.get(key))
def putAll(entries: Map[K, V]): Unit = {
import scala.collection.JavaConverters._
cache.putAll(entries.asJava)
}
def getAll(keys: Set[K]): Map[K, V] = {
import scala.collection.JavaConverters._
cache.getAll(keys.asJava).asScala.toMap
}
}
// 使用例
def example(): Unit = {
val cache = createCache("products", classOf[Long], classOf[Product], 30)
// Scala風の操作
cache.put(1L, Product(1L, "Laptop", 999.99))
cache.getOption(1L) match {
case Some(product) => println(s"Found: $product")
case None => println("Not found")
}
// バッチ操作
val products = Map(
2L -> Product(2L, "Mouse", 29.99),
3L -> Product(3L, "Keyboard", 79.99)
)
cache.putAll(products)
val retrieved = cache.getAll(Set(1L, 2L, 3L))
retrieved.foreach { case (id, product) =>
println(s"Product $id: $product")
}
cacheManager.close()
}
}
case class Product(id: Long, name: String, price: Double)
関数型スタイルでの使用
import cats.effect._
import cats.implicits._
import org.ehcache.{Cache => EhCache}
// 関数型インターフェース
trait CacheAlgebra[F[_], K, V] {
def get(key: K): F[Option[V]]
def put(key: K, value: V): F[Unit]
def remove(key: K): F[Boolean]
def clear(): F[Unit]
def modify(key: K)(f: Option[V] => Option[V]): F[Unit]
}
// Ehcacheの実装
class EhcacheInterpreter[F[_]: Sync, K, V](
underlying: EhCache[K, V]
) extends CacheAlgebra[F, K, V] {
override def get(key: K): F[Option[V]] =
Sync[F].delay(Option(underlying.get(key)))
override def put(key: K, value: V): F[Unit] =
Sync[F].delay(underlying.put(key, value))
override def remove(key: K): F[Boolean] =
Sync[F].delay(underlying.remove(key))
override def clear(): F[Unit] =
Sync[F].delay(underlying.clear())
override def modify(key: K)(f: Option[V] => Option[V]): F[Unit] =
for {
current <- get(key)
newValue = f(current)
_ <- newValue match {
case Some(v) => put(key, v)
case None => remove(key).void
}
} yield ()
}
// 使用例
object FunctionalExample extends IOApp {
def program[F[_]: Sync](cache: CacheAlgebra[F, String, Int]): F[Unit] =
for {
_ <- cache.put("counter", 0)
_ <- cache.modify("counter")(_.map(_ + 1))
value <- cache.get("counter")
_ <- Sync[F].delay(println(s"Counter value: $value"))
} yield ()
override def run(args: List[String]): IO[ExitCode] = {
val cacheManager = CacheManagerBuilder.newCacheManagerBuilder().build(true)
val ehcache = cacheManager.createCache(
"functionalCache",
CacheConfigurationBuilder.newCacheConfigurationBuilder(
classOf[String],
classOf[Int],
ResourcePoolsBuilder.heap(100)
)
)
val cache = new EhcacheInterpreter[IO, String, Int](ehcache)
program(cache).as(ExitCode.Success).guarantee(IO {
cacheManager.close()
})
}
}
パフォーマンスのベストプラクティス
1. リソースプールの最適化
// 使用パターンに応じた設定
val optimizedCache = CacheConfigurationBuilder.newCacheConfigurationBuilder(
classOf[String],
classOf[Array[Byte]],
ResourcePoolsBuilder.newResourcePoolsBuilder()
.heap(1000, EntryUnit.ENTRIES) // 頻繁にアクセスされるデータ
.offheap(100, MemoryUnit.MB) // 中程度の頻度
.disk(1, MemoryUnit.GB, false) // 低頻度、永続化なし
)
2. バッチ操作の活用
implicit class BatchOperations[K, V](cache: ScalaCache[K, V]) {
def putAll(entries: Map[K, V]): Unit = {
entries.foreach { case (k, v) => cache.put(k, v) }
}
def getAll(keys: Set[K]): Map[K, V] = {
keys.flatMap(k => cache.get(k).map(k -> _)).toMap
}
def removeAll(keys: Set[K]): Set[K] = {
keys.filter(cache.remove)
}
}
3. 非同期処理との統合
import scala.concurrent.{Future, ExecutionContext}
class AsyncCache[K, V](cache: ScalaCache[K, V])(implicit ec: ExecutionContext) {
def getAsync(key: K): Future[Option[V]] = Future(cache.get(key))
def putAsync(key: K, value: V): Future[Unit] = Future(cache.put(key, value))
def computeIfAbsentAsync(key: K)(compute: => Future[V]): Future[V] = {
getAsync(key).flatMap {
case Some(value) => Future.successful(value)
case None =>
compute.flatMap { value =>
putAsync(key, value).map(_ => value)
}
}
}
}
他のScalaキャッシュライブラリとの比較
Ehcache vs Caffeine (Scala)
| 特徴 | Ehcache | Caffeine |
|---|---|---|
| 3層構造 | ◎ | △ |
| JSR-107準拠 | ◎ | ○ |
| パフォーマンス | ○ | ◎ |
| Scala API | △ | ○ (Scaffeine) |
| エンタープライズ機能 | ◎ | ○ |
Ehcache vs ScalaCache
| 特徴 | Ehcache | ScalaCache |
|---|---|---|
| バックエンドの柔軟性 | Ehcache専用 | 複数対応 |
| 機能の豊富さ | ◎ | ○ |
| Scala統合 | △ | ◎ |
| 設定の複雑さ | 高 | 低 |
まとめ
Ehcache 3は、Scalaアプリケーションでも強力なキャッシング機能を提供します。3層構造、JSR-107準拠、永続化などのエンタープライズ機能により、大規模なシステムに適しています。適切なScalaラッパーを作成することで、より慣用的なAPIで利用でき、Akkaやcats-effectなどのScalaエコシステムとも良好に統合できます。