基于Spring Data JPA与Redis二级缓存集成实战指南
在高并发场景中,数据库频繁查询会成为系统性能瓶颈。本文将以Spring Boot为基础,深入讲解如何通过Spring Data JPA与Redis二级缓存结合,提升查询性能,减少数据库压力,并提供可运行的完整代码示例,帮助有一定技术基础的后端开发者快速落地。
一、业务场景描述
在电商系统的订单查询模块中,商户和用户会频繁查询订单详情。如果每次都 hit 数据库,读写压力会迅速增大。为了提升系统性能,需要在ORM框架级别进行缓存优化,采用Redis作为二级缓存,减少数据库访问次数。
二、技术选型过程
- Spring Data JPA:提供丰富的ORM特性和二级缓存扩展支持。
- Redis:高性能键值存储,支持丰富的数据结构和高并发读写。
- Spring Cache:Spring内置缓存抽象,支持注解化使用。
选择Spring Data JPA与Spring Cache无缝对接Redis,可以最小改造实现二级缓存功能,且易于维护。
三、实现方案详解
1. 项目结构
spring-data-jpa-redis-cache/
├── src/main/java/com/example/cache/
│ ├── config/CacheConfig.java // 缓存配置
│ ├── entity/Order.java // JPA实体
│ ├── repository/OrderRepository.java // JPA仓库
│ ├── service/OrderService.java // 业务接口
│ ├── service/impl/OrderServiceImpl.java // 业务实现
│ └── SpringBootCacheApplication.java // 启动类
└── src/main/resources/
└── application.yml // 配置文件
2. 依赖配置
<dependencies>
<!-- Spring Boot Starter Data JPA -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>
<!-- Spring Boot Starter Cache -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-cache</artifactId>
</dependency>
<!-- Jedis 客户端 -->
<dependency>
<groupId>redis.clients</groupId>
<artifactId>jedis</artifactId>
<version>4.2.3</version>
</dependency>
<!-- Spring Data Redis -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
<!-- 数据库驱动,这里以MySQL为例 -->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<scope>runtime</scope>
</dependency>
</dependencies>
3. Redis 与 JPA 二级缓存配置
在 CacheConfig.java
中开启缓存并配置 Redis 连接:
package com.example.cache.config;
import org.springframework.cache.annotation.EnableCaching;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.serializer.GenericJackson2JsonRedisSerializer;
import org.springframework.data.redis.serializer.StringRedisSerializer;
@Configuration
@EnableCaching // 启用缓存
public class CacheConfig {
// 自定义 RedisTemplate,使用 String 序列化 key,JSON 序列化 value
@Bean
public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory factory) {
RedisTemplate<String, Object> template = new RedisTemplate<>();
template.setConnectionFactory(factory);
template.setKeySerializer(new StringRedisSerializer());
template.setValueSerializer(new GenericJackson2JsonRedisSerializer());
template.afterPropertiesSet();
return template;
}
}
在 application.yml
中配置数据库与 Redis:
spring:
datasource:
url: jdbc:mysql://localhost:3306/shop_db?useSSL=false&characterEncoding=UTF-8
username: root
password: password
jpa:
hibernate:
ddl-auto: update
show-sql: true
redis:
host: localhost
port: 6379
jedis:
pool:
max-active: 10
max-idle: 5
min-idle: 1
max-wait: 2000ms
4. JPA 实体与仓库
// Order.java
package com.example.cache.entity;
import javax.persistence.*;
import java.time.LocalDateTime;
@Entity
@Table(name = "orders")
public class Order {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String userId;
private String productId;
private Integer quantity;
private LocalDateTime createTime;
// getter & setter ...
}
// OrderRepository.java
package com.example.cache.repository;
import com.example.cache.entity.Order;
import org.springframework.data.jpa.repository.JpaRepository;
public interface OrderRepository extends JpaRepository<Order, Long> {
}
5. Service 层缓存注解示例
// OrderService.java
package com.example.cache.service;
import com.example.cache.entity.Order;
import java.util.Optional;
public interface OrderService {
Optional<Order> getOrderById(Long id);
Order saveOrder(Order order);
}
// OrderServiceImpl.java
package com.example.cache.service.impl;
import com.example.cache.entity.Order;
import com.example.cache.repository.OrderRepository;
import com.example.cache.service.OrderService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cache.annotation.CacheEvict;
import org.springframework.cache.annotation.Cacheable;
import org.springframework.stereotype.Service;
import java.util.Optional;
@Service
public class OrderServiceImpl implements OrderService {
private final OrderRepository orderRepository;
@Autowired
public OrderServiceImpl(OrderRepository orderRepository) {
this.orderRepository = orderRepository;
}
@Override
@Cacheable(value = "order_cache", key = "#id")
public Optional<Order> getOrderById(Long id) {
// 首次查询会触发数据库访问,之后从Redis缓存读取
return orderRepository.findById(id);
}
@Override
@CacheEvict(value = "order_cache", key = "#order.id")
public Order saveOrder(Order order) {
// 更新数据时清除缓存
return orderRepository.save(order);
}
}
6. 启动类
package com.example.cache;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication
public class SpringBootCacheApplication {
public static void main(String[] args) {
SpringApplication.run(SpringBootCacheApplication.class, args);
}
}
四、踩过的坑与解决方案
缓存序列化不一致导致反序列化失败
- 原因:默认使用 JDK 序列化,字段变更后无法兼容。
- 解决:统一使用
GenericJackson2JsonRedisSerializer
。
JPA二级缓存和Spring Cache冲突
- 原因:Hibernate自身二级缓存和Spring Cache重复。
- 解决:关闭Hibernate二级缓存,仅使用Spring Cache管理Redis缓存。
在
application.yml
中添加:spring: jpa: properties: hibernate: cache: use_second_level_cache: false use_query_cache: false
缓存雪崩风险
- 原因:大量缓存同时过期导致DB突击。
- 解决:使用随机过期时间、热点数据永不过期或手动刷新策略。
五、总结与最佳实践
- 通过Spring Cache注解化集成Spring Data JPA与Redis,极大简化二级缓存实现。
- 配置合理的序列化方案与过期策略,保证系统稳定性与性能。
- 结合生产环境监控(如Spring Boot Actuator、Redis监控),实时观察缓存命中率与Key分布。
- 根据业务场景使用本地缓存与分布式缓存组合,进一步降低网络IO消耗。
至此,基于Spring Data JPA与Redis的二级缓存实战方案就完成了。希望本文能帮助开发者在高并发场景下快速搭建可靠、高效的查询缓存体系。