缓存机制详解:JPA、Hibernate、Redis、JCache 的关系
本文档详细解释 Spring Data JPA、Hibernate、Redis、JCache 之间的关系,以及 MyBatis 的缓存机制。
1. Spring Data JPA 和 Hibernate JCache 的关系
Spring Data JPA 的本质
你的理解:Spring Data JPA 是简单处理数据库数据写 SQL 语句执行的
实际情况:Spring Data JPA 是一个抽象层,它本身不直接执行 SQL,而是:
提供统一的 Repository 接口
javapublic interface UserRepository extends JpaRepository<User, Long> { // Spring Data JPA 提供的方法 User findByUsername(String username); }底层使用 ORM 框架(默认是 Hibernate)
- Spring Data JPA 是规范(JPA 规范)
- Hibernate 是实现(JPA 规范的实现之一)
- 关系:Spring Data JPA → JPA 规范 → Hibernate 实现
执行流程:
你的代码:userRepository.findByUsername("admin") ↓ Spring Data JPA:解析方法名,生成查询 ↓ Hibernate:将查询转换为 SQL ↓ 数据库:执行 SQL ↓ Hibernate:将结果映射为 Java 对象 ↓ 返回给你:User 对象
Hibernate 的二级缓存(JCache)
Hibernate 有两级缓存:
一级缓存(Session 缓存)
- 作用域:单个 Session(事务)
- 自动管理:Hibernate 自动管理,无需配置
- 生命周期:事务结束即清除
- 示例:java
// 第一次查询,访问数据库 User user1 = session.get(User.class, 1L); // 第二次查询,从一级缓存获取,不访问数据库 User user2 = session.get(User.class, 1L);
二级缓存(JCache)
- 作用域:跨 Session,应用级别
- 需要配置:需要显式配置(如 EhCache、Redis 等)
- 生命周期:应用运行期间
- 示例:java
// Session 1:第一次查询,访问数据库,存入二级缓存 User user1 = session1.get(User.class, 1L); // Session 2:从二级缓存获取,不访问数据库 User user2 = session2.get(User.class, 1L);
为什么需要 Hibernate JCache?
JCache (JSR-107) 是 Java 缓存规范:
- 定义了统一的缓存 API
- 支持多种缓存实现(EhCache、Redis、Caffeine 等)
- Hibernate 通过 JCache 接口使用缓存
关系链:
Spring Data JPA
↓
Hibernate (ORM)
↓
JCache (缓存规范)
↓
EhCache/Redis (缓存实现)为什么配置了 EhCache 还需要 hibernate-jcache?
ehcache依赖:提供 EhCache 缓存实现hibernate-jcache依赖:提供 Hibernate 和 JCache 之间的桥接- 没有
hibernate-jcache,Hibernate 无法使用 JCache 规范的缓存
2. Redis Repository 是什么?
Redis Repository 的概念
Redis Repository 是 Spring Data Redis 提供的功能,类似于 JPA Repository:
java
// JPA Repository(你当前使用的)
@Repository
public interface UserRepository extends JpaRepository<User, Long> {
User findByUsername(String username);
}
// Redis Repository(如果使用)
@Repository
public interface UserRedisRepository extends CrudRepository<User, String> {
User findByUsername(String username);
}Redis Repository 的特点
- 数据存储:数据存储在 Redis 中,不是数据库
- 使用场景:
- 临时数据(如会话、购物车)
- 缓存数据(如热点数据)
- 不需要持久化的数据
- 序列化:需要将对象序列化为 Redis 可存储的格式
为什么会冲突?
问题场景:
你的项目结构:
com.alisunxin.api.infrastructure.dao.repository/
├── ActivityRepository.java (JPA Repository)
├── UserRepository.java (JPA Repository)
└── ...
Spring Boot 启动时:
1. Spring Data JPA 扫描:找到 ActivityRepository,识别为 JPA Repository ✅
2. Spring Data Redis 扫描:也扫描到 ActivityRepository,尝试识别为 Redis Repository ❌
3. 发现 ActivityRepository 继承的是 JpaRepository,不是 Redis Repository
4. 发出警告:无法安全识别为 Redis Repository为什么会扫描?
- Spring Boot 的自动配置会扫描所有 Repository 接口
- 如果同时引入了
spring-boot-starter-data-jpa和spring-boot-starter-data-redis - 两者都会尝试识别 Repository
解决方案:
yaml
spring:
data:
redis:
repositories:
enabled: false # 禁用 Redis Repository 自动配置Redis 的两种使用方式
方式 1:Redis Repository(你不需要)
java
@Repository
public interface UserRedisRepository extends CrudRepository<User, String> {
// 数据存储在 Redis,不是数据库
}方式 2:RedisTemplate(你当前使用的)
java
@Autowired
private RedisTemplate<String, Object> redisTemplate;
public void cacheUser(User user) {
redisTemplate.opsForValue().set("user:" + user.getId(), user);
}你的项目使用的是方式 2,所以不需要 Redis Repository。
3. MyBatis 的缓存机制
MyBatis 的缓存
MyBatis 也有两级缓存:
一级缓存(SqlSession 缓存)
- 作用域:单个 SqlSession
- 自动管理:MyBatis 自动管理
- 生命周期:SqlSession 关闭即清除
- 示例:java
// 第一次查询 User user1 = sqlSession.selectOne("getUserById", 1L); // 第二次查询,从一级缓存获取 User user2 = sqlSession.selectOne("getUserById", 1L);
二级缓存(Mapper 级别缓存)
- 作用域:Mapper 级别,跨 SqlSession
- 需要配置:需要在 Mapper.xml 中启用
- 默认实现:内存缓存(HashMap)
- 配置示例:xml
<!-- MyBatis 配置 --> <settings> <setting name="cacheEnabled" value="true"/> </settings> <!-- Mapper.xml --> <mapper namespace="com.example.UserMapper"> <cache/> <!-- 启用二级缓存 --> <select id="getUserById" resultType="User"> SELECT * FROM user WHERE id = #{id} </select> </mapper>
MyBatis vs Hibernate 缓存对比
| 特性 | MyBatis | Hibernate |
|---|---|---|
| 一级缓存 | SqlSession 级别 | Session 级别 |
| 二级缓存 | Mapper 级别,默认内存 | 应用级别,支持多种实现 |
| 配置方式 | Mapper.xml 中 <cache/> | Hibernate 配置 + JCache |
| 缓存实现 | 默认 HashMap,可扩展 | 支持 EhCache、Redis 等 |
| 缓存粒度 | SQL 级别 | Entity 级别 |
| 自动管理 | 需要手动配置 | 自动管理 Entity 缓存 |
为什么 MyBatis 不需要 JCache?
MyBatis 的缓存机制:
- 默认实现:使用内存(HashMap)作为二级缓存
- 扩展性:可以实现
Cache接口自定义缓存 - 不需要 JCache:MyBatis 有自己的缓存接口,不依赖 JCache 规范
MyBatis 自定义缓存示例:
java
public class RedisCache implements Cache {
// 实现 Cache 接口,使用 Redis 作为缓存
}xml
<!-- 使用自定义缓存 -->
<cache type="com.example.RedisCache"/>为什么 Hibernate 需要 JCache?
Hibernate 的设计理念:
- 标准化:遵循 JCache (JSR-107) 标准
- 灵活性:可以切换不同的缓存实现(EhCache、Redis、Caffeine)
- 统一接口:通过 JCache 接口,无需修改代码即可切换缓存实现
对比:
MyBatis:
自定义 Cache 接口 → 需要为每个缓存实现编写适配器
Hibernate:
JCache 标准接口 → 所有 JCache 实现都可以直接使用总结
1. Spring Data JPA 和 Hibernate JCache
Spring Data JPA(抽象层)
↓
Hibernate(ORM 实现)
↓
JCache(缓存规范)
↓
EhCache(缓存实现)- Spring Data JPA 不直接执行 SQL,而是通过 Hibernate
- Hibernate 使用 JCache 规范来支持二级缓存
- 需要
hibernate-jcache作为桥接
2. Redis Repository
- Redis Repository 是 Spring Data Redis 提供的功能
- 类似于 JPA Repository,但数据存储在 Redis
- 你的项目不需要,因为使用的是 RedisTemplate
- 可以通过配置禁用,避免误识别 JPA Repository
3. MyBatis 缓存
- MyBatis 有自己的缓存机制
- 一级缓存:SqlSession 级别(自动)
- 二级缓存:Mapper 级别(需要配置)
- 默认使用内存缓存,可以自定义实现
- 不需要 JCache,因为有自己的缓存接口
你的项目架构
应用层缓存(Redis)
↓
RedisTemplate → 缓存用户信息、会话等
数据库查询缓存(EhCache)
↓
Hibernate JCache → 缓存数据库查询结果
数据库
↓
MySQL → 持久化存储三者各司其职:
- Redis:应用层缓存(快速访问热点数据)
- EhCache:数据库查询缓存(减少数据库访问)
- MySQL:持久化存储(数据最终存储)
常见问题
Q1: 为什么 Hibernate 需要 JCache,而 MyBatis 不需要?
A:
- Hibernate 遵循 JCache 标准,可以灵活切换缓存实现
- MyBatis 有自己的缓存接口,不依赖标准
Q2: Redis 和 EhCache 可以同时使用吗?
A: 可以!它们用途不同:
- Redis:应用层缓存(分布式缓存)
- EhCache:Hibernate 二级缓存(本地缓存)
Q3: 为什么配置了 EhCache 还需要 hibernate-jcache?
A:
ehcache:提供缓存实现hibernate-jcache:提供 Hibernate 和 JCache 之间的桥接- 两者缺一不可
Q4: Redis Repository 和 RedisTemplate 有什么区别?
A:
- Redis Repository:类似 JPA Repository,自动 CRUD,数据存储在 Redis
- RedisTemplate:手动操作 Redis,更灵活,你当前使用的
Q5: MyBatis 的缓存和 Hibernate 的缓存哪个更好?
A: 各有优势:
- MyBatis:缓存粒度更细(SQL 级别),更可控
- Hibernate:缓存更智能(Entity 级别),自动管理关联关系