From 14fe13f310ef690ebc144d09bf9667fa782cbdd7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E9=94=90=20=E5=BD=AD?= <2781841463@qq.com> Date: Thu, 9 Jul 2026 19:39:21 +0800 Subject: [PATCH 1/2] =?UTF-8?q?=E6=96=B0=E5=A2=9Eredis=E9=85=8D=E7=BD=AE?= =?UTF-8?q?=EF=BC=8C=E5=B9=B6=E5=B0=86redis=E5=B0=81=E8=A3=85=E6=88=90?= =?UTF-8?q?=E7=BB=9F=E4=B8=80=E5=B0=81=E8=A3=85=E7=B1=BB?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../com/example/demo/config/RedisConfig.java | 57 ++++++ .../example/demo/service/RedisService.java | 103 ++++++++++ .../demo/service/impl/RedisServiceImpl.java | 166 ++++++++++++++++ .../java/com/example/demo/util/RedisUtil.java | 186 ++++++++++++++++++ 4 files changed, 512 insertions(+) create mode 100644 src/main/java/com/example/demo/config/RedisConfig.java create mode 100644 src/main/java/com/example/demo/service/RedisService.java create mode 100644 src/main/java/com/example/demo/service/impl/RedisServiceImpl.java create mode 100644 src/main/java/com/example/demo/util/RedisUtil.java diff --git a/src/main/java/com/example/demo/config/RedisConfig.java b/src/main/java/com/example/demo/config/RedisConfig.java new file mode 100644 index 0000000..7afc6b0 --- /dev/null +++ b/src/main/java/com/example/demo/config/RedisConfig.java @@ -0,0 +1,57 @@ +package com.example.demo.config; + +import com.fasterxml.jackson.annotation.JsonAutoDetect; +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.annotation.PropertyAccessor; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.databind.SerializationFeature; +import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule; +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.RedisSerializer; +import org.springframework.data.redis.serializer.StringRedisSerializer; + +@Configuration +public class RedisConfig { + + @Bean + public RedisTemplate redisTemplate(RedisConnectionFactory connectionFactory) { + // 1. 自定义 ObjectMapper + ObjectMapper objectMapper = new ObjectMapper(); + // 注册 Java 8 时间模块(LocalDate、LocalDateTime 等) + objectMapper.registerModule(new JavaTimeModule()); + // 禁用 write-dates-as-timestamps,让日期序列化为可读格式 + objectMapper.disable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS); + // 不序列化 null 值,减少数据量 + objectMapper.setSerializationInclusion(JsonInclude.Include.NON_NULL); + // 按字段序列化(而非 getter/setter) + objectMapper.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY); + // 激活默认类型,序列化时写入 @class,反序列化时自动还原子类型 + objectMapper.activateDefaultTyping( + objectMapper.getPolymorphicTypeValidator(), + ObjectMapper.DefaultTyping.NON_FINAL + ); + + // 2. 基于自定义 ObjectMapper 创建 JSON 序列化器 + GenericJackson2JsonRedisSerializer jsonSerializer = + new GenericJackson2JsonRedisSerializer(objectMapper); + + // 3. 组装 RedisTemplate + RedisTemplate template = new RedisTemplate<>(); + template.setConnectionFactory(connectionFactory); + + // Key / HashKey → 字符串序列化 + template.setKeySerializer(RedisSerializer.string()); + template.setHashKeySerializer(RedisSerializer.string()); + + // Value / HashValue → JSON 序列化 + template.setValueSerializer(jsonSerializer); + template.setHashValueSerializer(jsonSerializer); + + template.afterPropertiesSet(); + return template; + } +} diff --git a/src/main/java/com/example/demo/service/RedisService.java b/src/main/java/com/example/demo/service/RedisService.java new file mode 100644 index 0000000..0d59e14 --- /dev/null +++ b/src/main/java/com/example/demo/service/RedisService.java @@ -0,0 +1,103 @@ +package com.example.demo.service; + +import java.util.Collection; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.concurrent.TimeUnit; + +/** + * Redis 服务接口 + */ +public interface RedisService { + + // ==================== Key 操作 ==================== + + /** 判断 key 是否存在 */ + boolean exists(String key); + + /** 删除 key */ + boolean delete(String key); + + /** 批量删除 key */ + long deleteBatch(Collection keys); + + /** 设置过期时间 */ + boolean expire(String key, long timeout, TimeUnit unit); + + /** 获取剩余过期时间(秒) */ + long getExpire(String key); + + // ==================== String 操作 ==================== + + /** 设置值(永不过期) */ + void set(String key, Object value); + + /** 设置值并指定过期时间 */ + void set(String key, Object value, long timeout, TimeUnit unit); + + /** 获取值 */ + T get(String key); + + /** 递增 */ + long incr(String key, long delta); + + // ==================== Hash 操作 ==================== + + /** 设置 Hash 字段 */ + void hSet(String key, String field, Object value); + + /** 批量设置 Hash */ + void hSetAll(String key, Map map); + + /** 获取 Hash 字段 */ + T hGet(String key, String field); + + /** 获取整个 Hash */ + Map hGetAll(String key); + + /** 删除 Hash 字段 */ + long hDelete(String key, Object... fields); + + /** 判断 Hash 字段是否存在 */ + boolean hExists(String key, String field); + + // ==================== List 操作 ==================== + + /** 头部插入 */ + long lPush(String key, Object value); + + /** 尾部插入 */ + long rPush(String key, Object value); + + /** 获取 List 指定范围元素 */ + List lRange(String key, long start, long end); + + /** 获取 List 长度 */ + long lSize(String key); + + // ==================== Set 操作 ==================== + + /** 添加 Set 元素 */ + long sAdd(String key, Object... values); + + /** 获取 Set 所有成员 */ + Set sMembers(String key); + + /** 判断元素是否在 Set 中 */ + boolean sIsMember(String key, Object value); + + /** 获取 Set 大小 */ + long sSize(String key); + + // ==================== ZSet 操作 ==================== + + /** 添加 ZSet 元素 */ + boolean zAdd(String key, Object value, double score); + + /** 按分数范围获取 ZSet 元素 */ + Set zRangeByScore(String key, double min, double max); + + /** 删除 ZSet 元素 */ + long zRemove(String key, Object... values); +} diff --git a/src/main/java/com/example/demo/service/impl/RedisServiceImpl.java b/src/main/java/com/example/demo/service/impl/RedisServiceImpl.java new file mode 100644 index 0000000..533534f --- /dev/null +++ b/src/main/java/com/example/demo/service/impl/RedisServiceImpl.java @@ -0,0 +1,166 @@ +package com.example.demo.service.impl; + +import com.example.demo.service.RedisService; +import com.example.demo.util.RedisUtil; +import org.springframework.stereotype.Service; + +import java.util.Collection; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.concurrent.TimeUnit; + +/** + * Redis 服务实现 + */ +@Service +public class RedisServiceImpl implements RedisService { + + private final RedisUtil redisUtil; + + public RedisServiceImpl(RedisUtil redisUtil) { + this.redisUtil = redisUtil; + } + + // ==================== Key 操作 ==================== + + @Override + public boolean exists(String key) { + return redisUtil.hasKey(key); + } + + @Override + public boolean delete(String key) { + return redisUtil.delete(key); + } + + @Override + public long deleteBatch(Collection keys) { + return redisUtil.delete(keys); + } + + @Override + public boolean expire(String key, long timeout, TimeUnit unit) { + return redisUtil.expire(key, timeout, unit); + } + + @Override + public long getExpire(String key) { + return redisUtil.getExpire(key); + } + + // ==================== String 操作 ==================== + + @Override + public void set(String key, Object value) { + redisUtil.set(key, value); + } + + @Override + public void set(String key, Object value, long timeout, TimeUnit unit) { + redisUtil.set(key, value, timeout, unit); + } + + @Override + public T get(String key) { + return redisUtil.get(key); + } + + @Override + public long incr(String key, long delta) { + return redisUtil.incr(key, delta); + } + + // ==================== Hash 操作 ==================== + + @Override + public void hSet(String key, String field, Object value) { + redisUtil.hSet(key, field, value); + } + + @Override + public void hSetAll(String key, Map map) { + redisUtil.hSetAll(key, map); + } + + @Override + public T hGet(String key, String field) { + return redisUtil.hGet(key, field); + } + + @Override + public Map hGetAll(String key) { + return redisUtil.hGetAll(key); + } + + @Override + public long hDelete(String key, Object... fields) { + return redisUtil.hDelete(key, fields); + } + + @Override + public boolean hExists(String key, String field) { + return redisUtil.hHasKey(key, field); + } + + // ==================== List 操作 ==================== + + @Override + public long lPush(String key, Object value) { + return redisUtil.lPush(key, value); + } + + @Override + public long rPush(String key, Object value) { + return redisUtil.rPush(key, value); + } + + @Override + public List lRange(String key, long start, long end) { + return redisUtil.lRange(key, start, end); + } + + @Override + public long lSize(String key) { + return redisUtil.lSize(key); + } + + // ==================== Set 操作 ==================== + + @Override + public long sAdd(String key, Object... values) { + return redisUtil.sAdd(key, values); + } + + @Override + public Set sMembers(String key) { + return redisUtil.sMembers(key); + } + + @Override + public boolean sIsMember(String key, Object value) { + return redisUtil.sIsMember(key, value); + } + + @Override + public long sSize(String key) { + return redisUtil.sSize(key); + } + + // ==================== ZSet 操作 ==================== + + @Override + public boolean zAdd(String key, Object value, double score) { + return redisUtil.zAdd(key, value, score); + } + + @Override + public Set zRangeByScore(String key, double min, double max) { + return redisUtil.zRangeByScore(key, min, max); + } + + @Override + public long zRemove(String key, Object... values) { + return redisUtil.zRemove(key, values); + } +} diff --git a/src/main/java/com/example/demo/util/RedisUtil.java b/src/main/java/com/example/demo/util/RedisUtil.java new file mode 100644 index 0000000..10581c8 --- /dev/null +++ b/src/main/java/com/example/demo/util/RedisUtil.java @@ -0,0 +1,186 @@ +package com.example.demo.util; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.data.redis.core.RedisTemplate; +import org.springframework.stereotype.Component; + +import java.util.Collection; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.concurrent.TimeUnit; + +/** + * Redis 工具类,封装 RedisTemplate 常用操作 + */ +@Component +public class RedisUtil { + + private final RedisTemplate redisTemplate; + + public RedisUtil(RedisTemplate redisTemplate) { + this.redisTemplate = redisTemplate; + } + + // ==================== Key 操作 ==================== + + /** 判断 key 是否存在 */ + public boolean hasKey(String key) { + return Boolean.TRUE.equals(redisTemplate.hasKey(key)); + } + + /** 删除 key */ + public boolean delete(String key) { + return Boolean.TRUE.equals(redisTemplate.delete(key)); + } + + /** 批量删除 key */ + public long delete(Collection keys) { + Long count = redisTemplate.delete(keys); + return count != null ? count : 0; + } + + /** 设置过期时间 */ + public boolean expire(String key, long timeout, TimeUnit unit) { + return Boolean.TRUE.equals(redisTemplate.expire(key, timeout, unit)); + } + + /** 获取过期时间(秒),未设置返回 -1 */ + public long getExpire(String key) { + Long expire = redisTemplate.getExpire(key); + return expire != null ? expire : -1; + } + + // ==================== String 操作 ==================== + + /** 设置值 */ + public void set(String key, Object value) { + redisTemplate.opsForValue().set(key, value); + } + + /** 设置值并指定过期时间 */ + public void set(String key, Object value, long timeout, TimeUnit unit) { + redisTemplate.opsForValue().set(key, value, timeout, unit); + } + + /** 获取值 */ + public T get(String key) { + @SuppressWarnings("unchecked") + T result = (T) redisTemplate.opsForValue().get(key); + return result; + } + + /** 递增 */ + public long incr(String key, long delta) { + Long result = redisTemplate.opsForValue().increment(key, delta); + return result != null ? result : 0; + } + + // ==================== Hash 操作 ==================== + + /** 设置 Hash 字段 */ + public void hSet(String key, String field, Object value) { + redisTemplate.opsForHash().put(key, field, value); + } + + /** 批量设置 Hash */ + public void hSetAll(String key, Map map) { + redisTemplate.opsForHash().putAll(key, map); + } + + /** 获取 Hash 字段 */ + public T hGet(String key, String field) { + @SuppressWarnings("unchecked") + T result = (T) redisTemplate.opsForHash().get(key, field); + return result; + } + + /** 获取整个 Hash */ + public Map hGetAll(String key) { + return redisTemplate.opsForHash().entries(key); + } + + /** 删除 Hash 字段 */ + public long hDelete(String key, Object... fields) { + Long result = redisTemplate.opsForHash().delete(key, fields); + return result != null ? result : 0; + } + + /** 判断 Hash 字段是否存在 */ + public boolean hHasKey(String key, String field) { + return redisTemplate.opsForHash().hasKey(key, field); + } + + // ==================== List 操作 ==================== + + /** 头部插入 */ + public long lPush(String key, Object value) { + Long result = redisTemplate.opsForList().leftPush(key, value); + return result != null ? result : 0; + } + + /** 尾部插入 */ + public long rPush(String key, Object value) { + Long result = redisTemplate.opsForList().rightPush(key, value); + return result != null ? result : 0; + } + + /** 获取 List 所有元素 */ + public List lRange(String key, long start, long end) { + return redisTemplate.opsForList().range(key, start, end); + } + + /** 获取 List 长度 */ + public long lSize(String key) { + Long result = redisTemplate.opsForList().size(key); + return result != null ? result : 0; + } + + // ==================== Set 操作 ==================== + + /** 添加 Set 元素 */ + public long sAdd(String key, Object... values) { + Long result = redisTemplate.opsForSet().add(key, values); + return result != null ? result : 0; + } + + /** 获取 Set 所有成员 */ + public Set sMembers(String key) { + return redisTemplate.opsForSet().members(key); + } + + /** 判断元素是否在 Set 中 */ + public boolean sIsMember(String key, Object value) { + return Boolean.TRUE.equals(redisTemplate.opsForSet().isMember(key, value)); + } + + /** 获取 Set 大小 */ + public long sSize(String key) { + Long result = redisTemplate.opsForSet().size(key); + return result != null ? result : 0; + } + + // ==================== ZSet 操作 ==================== + + /** 添加 ZSet 元素 */ + public boolean zAdd(String key, Object value, double score) { + return Boolean.TRUE.equals(redisTemplate.opsForZSet().add(key, value, score)); + } + + /** 按分数范围获取 ZSet 元素 */ + public Set zRangeByScore(String key, double min, double max) { + return redisTemplate.opsForZSet().rangeByScore(key, min, max); + } + + /** 删除 ZSet 元素 */ + public long zRemove(String key, Object... values) { + Long result = redisTemplate.opsForZSet().remove(key, values); + return result != null ? result : 0; + } + + /** 获取 ZSet 大小 */ + public long zSize(String key) { + Long result = redisTemplate.opsForZSet().size(key); + return result != null ? result : 0; + } +} -- Gitee From 1941c7261c372c5c48b6acbf285b3333c9fd7a24 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E9=94=90=20=E5=BD=AD?= <2781841463@qq.com> Date: Thu, 9 Jul 2026 19:45:36 +0800 Subject: [PATCH 2/2] =?UTF-8?q?=E5=9C=A8StudentServiceImpl=E4=B8=AD?= =?UTF-8?q?=E4=BD=BF=E7=94=A8redisUtil=E5=B7=A5=E5=85=B7=E7=B1=BB?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../demo/service/impl/StudentServiceImpl.java | 44 ++++++++++++++++--- 1 file changed, 38 insertions(+), 6 deletions(-) diff --git a/src/main/java/com/example/demo/service/impl/StudentServiceImpl.java b/src/main/java/com/example/demo/service/impl/StudentServiceImpl.java index 34a14ee..ba3ae44 100644 --- a/src/main/java/com/example/demo/service/impl/StudentServiceImpl.java +++ b/src/main/java/com/example/demo/service/impl/StudentServiceImpl.java @@ -4,21 +4,34 @@ import com.example.demo.common.BusinessException; import com.example.demo.entity.Student; import com.example.demo.mapper.StudentMapper; import com.example.demo.service.StudentService; +import com.example.demo.util.RedisUtil; import org.springframework.stereotype.Service; import java.util.List; +import java.util.concurrent.TimeUnit; /** * 学生服务实现 * 使用构造器注入 StudentMapper,通过 MyBatis 访问数据库 + * Redis 缓存:先查缓存,缓存未命中则查数据库并回写 */ @Service public class StudentServiceImpl implements StudentService { + private static final String CACHE_PREFIX = "student:"; + private static final long CACHE_TIMEOUT = 30; + private static final TimeUnit CACHE_TIMEOUT_UNIT = TimeUnit.MINUTES; + private final StudentMapper studentMapper; + private final RedisUtil redisUtil; - public StudentServiceImpl(StudentMapper studentMapper) { + public StudentServiceImpl(StudentMapper studentMapper, RedisUtil redisUtil) { this.studentMapper = studentMapper; + this.redisUtil = redisUtil; + } + + private String cacheKey(Integer id) { + return CACHE_PREFIX + id; } @Override @@ -28,23 +41,37 @@ public class StudentServiceImpl implements StudentService { @Override public Student findById(Integer id) { - Student student = studentMapper.findById(id); + String key = cacheKey(id); + + // 1. 先查 Redis 缓存 + Student student = redisUtil.get(key); + if (student != null) { + return student; + } + + // 2. 缓存未命中,查数据库 + student = studentMapper.findById(id); if (student == null) { throw new BusinessException(404, "学生不存在: id=" + id); } + + // 3. 回写缓存,设置 30 分钟过期 + redisUtil.set(key, student, CACHE_TIMEOUT, CACHE_TIMEOUT_UNIT); return student; } @Override public Student create(Student student) { studentMapper.insert(student); - // insert 后 MyBatis 自动回填 id,直接查回完整对象 - return studentMapper.findById(student.getId()); + Student saved = studentMapper.findById(student.getId()); + // 写入缓存 + redisUtil.set(cacheKey(saved.getId()), saved, CACHE_TIMEOUT, CACHE_TIMEOUT_UNIT); + return saved; } @Override public Student update(Integer id, Student student) { - // 先确认学生存在 + // 先确认学生存在(会走缓存/数据库) Student existing = findById(id); // 只更新非空字段(部分更新) if (student.getName() != null) { @@ -72,12 +99,17 @@ public class StudentServiceImpl implements StudentService { existing.setStatus(student.getStatus()); } studentMapper.update(existing); - return studentMapper.findById(id); + Student updated = studentMapper.findById(id); + // 刷新缓存 + redisUtil.set(cacheKey(id), updated, CACHE_TIMEOUT, CACHE_TIMEOUT_UNIT); + return updated; } @Override public void delete(Integer id) { findById(id); // 确保存在,不存在会抛 BusinessException studentMapper.deleteById(id); + // 删除缓存 + redisUtil.delete(cacheKey(id)); } } -- Gitee