Ehcache

Ehcache 是 Java 生态最成熟的进程内缓存框架,实现 JSR-107(JCache) 标准,Hibernate 默认使用其作为二级缓存(L2 Cache)。3.x 版本支持堆内(Heap)、堆外(Off-Heap)、磁盘(Disk)三层存储,适合缓存大量数据且不希望增加 GC 压力的场景。

适合:Hibernate L2 Cache、会话缓存、本地大对象缓存;分布式场景优先选 Redis


三层存储模型

L1: Heap(JVM 堆)     ← 最快,受 GC 影响,容量小
     │ 溢出
     ▼
L2: Off-Heap(堆外)   ← 较快,不受 GC 影响,需序列化
     │ 溢出
     ▼
L3: Disk(磁盘)       ← 最慢,容量最大,持久化

写入数据时先放 Heap,热点数据留在 Heap,冷数据下沉到 Off-Heap / Disk,淘汰时按配置策略(LRU / LFU / FIFO)驱逐。


快速上手

<dependency>
    <groupId>org.ehcache</groupId>
    <artifactId>ehcache</artifactId>
    <version>3.10.8</version>
</dependency>

编程式配置

CacheManager cacheManager = CacheManagerBuilder.newCacheManagerBuilder()
    .withCache("userCache",
        CacheConfigurationBuilder.newCacheConfigurationBuilder(
            Long.class, User.class,
            ResourcePoolsBuilder.newResourcePoolsBuilder()
                .heap(1000, EntryUnit.ENTRIES)          // L1:堆内 1000 条
                .offheap(100, MemoryUnit.MB)             // L2:堆外 100MB
                .disk(1, MemoryUnit.GB, true)            // L3:磁盘 1GB,true=持久化
        )
        .withExpiry(ExpiryPolicyBuilder.timeToLiveExpiration(Duration.ofMinutes(30)))
        .withLoaderWriter(new UserCacheLoaderWriter())   // 读穿/写穿
    )
    .build(true);  // true = 立即初始化
 
Cache<Long, User> userCache = cacheManager.getCache("userCache", Long.class, User.class);
 
userCache.put(1L, new User(1L, "Alice"));
User user = userCache.get(1L);      // 命中返回,未命中 LoaderWriter 加载
userCache.remove(1L);

XML 配置(ehcache.xml)

<config xmlns="http://www.ehcache.org/v3">
 
    <cache alias="userCache">
        <key-type>java.lang.Long</key-type>
        <value-type>com.example.User</value-type>
 
        <expiry>
            <ttl unit="minutes">30</ttl>
        </expiry>
 
        <resources>
            <heap unit="entries">1000</heap>
            <offheap unit="MB">100</offheap>
            <disk persistent="true" unit="GB">1</disk>
        </resources>
    </cache>
 
</config>

Spring Boot 集成(JCache / JSR-107)

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-cache</artifactId>
</dependency>
<dependency>
    <groupId>org.ehcache</groupId>
    <artifactId>ehcache</artifactId>
    <classifier>jakarta</classifier>  <!-- Spring Boot 3.x 用 Jakarta 版 -->
</dependency>
<dependency>
    <groupId>javax.cache</groupId>
    <artifactId>cache-api</artifactId>
</dependency>
spring:
  cache:
    type: jcache
    jcache:
      config: classpath:ehcache.xml
@Configuration
@EnableCaching
public class CacheConfig {}
@Cacheable(value = "userCache", key = "#id")
public User getUser(Long id) {
    return userRepository.findById(id).orElseThrow();
}
 
@CachePut(value = "userCache", key = "#user.id")
public User updateUser(User user) {
    return userRepository.save(user);
}
 
@CacheEvict(value = "userCache", key = "#id")
public void deleteUser(Long id) {
    userRepository.deleteById(id);
}

读穿 / 写穿(CacheLoaderWriter)

public class UserCacheLoaderWriter implements CacheLoaderWriter<Long, User> {
 
    @Autowired
    private UserRepository userRepository;
 
    @Override
    public User load(Long key) {
        // 缓存 miss 时自动从 DB 加载
        return userRepository.findById(key).orElse(null);
    }
 
    @Override
    public void write(Long key, User value) {
        // put 时同步写入 DB(写穿)
        userRepository.save(value);
    }
 
    @Override
    public void delete(Long key) {
        userRepository.deleteById(key);
    }
 
    @Override
    public Map<Long, User> loadAll(Iterable<? extends Long> keys) {
        List<Long> ids = StreamSupport.stream(keys.spliterator(), false)
            .collect(Collectors.toList());
        return userRepository.findAllById(ids).stream()
            .collect(Collectors.toMap(User::getId, u -> u));
    }
}

Hibernate L2 Cache 集成

<!-- pom.xml -->
<dependency>
    <groupId>org.hibernate.orm</groupId>
    <artifactId>hibernate-jcache</artifactId>
</dependency>
spring:
  jpa:
    properties:
      hibernate:
        cache:
          use_second_level_cache: true
          use_query_cache: true
          region.factory_class: jcache
          javax.cache.provider: org.ehcache.jsr107.EhcacheCachingProvider
          javax.cache.uri: classpath:ehcache.xml
@Entity
@Cache(usage = CacheConcurrencyStrategy.READ_WRITE)  // 开启实体二级缓存
public class User { ... }

缓存事件监听

CacheEventListenerConfigurationBuilder listener =
    CacheEventListenerConfigurationBuilder
        .newEventListenerConfiguration(
            event -> log.info("事件: {} key={}", event.getType(), event.getKey()),
            EventType.CREATED, EventType.UPDATED, EventType.EXPIRED, EventType.EVICTED
        )
        .asynchronous()        // 异步触发,不影响主流程
        .unordered();

Ehcache vs Caffeine

特性Ehcache 3Caffeine
多层存储堆 + 堆外 + 磁盘仅堆内
持久化支持(磁盘层)不支持
JSR-107完整实现需适配层
Hibernate L2官方支持需额外适配
淘汰算法LRU / LFU / FIFOW-TinyLFU(命中率更高)
性能(纯堆内)略低更高
适用场景大对象、持久化、Hibernate高频小对象、本地热点缓存

相关链接

  • Caffeine — 纯堆内缓存,命中率更高,适合高频小对象
  • Redis — 分布式缓存,多实例共享
  • 缓存 ← 返回缓存目录