ehcache

Scalaで使用可能なエンタープライズ向けキャッシュライブラリ。3層構造(ヒープ、オフヒープ、ディスク)とJSR-107準拠により、大規模アプリケーションに対応。

GitHub概要

ehcache/ehcache3

Ehcache 3.x line

スター2,070
ウォッチ154
フォーク584
作成日:2014年2月26日
言語:Java
ライセンス:Apache License 2.0

トピックス

なし

スター履歴

ehcache/ehcache3 Star History
データ取得日時: 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)

特徴EhcacheCaffeine
3層構造
JSR-107準拠
パフォーマンス
Scala API○ (Scaffeine)
エンタープライズ機能

Ehcache vs ScalaCache

特徴EhcacheScalaCache
バックエンドの柔軟性Ehcache専用複数対応
機能の豊富さ
Scala統合
設定の複雑さ

まとめ

Ehcache 3は、Scalaアプリケーションでも強力なキャッシング機能を提供します。3層構造、JSR-107準拠、永続化などのエンタープライズ機能により、大規模なシステムに適しています。適切なScalaラッパーを作成することで、より慣用的なAPIで利用でき、Akkaやcats-effectなどのScalaエコシステムとも良好に統合できます。