在 Java 缓存生态里,Redis 几乎成了分布式缓存的代名词,但提到本地缓存,有一个 “老伙计” 你一定绕不开 ——Ehcache。作为纯 Java 实现的进程内缓存框架,Ehcache 凭借轻量、易集成、高性能的特点,至今仍是中小型项目、单机应用的首选。本文带你吃透 Ehcache 的核心用法、适用场景,以及和 Redis 的选型逻辑。
一、Ehcache 是什么?先搞懂核心定位
Ehcache 是一款开源的纯 Java 本地缓存框架,核心作用是将高频访问、变更不频繁的数据缓存到 JVM 内存中,避免频繁查询数据库 / 调用接口,从而提升系统响应速度、降低后端存储压力。
关键特征(为什么它能火这么多年?)
本地性:数据存在当前应用的 JVM 内存里,读写无网络开销,速度比 Redis 更快(毫秒级→微秒级);
轻量级:无外部依赖,引入 jar 包就能用,Spring Boot 一键集成;
多级缓存:支持 “内存 + 磁盘” 两级缓存,内存满了自动刷盘,避免 OOM;
灵活配置:自定义过期时间、淘汰策略(LRU/FIFO)、缓存大小,适配不同业务场景;
生态友好:完美兼容 Spring Cache、MyBatis、Hibernate 等主流框架,开箱即用。
二、Ehcache 实战:Spring Boot 集成三步搞定
以 “电商商品缓存” 为例,手把手教你落地 Ehcache,全程复制就能用。
1. 引入依赖
<!-- Spring Boot 缓存核心依赖 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-cache</artifactId>
</dependency>
<!-- Ehcache 核心包 -->
<dependency>
<groupId>net.sf.ehcache</groupId>
<artifactId>ehcache</artifactId>
<version>2.10.6</version> <!-- 稳定版本,适配大部分项目 -->
</dependency>
2. 配置 Ehcache 规则
在 resources 目录下创建 ehcache.xml,定义缓存策略(核心是 “控制缓存大小 + 过期时间”):
<ehcache xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:noNamespaceSchemaLocation="ehcache.xsd">
<!-- 磁盘缓存文件存储路径(默认临时目录) -->
<diskStore path="java.io.tmpdir/ehcache"/>
<!-- 默认缓存配置(兜底规则) -->
<defaultCache
maxEntriesLocalHeap="10000" <!-- 内存最大缓存条目数 -->
eternal="false" <!-- 是否永久有效(建议false,避免内存溢出) -->
timeToIdleSeconds="300" <!-- 闲置5分钟过期(无访问则失效) -->
timeToLiveSeconds="600" <!-- 存活10分钟过期(创建后无论是否访问都失效) -->
diskExpiryThreadIntervalSeconds="120" <!-- 磁盘过期扫描线程间隔 -->
memoryStoreEvictionPolicy="LRU"/> <!-- 内存淘汰策略:最近最少使用 -->
<!-- 自定义缓存:商品缓存(针对性配置) -->
<cache name="goodsCache"
maxEntriesLocalHeap="5000" <!-- 商品缓存量小,按需调整 -->
eternal="false"
timeToIdleSeconds="600" <!-- 商品信息变更少,闲置10分钟过期 -->
timeToLiveSeconds="1800" <!-- 存活30分钟 -->
maxEntriesLocalDisk="10000" <!-- 磁盘最大缓存数 -->
diskPersistent="false"/> <!-- 重启是否持久化到磁盘(默认false) -->
</cache>
3. 业务代码中使用
结合 Spring Cache 注解,一行代码实现缓存逻辑,无需手动操作缓存:
@Service
@CacheConfig(cacheNames = "goodsCache") // 全局指定缓存名称,简化后续注解
public class GoodsService {
@Autowired
private GoodsMapper goodsMapper;
/**
* 查询商品:缓存命中则直接返回,未命中则查库并缓存
* key = 商品ID,保证缓存唯一性
*/
@Cacheable(key = "#goodsId", unless = "#result == null") // 空结果不缓存
public GoodsVO getGoodsById(Long goodsId) {
// 仅第一次调用/缓存过期时执行,后续走内存缓存
return goodsMapper.selectById(goodsId);
}
/**
* 更新商品:删除对应缓存,避免缓存脏数据
*/
@CacheEvict(key = "#goodsDTO.id")
public void updateGoods(GoodsDTO goodsDTO) {
goodsMapper.updateById(goodsDTO);
}
/**
* 批量删除商品:清空整个商品缓存
*/
@CacheEvict(allEntries = true)
public void batchDeleteGoods(List<Long> goodsIdList) {
goodsMapper.deleteBatchIds(goodsIdList);
}
}
三、Ehcache 用在哪?看场景选对才高效
Ehcache 不是 “万能缓存”,核心适配本地、低并发、小数据量场景,具体分三类:
✅ 首选场景(用 Ehcache 性价比最高)
后台管理系统:OA/CRM/ERP 等单机 / 小集群部署的系统,数据量小、访问量低,无需分布式缓存;
MyBatis/Hibernate 二级缓存:框架原生支持,无需额外开发,提升数据库查询效率;
Shiro 权限缓存:缓存用户角色、权限信息,避免每次接口校验都查库;
单机高频读场景:比如定时任务配置、字典表数据,变更少、访问频繁。
❌ 不推荐场景(优先选 Redis)
分布式集群:多服务实例需要共享缓存(比如用户登录态、订单状态);
高并发 / 大数据量:每秒万级以上访问、缓存数据量超 GB 级,JVM 内存扛不住;
跨语言场景:系统包含 Java/PHP/Go 等多语言服务,需要统一缓存中间件。
📌 最优组合:Ehcache + Redis 两级缓存
大型项目常用 “本地缓存 + 分布式缓存” 架构,兼顾速度和一致性:
一级缓存(Ehcache):缓存热点数据,优先读取,减少网络开销;
二级缓存(Redis):缓存分布式共享数据,兜底一级缓存未命中的场景;
缓存更新:更新数据时,先删 Redis 缓存,再删 Ehcache 缓存,保证数据一致性。
四、避坑指南:使用 Ehcache 必注意的 3 个问题
1. 内存溢出(OOM)
问题:缓存数据过多,占用 JVM 堆内存导致 OOM;
解决:合理配置
maxEntriesLocalHeap(内存最大条目数)、timeToLiveSeconds(过期时间),开启磁盘缓存兜底。
2. 缓存数据不一致(多实例场景)
问题:单机缓存不共享,A 实例更新数据,B 实例缓存还是旧数据;
解决:① 小集群可通过广播通知刷新缓存;② 核心数据优先用 Redis;③ 给缓存设置合理过期时间,兜底不一致问题。
3. 缓存穿透 / 击穿
缓存穿透:查询不存在的数据,每次都查库;解决:缓存空值(
unless = "#result == null"可调整为缓存空值);缓存击穿:热点 key 过期瞬间,大量请求打穿到数据库;解决:设置热点 key 永不过期(
eternal = true),或加分布式锁。
五、总结:Ehcache 该不该用?
Ehcache 虽然不如 Redis “热门”,但在本地缓存领域仍是标杆:
中小型项目 / 单机应用:优先用 Ehcache,轻量、易集成、成本低;
分布式 / 高并发项目:以 Redis 为主,Ehcache 为辅做一级缓存;
学习成本:掌握 Spring Cache 注解 + 核心配置即可,无需深钻底层,够用就好。
Java 本地缓存 Ehcache 该怎么用?
https://lautung.com/archives/gNDnvddv