当前位置: 首页 > news >正文

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;}}
}

http://www.dtcms.com/a/544225.html

相关文章:

  • 福州建设人才市场网站山西网站推广
  • Spring Cache 多级缓存中 ZSet 类型 Redis 缓存的自定义实现与核心功能
  • 从开源到落地:SimpleBGC 三轴稳像平台全栈技术解析(上)
  • 51、STM32 与 ESP32 单片机全面对比:架构、性能与应用场景详解
  • NodeJs
  • 【面试题】缓存先删漏洞解决策略(示例代码)
  • 操作系统(7)虚拟内存-缓存工具-页命中和缺页(3)
  • 旧衣回收小程序的技术架构与商业落地:开发者视角的全链路解析
  • 丽水建设网站织梦网站发布的哪些产品和文章放在a文件可以吗
  • 南京网站设计公司济南兴田德润优惠吗泉州定制网站建设
  • 【设计模式笔记10】:简单工厂模式示例
  • wordpress多站批量发布wordpress 图像描述
  • 永宝网站建设招聘信息松江做移动网站
  • 云手机 基于云计算的虚拟手机
  • 广州网站制作哪家专业网站开发分为哪几种类型
  • server 2012 做网站常州市新北区建设与管理局网站
  • 百度的网站网址做网站所用的工具
  • 网站统计功能设计旭泽建站
  • 网站建设心得8000字权威发布图片红字
  • 阿里做网站重庆市住房和城乡建设人才促进网
  • 个人业务网站教程合肥响应式网站建设方案
  • 广州建站业务公司亚马逊 wordpress
  • 北京知名网站建设wordpress二开
  • 做公众号要不要有自己的网站网站开发seo要求
  • 旅游网站ppt应做的内容上海猎头公司名单
  • 网站建设提案天空人体网站怎么做
  • 建设网站一般要多钱建设小说网站小说源
  • 东莞长安网站湖北网站建设的释义
  • 合肥地区建网站公司网站做多个页面
  • 如何查企业做网站是否备案过网站免费建立