Spring Cache 多级缓存中 hash 类型 Redis 缓存的自定义实现与核心功能
需求背景:
spring cache 使用多级缓存,缓存实现方式用redis时,默认存储的结构是string,结果,但项目中需要使用其他结果,于是想到自己定义一个实现类,然后根据类型进行切换不同的实现方式,于是乎有了下面的hash自定义实现代码
import com.alibaba.fastjson.JSON;
import org.springframework.data.redis.cache.RedisCache;
import org.springframework.data.redis.cache.RedisCacheConfiguration;
import org.springframework.data.redis.cache.RedisCacheWriter;
import org.springframework.data.redis.connection.RedisConnection;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.connection.RedisConnectionUtils;
import org.springframework.cache.Cache;
import org.springframework.data.redis.serializer.RedisSerializer;
import org.springframework.data.redis.serializer.SerializationException;
import java.nio.charset.StandardCharsets;
import java.time.Duration;
import java.util.*;
import java.util.stream.Collectors;/*** Hash 结构 Redis 缓存实现(核心版)* 功能:键值对存储、支持 List/Map 分批入库、自动去重、批量读取*/
public class HashRedisCache extends RedisCache {// 键序列化器(Hash 的 field)private static final RedisSerializer<String> KEY_SERIALIZER = new StringRedisSerializer();// 值序列化器(Hash 的 value)private static final RedisSerializer<Object> VALUE_SERIALIZER = new FastJsonRedisSerializer<>();private final Duration ttl;private final RedisConnectionFactory connectionFactory;private final boolean allowOverwrite; // 是否允许覆盖已存在的键public HashRedisCache(String name,RedisCacheWriter cacheWriter,RedisCacheConfiguration config,RedisConnectionFactory connectionFactory,boolean allowOverwrite) {super(name, cacheWriter, config);this.ttl = config.getTtl();this.connectionFactory = connectionFactory;this.allowOverwrite = allowOverwrite;}/*** FastJSON 序列化器(纯 JSON 无类型信息)*/private static class FastJsonRedisSerializer<T> implements RedisSerializer<T> {@Overridepublic byte[] serialize(T t) throws SerializationException {if (t == null) {return new byte[0];}return JSON.toJSONString(t).getBytes(StandardCharsets.UTF_8);}@Overridepublic T deserialize(byte[] bytes) throws SerializationException {if (bytes == null || bytes.length == 0) {return null;}return (T) JSON.parse(bytes);}}/*** 核心:存储数据(支持单键值对、Map 批量、List 批量)* - 单条:key=field,value=数据* - 批量 Map:value 为 Map<String, Object>,key 忽略* - 批量 List:需为 List<Map<String, Object>>,每个 Map 含一个键值对*/@Overridepublic void put(Object key, Object value) {if (key == null && value == null) {return;}RedisConnection connection = getRedisConnection();try {byte[] hashKey = getNameBytes(); // Hash 主键为缓存名if (hashKey == null) {return;}Map<byte[], byte[]> hashEntries = new HashMap<>();// 处理 Map 批量入库if (value instanceof Map<?, ?>) {Map<?, ?> dataMap = (Map<?, ?>) value;for (Map.Entry<?, ?> entry : dataMap.entrySet()) {String field = entry.getKey().toString();Object data = entry.getValue();addToHashEntries(field, data, hashEntries, connection, hashKey);}}// 批量存入 Hashif (!hashEntries.isEmpty()) {connection.hMSet(hashKey, hashEntries);}// 设置过期时间if (!ttl.isNegative()) {connection.expire(hashKey, ttl.getSeconds());}} finally {releaseRedisConnection(connection);}}/*** 辅助:添加键值对到 Hash(含去重逻辑)*/private void addToHashEntries(String field, Object data, Map<byte[], byte[]> hashEntries,RedisConnection connection, byte[] hashKey) {byte[] fieldBytes = KEY_SERIALIZER.serialize(field);byte[] valueBytes = VALUE_SERIALIZER.serialize(data);if (fieldBytes == null || valueBytes == null) {return;}// 去重逻辑:若不允许覆盖,检查字段是否已存在if (!allowOverwrite) {Boolean exists = connection.hExists(hashKey, fieldBytes);if (exists != null && exists) {return; // 已存在则跳过}}hashEntries.put(fieldBytes, valueBytes);}/*** 核心:获取单个字段的值*/@Overridepublic Cache.ValueWrapper get(Object key) {if (key == null) {return null;}RedisConnection connection = getRedisConnection();try {byte[] hashKey = getNameBytes();byte[] fieldBytes = KEY_SERIALIZER.serialize(key.toString());if (hashKey == null || fieldBytes == null) {return null;}byte[] valueBytes = connection.hGet(hashKey, fieldBytes);return valueBytes != null ? new SimpleValueWrapper(VALUE_SERIALIZER.deserialize(valueBytes)) : null;} finally {releaseRedisConnection(connection);}}/*** 核心:获取所有键值对*/public Map<String, Object> getAll() {RedisConnection connection = getRedisConnection();try {byte[] hashKey = getNameBytes();if (hashKey == null) {return Collections.emptyMap();}Map<byte[], byte[]> allEntries = connection.hGetAll(hashKey);if (allEntries.isEmpty()) {return Collections.emptyMap();}// 反序列化为 Map<String, Object>return allEntries.entrySet().stream().collect(Collectors.toMap(entry -> KEY_SERIALIZER.deserialize(entry.getKey()),entry -> VALUE_SERIALIZER.deserialize(entry.getValue())));} finally {releaseRedisConnection(connection);}}/*** 核心:获取所有字段(键)*/public Set<String> getFields() {RedisConnection connection = getRedisConnection();try {byte[] hashKey = getNameBytes();if (hashKey == null) {return Collections.emptySet();}Set<byte[]> fieldBytesSet = connection.hKeys(hashKey);return fieldBytesSet.stream().map(KEY_SERIALIZER::deserialize).filter(Objects::nonNull).collect(Collectors.toSet());} finally {releaseRedisConnection(connection);}}/*** 核心:删除单个字段*/@Overridepublic void evict(Object key) {if (key == null) {return;}RedisConnection connection = getRedisConnection();try {byte[] hashKey = getNameBytes();byte[] fieldBytes = KEY_SERIALIZER.serialize(key.toString());if (hashKey != null && fieldBytes != null) {connection.hDel(hashKey, fieldBytes);}} finally {releaseRedisConnection(connection);}}/*** 核心:清空整个 Hash*/@Overridepublic void clear() {RedisConnection connection = getRedisConnection();try {byte[] hashKey = getNameBytes();if (hashKey != null) {connection.del(hashKey);}} finally {releaseRedisConnection(connection);}}/*** 核心:获取字段总数*/public Long getFieldCount() {RedisConnection connection = getRedisConnection();try {byte[] hashKey = getNameBytes();return hashKey != null ? connection.hLen(hashKey) : 0L;} finally {releaseRedisConnection(connection);}}// -------------------------- 工具方法 --------------------------private byte[] getNameBytes() {return getName().getBytes(StandardCharsets.UTF_8);}private RedisConnection getRedisConnection() {return RedisConnectionUtils.getConnection(connectionFactory);}private void releaseRedisConnection(RedisConnection connection) {RedisConnectionUtils.releaseConnection(connection, connectionFactory);}// -------------------------- 内部类 --------------------------private static class StringRedisSerializer implements RedisSerializer<String> {@Overridepublic byte[] serialize(String s) throws SerializationException {return s != null ? s.getBytes(StandardCharsets.UTF_8) : new byte[0];}@Overridepublic String deserialize(byte[] bytes) throws SerializationException {return bytes != null && bytes.length > 0 ? new String(bytes, StandardCharsets.UTF_8) : null;}}private static class SimpleValueWrapper implements Cache.ValueWrapper {private final Object value;public SimpleValueWrapper(Object value) {this.value = value;}@Overridepublic Object get() {return value;}}
}