ehcache

Java/Kotlin向けの強力なキャッシュライブラリ。ヒープ、オフヒープ、ディスクの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は、Java/Kotlin向けのオープンソースキャッシュライブラリです。ヒープ、オフヒープ、ディスクの3層構造をサポートし、スケーラブルで高性能なキャッシュソリューションを提供します。JSR-107(JCache)標準に準拠し、エンタープライズアプリケーションに適した豊富な機能を備えています。

主な特徴

  • 3層キャッシュ構造: ヒープ、オフヒープ、ディスクの階層型ストレージ
  • JSR-107準拠: JCache標準APIのフル実装
  • クラスタリング対応: Terracottaサーバーによる分散キャッシュ
  • トランザクションサポート: XAトランザクション対応
  • 型安全: ジェネリクスによる強い型付け
  • 永続化: アプリケーション再起動後もデータを保持

インストール

Gradle (Kotlin DSL)

dependencies {
    // Ehcache 3 コア
    implementation("org.ehcache:ehcache:3.10.8")
    
    // JSR-107 Cache API(必須)
    implementation("javax.cache:cache-api:1.1.1")
    
    // ロギング実装(必須)
    implementation("org.slf4j:slf4j-simple:2.0.7")
    
    // オプション:クラスタリング
    implementation("org.ehcache:ehcache-clustered:3.10.8")
    
    // オプション:トランザクション
    implementation("org.ehcache:ehcache-transactions:3.10.8")
}

Androidでの注意事項

重要: Ehcacheは主にサーバーサイドJavaアプリケーション向けに設計されており、Androidでの使用は推奨されません。代替として以下を検討してください:

  • メモリキャッシュ: androidx.collection.LruCache
  • 永続化: Room Database、Jetpack DataStore
  • ネットワークキャッシュ: OkHttp、Retrofitの組み込みキャッシュ

基本的な使い方

シンプルなヒープキャッシュ

import org.ehcache.Cache
import org.ehcache.CacheManager
import org.ehcache.config.builders.CacheConfigurationBuilder
import org.ehcache.config.builders.CacheManagerBuilder
import org.ehcache.config.builders.ResourcePoolsBuilder
import org.ehcache.config.units.EntryUnit

// CacheManagerの作成
val cacheManager: CacheManager = CacheManagerBuilder
    .newCacheManagerBuilder()
    .build(true) // 初期化

// キャッシュの作成
val userCache: Cache<Long, User> = cacheManager.createCache(
    "userCache",
    CacheConfigurationBuilder.newCacheConfigurationBuilder(
        Long::class.javaObjectType,  // キーの型
        User::class.java,            // 値の型
        ResourcePoolsBuilder.newResourcePoolsBuilder()
            .heap(100, EntryUnit.ENTRIES) // 最大100エントリ
    )
)

// データの操作
data class User(val id: Long, val name: String, val email: String)

// 追加
userCache.put(1L, User(1L, "田中太郎", "[email protected]"))

// 取得
val user = userCache.get(1L)
println(user?.name) // 田中太郎

// 削除
userCache.remove(1L)

// クリーンアップ
cacheManager.close()

期限切れポリシーの設定

import org.ehcache.expiry.ExpiryPolicyBuilder
import java.time.Duration

val sessionCache: Cache<String, Session> = cacheManager.createCache(
    "sessionCache",
    CacheConfigurationBuilder.newCacheConfigurationBuilder(
        String::class.java,
        Session::class.java,
        ResourcePoolsBuilder.heap(1000)
    )
    // 書き込み後30分で期限切れ
    .withExpiry(ExpiryPolicyBuilder.timeToLiveExpiration(Duration.ofMinutes(30)))
    // アクセス後10分で期限切れ
    .withExpiry(ExpiryPolicyBuilder.timeToIdleExpiration(Duration.ofMinutes(10)))
)

高度な使い方

3層キャッシュ構成

import org.ehcache.config.units.MemoryUnit
import org.ehcache.PersistentCacheManager
import java.io.File

class ThreeTierCacheManager(cacheDirectory: File) {
    
    val persistentCacheManager: PersistentCacheManager = CacheManagerBuilder
        .newCacheManagerBuilder()
        .with(CacheManagerBuilder.persistence(cacheDirectory))
        .build(true)
    
    // 3層構成のキャッシュ
    val dataCache: Cache<String, ByteArray> = persistentCacheManager.createCache(
        "dataCache",
        CacheConfigurationBuilder.newCacheConfigurationBuilder(
            String::class.java,
            ByteArray::class.java,
            ResourcePoolsBuilder.newResourcePoolsBuilder()
                .heap(100, EntryUnit.ENTRIES)          // ヒープ: 100エントリ
                .offheap(10, MemoryUnit.MB)           // オフヒープ: 10MB
                .disk(100, MemoryUnit.MB, true)       // ディスク: 100MB(永続化)
        )
    )
    
    fun shutdown() {
        persistentCacheManager.close()
    }
}

イベントリスナーの実装

import org.ehcache.event.CacheEvent
import org.ehcache.event.CacheEventListener
import org.ehcache.event.EventType

class CacheEventLogger<K, V> : CacheEventListener<K, V> {
    override fun onEvent(event: CacheEvent<out K, out V>) {
        println("Cache Event: ${event.type} - Key: ${event.key}, Old: ${event.oldValue}, New: ${event.newValue}")
    }
}

// イベントリスナーを持つキャッシュの作成
val monitoredCache: Cache<String, String> = cacheManager.createCache(
    "monitoredCache",
    CacheConfigurationBuilder.newCacheConfigurationBuilder(
        String::class.java,
        String::class.java,
        ResourcePoolsBuilder.heap(50)
    )
    .withEventListenersConfig(
        EventListenersConfigurationBuilder.newEventListenersConfiguration()
            .eventListener(CacheEventLogger<String, String>(), EventType.CREATED, EventType.UPDATED, EventType.REMOVED)
    )
)

カスタム期限切れポリシー

import org.ehcache.expiry.ExpiryPolicy
import java.time.Duration
import java.util.function.Supplier

class DynamicExpiryPolicy : ExpiryPolicy<String, CachedData> {
    
    override fun getExpiryForCreation(key: String, value: CachedData): Duration {
        // 優先度に基づいて期限を設定
        return when (value.priority) {
            Priority.HIGH -> Duration.ofHours(24)
            Priority.MEDIUM -> Duration.ofHours(6)
            Priority.LOW -> Duration.ofHours(1)
        }
    }
    
    override fun getExpiryForAccess(key: String, value: Supplier<out CachedData>): Duration? {
        // アクセス時は期限を延長しない
        return null
    }
    
    override fun getExpiryForUpdate(key: String, oldValue: Supplier<out CachedData>, newValue: CachedData): Duration {
        // 更新時は新しい値の優先度に基づいて再設定
        return getExpiryForCreation(key, newValue)
    }
}

data class CachedData(val data: String, val priority: Priority)
enum class Priority { HIGH, MEDIUM, LOW }

実践的な例

リポジトリパターンでの実装

import kotlinx.coroutines.*
import kotlin.coroutines.CoroutineContext

class CachedRepository(
    private val remoteDataSource: RemoteDataSource,
    private val cacheManager: CacheManager,
    private val coroutineContext: CoroutineContext = Dispatchers.IO
) {
    
    private val apiCache: Cache<String, ApiResponse> = cacheManager.createCache(
        "apiCache",
        CacheConfigurationBuilder.newCacheConfigurationBuilder(
            String::class.java,
            ApiResponse::class.java,
            ResourcePoolsBuilder.newResourcePoolsBuilder()
                .heap(500, EntryUnit.ENTRIES)
                .offheap(50, MemoryUnit.MB)
        )
        .withExpiry(ExpiryPolicyBuilder.timeToLiveExpiration(Duration.ofMinutes(15)))
    )
    
    suspend fun getData(id: String): ApiResponse = withContext(coroutineContext) {
        // キャッシュから取得を試みる
        apiCache.get(id)?.let { cachedData ->
            println("Cache hit for id: $id")
            return@withContext cachedData
        }
        
        // キャッシュミスの場合はリモートから取得
        println("Cache miss for id: $id, fetching from remote")
        val data = remoteDataSource.fetchData(id)
        
        // キャッシュに保存
        apiCache.put(id, data)
        
        data
    }
    
    fun invalidate(id: String) {
        apiCache.remove(id)
    }
    
    fun invalidateAll() {
        apiCache.clear()
    }
}

data class ApiResponse(val id: String, val data: Map<String, Any>)

interface RemoteDataSource {
    suspend fun fetchData(id: String): ApiResponse
}

Kotlin拡張関数の活用

// CacheExtensions.kt

/**
 * キャッシュから値を取得するか、存在しない場合は計算して保存する
 */
inline fun <K, V> Cache<K, V>.getOrPut(key: K, crossinline defaultValue: () -> V): V {
    get(key)?.let { return it }
    
    val value = defaultValue()
    put(key, value)
    return value
}

/**
 * 条件付きでキャッシュを更新
 */
inline fun <K, V> Cache<K, V>.updateIf(key: K, crossinline condition: (V?) -> Boolean, newValue: V) {
    val currentValue = get(key)
    if (condition(currentValue)) {
        put(key, newValue)
    }
}

/**
 * バッチ操作の拡張
 */
fun <K, V> Cache<K, V>.putAll(entries: Map<K, V>) {
    entries.forEach { (key, value) ->
        put(key, value)
    }
}

fun <K, V> Cache<K, V>.getAll(keys: Set<K>): Map<K, V> {
    return keys.associateWith { key -> get(key) }
        .filterValues { it != null } as Map<K, V>
}

// 使用例
val cache: Cache<String, User> = // ... cache initialization

// getOrPut の使用
val user = cache.getOrPut("user:123") {
    // データベースやAPIから取得
    fetchUserFromDatabase("123")
}

// 条件付き更新
cache.updateIf("user:123", { currentUser ->
    currentUser == null || currentUser.lastUpdated < System.currentTimeMillis() - 3600000
}, updatedUser)

JSR-107 (JCache) APIの使用

import javax.cache.Cache as JCache
import javax.cache.CacheManager as JCacheManager
import javax.cache.Caching
import javax.cache.configuration.MutableConfiguration
import javax.cache.expiry.CreatedExpiryPolicy
import javax.cache.expiry.Duration as JDuration

class JCacheExample {
    
    // JCache プロバイダーとしてEhcacheを使用
    private val cacheManager: JCacheManager = Caching.getCachingProvider(
        "org.ehcache.jsr107.EhcacheCachingProvider"
    ).cacheManager
    
    fun createCache(): JCache<Long, Product> {
        val config = MutableConfiguration<Long, Product>()
            .setTypes(Long::class.java, Product::class.java)
            .setExpiryPolicyFactory(
                CreatedExpiryPolicy.factoryOf(
                    JDuration(java.util.concurrent.TimeUnit.MINUTES, 30)
                )
            )
            .setStatisticsEnabled(true)
            .setManagementEnabled(true)
        
        return cacheManager.createCache("productCache", config)
    }
    
    fun demonstrateUsage() {
        val cache = createCache()
        
        // 標準JCache APIの使用
        cache.put(1L, Product(1L, "商品A", 1000))
        
        val product = cache.get(1L)
        println("Product: ${product?.name}")
        
        // 統計情報の取得
        val stats = cache.unwrap(org.ehcache.Cache::class.java).runtimeConfiguration
        println("Cache statistics enabled: ${stats.isStatisticsEnabled}")
    }
}

data class Product(val id: Long, val name: String, val price: Int)

パフォーマンスチューニング

メモリ最適化設定

class OptimizedCacheConfig {
    
    fun createOptimizedCache(cacheManager: CacheManager): Cache<String, LargeObject> {
        return cacheManager.createCache(
            "optimizedCache",
            CacheConfigurationBuilder.newCacheConfigurationBuilder(
                String::class.java,
                LargeObject::class.java,
                ResourcePoolsBuilder.newResourcePoolsBuilder()
                    // ヒープは頻繁にアクセスされる少数のオブジェクト
                    .heap(50, EntryUnit.ENTRIES)
                    // オフヒープは中程度の頻度でアクセスされるオブジェクト
                    .offheap(100, MemoryUnit.MB)
                    // ディスクは低頻度アクセスの大量データ
                    .disk(1, MemoryUnit.GB, false)
            )
            // カスタムシリアライザーの設定(パフォーマンス向上)
            .withValueSerializer(KryoSerializer(LargeObject::class.java))
            // リソース使用量の監視
            .withService(DefaultStatisticsService())
        )
    }
}

// カスタムシリアライザーの例(疑似コード)
class KryoSerializer<T>(private val clazz: Class<T>) : Serializer<T> {
    // Kryoライブラリを使用した高速シリアライゼーション
    override fun serialize(obj: T): ByteBuffer {
        // 実装省略
    }
    
    override fun read(binary: ByteBuffer): T {
        // 実装省略
    }
    
    override fun equals(obj: T, binary: ByteBuffer): Boolean {
        // 実装省略
    }
}

ベストプラクティス

1. リソース管理

class CacheLifecycleManager : AutoCloseable {
    private val cacheManager = CacheManagerBuilder.newCacheManagerBuilder().build(true)
    private val caches = mutableMapOf<String, Cache<*, *>>()
    
    fun <K, V> getOrCreateCache(
        name: String,
        keyType: Class<K>,
        valueType: Class<V>,
        config: (ResourcePoolsBuilder) -> ResourcePoolsBuilder
    ): Cache<K, V> {
        @Suppress("UNCHECKED_CAST")
        return caches.getOrPut(name) {
            cacheManager.createCache(
                name,
                CacheConfigurationBuilder.newCacheConfigurationBuilder(
                    keyType,
                    valueType,
                    config(ResourcePoolsBuilder.newResourcePoolsBuilder())
                )
            )
        } as Cache<K, V>
    }
    
    override fun close() {
        cacheManager.close()
    }
}

// 使用例(try-with-resourcesパターン)
fun main() {
    CacheLifecycleManager().use { cacheManager ->
        val cache = cacheManager.getOrCreateCache(
            "myCache",
            String::class.java,
            String::class.java
        ) { it.heap(100, EntryUnit.ENTRIES) }
        
        // キャッシュの使用
        cache.put("key", "value")
    } // 自動的にクローズされる
}

2. エラーハンドリング

import org.ehcache.spi.resilience.ResilienceStrategy
import org.ehcache.spi.resilience.StoreAccessException

class ResilientCacheWrapper<K, V>(
    private val cache: Cache<K, V>,
    private val logger: Logger
) {
    
    fun get(key: K): V? {
        return try {
            cache.get(key)
        } catch (e: StoreAccessException) {
            logger.error("Cache access failed for key: $key", e)
            null
        }
    }
    
    fun put(key: K, value: V) {
        try {
            cache.put(key, value)
        } catch (e: StoreAccessException) {
            logger.error("Cache put failed for key: $key", e)
            // フォールバック処理(例:メモリキューに保存)
        }
    }
}

他のキャッシュライブラリとの比較

Ehcache vs Caffeine

特徴EhcacheCaffeine
3層構造
JSR-107準拠
クラスタリング×
パフォーマンス
メモリ効率
Android対応

Ehcache vs Hazelcast

特徴EhcacheHazelcast
ローカルキャッシュ
分散キャッシュ
設定の簡単さ
エンタープライズ機能

まとめ

Ehcache 3は、Javaエコシステムにおける成熟したキャッシュソリューションです。3層構造、JSR-107準拠、クラスタリング対応など、エンタープライズアプリケーションに必要な機能を網羅しています。ただし、モバイル環境(Android)での使用には適していないため、用途に応じて適切なキャッシュライブラリを選択することが重要です。