SpringBoot整合redis中的JSON序列化文件夾操作小結(jié)
前言
最近在開發(fā)項目,用到了redis作為緩存,來提高系統(tǒng)訪問速度和緩解系統(tǒng)壓力,提高用戶響應(yīng)和訪問速度,這里遇到幾個問題做一下總結(jié)和整理
快速配置
SpringBoot整合redis有專門的場景啟動器整合起來還是非常方便的
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-redis</artifactId> </dependency>
如果使用redis連接池引入
<!-- redis連接池 --> <dependency> <groupId>org.apache.commons</groupId> <artifactId>commons-pool2</artifactId> </dependency>
集成配置文件
#------------------redis緩存配置------------ # Redis數(shù)據(jù)庫索引(默認(rèn)為 0) spring.redis.database=1 # Redis服務(wù)器地址 spring.redis.host= 127.0.0.1 # Redis服務(wù)器連接端口 spring.redis.port=6379 # Redis 密碼 spring.redis.password # 連接超時時間(毫秒) spring.redis.timeout= 5000 # redis連接池 # 連接池中的最小空閑連接 spring.redis.lettuce.pool.min-idle=10 # 連接池中的最大空閑連接 spring.redis.lettuce.pool.max-idle= 500 # 連接池最大連接數(shù)(使用負(fù)值表示沒有限制) spring.redis.lettuce.pool.max-active=2000 # 連接池最大阻塞等待時間(使用負(fù)值表示沒有限制) spring.redis.lettuce.pool.max-wait=10000
JSON序列化
由于緩存數(shù)據(jù)默認(rèn)使用的是jdk自帶的序列化 二進(jìn)制
需要序列化的實體類繼承Serializable接口。而且序列化后的內(nèi)容在redis中看起來也不是很方便。
\xAC\xED\x00\x05sr\x00Lorg.springframework.security.oauth2.common.DefaultExpiringOAuth2RefreshToken/\xDFGc\x9D\xD0\xC9\xB7\x02\x00\x01L\x00\x0Aexpirationt\x00\x10Ljava/util/Date;xr\x00Dorg.springframework.security.oauth2.common.DefaultOAuth2RefreshTokens\xE1\x0E\x0AcT\xD4^\x02\x00\x01L\x00\x05valuet\x00\x12Ljava/lang/String;xpt\x00$805a75f7-2ee2-4a27-a598-591bfa1cf17dsr\x00\x0Ejava.util.Datehj\x81\x01KYt\x19\x03\x00\x00xpw\x08\x00\x00\x01}y\x81\xDB\x9Ax
于是萌生了需要將數(shù)據(jù)序列化成json的想法。
jackson序列化
在使用spring-data-redis,默認(rèn)情況下是使用org.springframework.data.redis.serializer.JdkSerializationRedisSerializer這個類來做序列化,Jackson redis序列化是spring中自帶的.我們使用jackson方式
@Bean @ConditionalOnClass(RedisOperations.class) public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory factory) { RedisTemplate<String, Object> template = new RedisTemplate<>(); template.setConnectionFactory(factory); Jackson2JsonRedisSerializer<Object> jackson2JsonRedisSerializer = new Jackson2JsonRedisSerializer<>(Object.class); //序列化包括類型描述 否則反向序列化實體會報錯,一律都為JsonObject ObjectMapper mapper = new ObjectMapper(); mapper.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY); mapper.activateDefaultTyping(mapper.getPolymorphicTypeValidator(), ObjectMapper.DefaultTyping.NON_FINAL); jackson2JsonRedisSerializer.setObjectMapper(mapper); StringRedisSerializer stringRedisSerializer = new StringRedisSerializer(); // key采用 String的序列化方式 template.setKeySerializer(stringRedisSerializer); // hash的 key也采用 String的序列化方式 template.setHashKeySerializer(stringRedisSerializer); // value序列化方式采用 jackson template.setValueSerializer(jackson2JsonRedisSerializer); // hash的 value序列化方式采用 jackson template.setHashValueSerializer(jackson2JsonRedisSerializer); template.afterPropertiesSet(); return template; }
序列化后存儲在redis后內(nèi)容
[ "com.qhong.test.dependBean.Person", { "age": 20, "name": "name0", "iss": true } ]
[ "java.util.ArrayList", [ [ "com.qhong.test.dependBean.Person", { "age": 20, "name": "name0", "iss": true } ], [ "com.qhong.test.dependBean.Person", { "age": 21, "name": "name1", "iss": true } ], [ "com.qhong.test.dependBean.Person", { "age": 22, "name": "name2", "iss": true } ] ] ]
上面的不是嚴(yán)格符合json格式規(guī)范,雖然比默認(rèn)二進(jìn)制好
注意這里序列化json代類型 "com.qhong.test.dependBean.Person"
如果沒有這個反序列化會報類型轉(zhuǎn)換異常錯誤
也就是代碼中這一段必須設(shè)置,我之前就是沒有設(shè)置,反序列化都是JsonObject必須自己轉(zhuǎn)換類型,否則會報錯
//序列化包括類型描述 否則反向序列化實體會報錯,一律都為JsonObject ObjectMapper mapper = new ObjectMapper(); mapper.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY); mapper.activateDefaultTyping(mapper.getPolymorphicTypeValidator(), ObjectMapper.DefaultTyping.NON_FINAL); jackson2JsonRedisSerializer.setObjectMapper(mapper);
Fastjson序列化
需要倒入Fastjson到依賴
<!-- JSON工具 --> <dependency> <groupId>com.alibaba</groupId> <artifactId>fastjson</artifactId> <version>1.2.76</version> </dependency>
實現(xiàn)RedisSerializer接口
import com.alibaba.fastjson.JSON; import com.alibaba.fastjson.parser.ParserConfig; import com.alibaba.fastjson.serializer.SerializerFeature; import org.springframework.data.redis.serializer.RedisSerializer; import org.springframework.data.redis.serializer.SerializationException; import java.nio.charset.Charset; import java.nio.charset.StandardCharsets; public class FastJson2JsonRedisSerializer<T> implements RedisSerializer<T> { public static final Charset DEFAULT_CHARSET = StandardCharsets.UTF_8; static { ParserConfig.getGlobalInstance().setAutoTypeSupport(true); } private final Class<T> clazz; public FastJson2JsonRedisSerializer(Class<T> clazz) { super(); this.clazz = clazz; /** * 序列化 */ @Override public byte[] serialize(T t) throws SerializationException { if (null == t) { return new byte[0]; } return JSON.toJSONString(t, SerializerFeature.WriteClassName).getBytes(DEFAULT_CHARSET); * 反序列化 public T deserialize(byte[] bytes) throws SerializationException { if (null == bytes || bytes.length <= 0) { return null; String str = new String(bytes, DEFAULT_CHARSET); return (T) JSON.parseObject(str, clazz); }
配置redisTemplate
import org.springframework.boot.autoconfigure.AutoConfigureAfter; import org.springframework.boot.autoconfigure.data.redis.RedisAutoConfiguration; 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.StringRedisSerializer; @Configuration @AutoConfigureAfter(RedisAutoConfiguration.class) public class RedisCacheAutoConfiguration { @Bean public RedisTemplate<Object, Object> redisTemplate(RedisConnectionFactory factory) { RedisTemplate<Object, Object> template = new RedisTemplate<>(); template.setConnectionFactory(factory); FastJson2JsonRedisSerializer<Object> fastJsonRedisSerializer = new FastJson2JsonRedisSerializer<>(Object.class); StringRedisSerializer stringRedisSerializer = new StringRedisSerializer(); // key采用String的序列化方式 template.setKeySerializer(stringRedisSerializer); // hash的key也采用String的序列化方式 template.setHashKeySerializer(stringRedisSerializer); // value序列化方式采用fastJson template.setValueSerializer(fastJsonRedisSerializer); // hash的value序列化方式采用fastJson template.setHashValueSerializer(fastJsonRedisSerializer); template.afterPropertiesSet(); return template; } }
注意這是一種方式自己實現(xiàn)RedisSerializer 序列化接口
但是FastJson 1.2.36版本以后不需要自己實現(xiàn)RedisSerializer
為我們提供序列化支持在com.alibaba.fastjson.support.spring中 有GenericFastJsonRedisSerializer
和FastJsonRedisSerializer
兩個實現(xiàn)類 ,
區(qū)別在于GenericFastJsonRedisSerializer
可以自動轉(zhuǎn)換對象類型,FastJsonRedisSerializer
需要自定義轉(zhuǎn)換需要的類型。
通常使用 GenericFastJsonRedisSerializer 即可滿足大部分場景,如果你想定義特定類型專用的 RedisTemplate 可以使用 FastJsonRedisSerializer 來代替 GenericFastJsonRedisSerializer”
FastJson github有對應(yīng)問題描述lssues 我已入坑 ,剛開始一直使用FastJsonRedisSerializer****無法自動反向序列化
序列化后存儲在redis后內(nèi)容
{ "@type": "com.qhong.test.dependBean.Person", "age": 20, "iss": true, "name": "name0" }
[ { "@type": "com.qhong.test.dependBean.Person", "age": 20, "iss": true, "name": "name0" }, { "@type": "com.qhong.test.dependBean.Person", "age": 21, "iss": true, "name": "name1" }, { "@type": "com.qhong.test.dependBean.Person", "age": 22, "iss": true, "name": "name2" } ]
正常情況是格式是正確的,但是如果你存儲內(nèi)容出現(xiàn)set或者doubble類型,會帶上Set,D類型描述如下
會出現(xiàn)問題無法解析,但是在程序里是可以反向序列化的
分析參考對比
jdkSerializationRedisSerializer:
使用JDK提供的序列化功能。 優(yōu)點(diǎn)是反序列化時不需要提供類型信息(class),但缺點(diǎn)是需要實現(xiàn)Serializable接口,還有序列化后的結(jié)果非常龐大,是JSON格式的5倍左右,這樣就會消耗redis服務(wù)器的大量內(nèi)存。Jackson2JsonRedisSerializer:
使用Jackson庫將對象序列化為JSON字符串。優(yōu)點(diǎn)是速度快,序列化后的字符串短小精悍,不需要實現(xiàn)Serializable接口。但缺點(diǎn)也非常致命,那就是此類的構(gòu)造函數(shù)中有一個類型參數(shù),必須提供要序列化對象的類型信息(.class對象)。 通過查看源代碼,發(fā)現(xiàn)其只在反序列化過程中用到了類型信息。FastJsonRedisSerializer
性能最優(yōu)號稱最快的json解析庫,但是反序列化后類字段順序和原來實體類不一致發(fā)生改變,在某些set,double字段情況下json格式不正確,但是在程序可以解析
更多問題參考
RedisTemplate序列化方式解讀
redis數(shù)據(jù)庫操作
在整合了spring-boot-starter-data-redis
后會自動幫我們注入redisTemplate
對象,專門用來操作reids數(shù)據(jù)庫的
在reids中如果想用文件夾方式存儲key的話類似這樣
我們只需要在存儲使用使用::表示文件夾就可以了
redisTemplate.opsForValue().set("userLoginCache::Kenx_6003783582be4c368af14daf3495559c", "user");
如果需要模糊查詢key話使用*
來表示 如
獲取所有key
public static Set<String> getAllKey(String keys) { Set<String> key = redisTemplate.keys(keys + "*"); return key; }
模糊批量刪除
/** * 刪除緩存 * * @param key 可以傳一個值 或多個 */ public static void del(String... key) { if (key != null && key.length > 0) { if (key.length == 1) { redisTemplate.delete(key[0]); } else { redisTemplate.delete(Arrays.asList(key)); } } }
public static void delByPrefix(String key) { if (key != null) { Set<String> keys = redisTemplate.keys(key + "*"); redisTemplate.delete(keys); } } public static void delBySuffix(String key) { if (key != null) { Set<String> keys = redisTemplate.keys("*" + key); redisTemplate.delete(keys); } } public static void clean(){ Set<String> keys = redisTemplate.keys("*"); redisTemplate.delete(keys); }
因為使用很頻繁所以我寫成工具庫RedisUtil
通過靜態(tài)方法方式去調(diào)用就可以了
基本上包含工作中用到的所有方法, 這里附上源碼
package cn.soboys.kmall.cache.utils; import cn.hutool.extra.spring.SpringUtil; import org.springframework.data.redis.core.RedisTemplate; import java.util.Arrays; import java.util.List; import java.util.Map; import java.util.Set; import java.util.concurrent.TimeUnit; /** * 定義常用的 Redis操作 * * @author kenx */ public class RedisUtil { private static final RedisTemplate<String, Object> redisTemplate = SpringUtil.getBean("redisTemplate", RedisTemplate.class); /** * 指定緩存失效時間 * * @param key 鍵 * @param time 時間(秒) * @return Boolean */ public static Boolean expire(String key, Long time) { try { if (time > 0) { redisTemplate.expire(key, time, TimeUnit.SECONDS); } return true; } catch (Exception e) { e.printStackTrace(); return false; } } * 根據(jù)key獲取過期時間 * @param key 鍵 不能為 null * @return 時間(秒) 返回 0代表為永久有效 public static Long getExpire(String key) { return redisTemplate.getExpire(key, TimeUnit.SECONDS); * 判斷 key是否存在 * @param key 鍵 * @return true 存在 false不存在 public static Boolean hasKey(String key) { return redisTemplate.hasKey(key); * 刪除緩存 * @param key 可以傳一個值 或多個 public static void del(String... key) { if (key != null && key.length > 0) { if (key.length == 1) { redisTemplate.delete(key[0]); } else { redisTemplate.delete(Arrays.asList(key)); public static void delByPrefix(String key) { if (key != null) { Set<String> keys = redisTemplate.keys(key + "*"); redisTemplate.delete(keys); public static void delBySuffix(String key) { Set<String> keys = redisTemplate.keys("*" + key); public static void clean(){ Set<String> keys = redisTemplate.keys("*"); redisTemplate.delete(keys); * 普通緩存獲取 * @return 值 public static Object get(String key) { return key == null ? null : redisTemplate.opsForValue().get(key); * 普通緩存放入 * @param key 鍵 * @param value 值 * @return true成功 false失敗 public static Boolean set(String key, Object value) { redisTemplate.opsForValue().set(key, value); * 普通緩存放入并設(shè)置時間 * @param time 時間(秒) time要大于0 如果time小于等于0 將設(shè)置無限期 * @return true成功 false 失敗 public static Boolean set(String key, Object value, Long time) { redisTemplate.opsForValue().set(key, value, time, TimeUnit.SECONDS); set(key, value); * 遞增 * @param delta 要增加幾(大于0) * @return Long public static Long incr(String key, Long delta) { if (delta < 0) { throw new RuntimeException("遞增因子必須大于0"); return redisTemplate.opsForValue().increment(key, delta); * 遞減 * @param delta 要減少幾 public static Long decr(String key, Long delta) { throw new RuntimeException("遞減因子必須大于0"); return redisTemplate.opsForValue().increment(key, -delta); * HashGet * @param key 鍵 不能為 null * @param item 項 不能為 null public static Object hget(String key, String item) { return redisTemplate.opsForHash().get(key, item); * 獲取 hashKey對應(yīng)的所有鍵值 * @return 對應(yīng)的多個鍵值 public static Map<Object, Object> hmget(String key) { return redisTemplate.opsForHash().entries(key); * 獲取 hashKey對應(yīng)的所有鍵 * @return 對應(yīng)的多個鍵 public static Set<String> hmgetKey(String key) { Map map = redisTemplate.opsForHash().entries(key); return map.keySet(); * HashSet * @param map 對應(yīng)多個鍵值 * @return true 成功 false 失敗 public static Boolean hmset(String key, Map<String, Object> map) { redisTemplate.opsForHash().putAll(key, map); * HashSet 并設(shè)置時間 * @param map 對應(yīng)多個鍵值 public static Boolean hmset(String key, Map<String, Object> map, Long time) { expire(key, time); * 向一張hash表中放入數(shù)據(jù),如果不存在將創(chuàng)建 * @param item 項 * @return true 成功 false失敗 public static Boolean hset(String key, String item, Object value) { redisTemplate.opsForHash().put(key, item, value); * @param time 時間(秒) 注意:如果已存在的hash表有時間,這里將會替換原有的時間 public static Boolean hset(String key, String item, Object value, Long time) { * 刪除hash表中的值 * @param item 項 可以使多個不能為 null public static void hdel(String key, Object... item) { redisTemplate.opsForHash().delete(key, item); * 判斷hash表中是否有該項的值 public static Boolean hHasKey(String key, String item) { return redisTemplate.opsForHash().hasKey(key, item); * hash遞增 如果不存在,就會創(chuàng)建一個 并把新增后的值返回 * @param item 項 * @param by 要增加幾(大于0) * @return Double public static Double hincr(String key, String item, Double by) { return redisTemplate.opsForHash().increment(key, item, by); * hash遞減 * @param by 要減少記(小于0) public static Double hdecr(String key, String item, Double by) { return redisTemplate.opsForHash().increment(key, item, -by); * 根據(jù) key獲取 Set中的所有值 * @return Set public static Set<Object> sGet(String key) { return redisTemplate.opsForSet().members(key); return null; * 根據(jù)value從一個set中查詢,是否存在 public static Boolean sHasKey(String key, Object value) { return redisTemplate.opsForSet().isMember(key, value); * 將數(shù)據(jù)放入set緩存 * @param key 鍵 * @param values 值 可以是多個 * @return 成功個數(shù) public static Long sSet(String key, Object... values) { return redisTemplate.opsForSet().add(key, values); return 0L; * 將set數(shù)據(jù)放入緩存 * @param time 時間(秒) public static Long sSetAndTime(String key, Long time, Object... values) { Long count = redisTemplate.opsForSet().add(key, values); return count; * 獲取set緩存的長度 public static Long sGetSetSize(String key) { return redisTemplate.opsForSet().size(key); * 移除值為value的 * @return 移除的個數(shù) public static Long setRemove(String key, Object... values) { return redisTemplate.opsForSet().remove(key, values); * 獲取list緩存的內(nèi)容 * @param start 開始 * @param end 結(jié)束 0 到 -1代表所有值 * @return List public static List<Object> lGet(String key, Long start, Long end) { return redisTemplate.opsForList().range(key, start, end); * 獲取list緩存的長度 public static Long lGetListSize(String key) { return redisTemplate.opsForList().size(key); * 通過索引 獲取list中的值 * @param index 索引 index>=0時, 0 表頭,1 第二個元素,依次類推; * index<0時,-1,表尾,-2倒數(shù)第二個元素,依次類推 * @return Object public static Object lGetIndex(String key, Long index) { return redisTemplate.opsForList().index(key, index); * 將list放入緩存 public static Boolean lSet(String key, Object value) { redisTemplate.opsForList().rightPush(key, value); * @param time 時間(秒) public static Boolean lSet(String key, Object value, Long time) { public static Boolean lSet(String key, List<Object> value) { redisTemplate.opsForList().rightPushAll(key, value); public static Boolean lSet(String key, List<Object> value, Long time) { * 根據(jù)索引修改list中的某條數(shù)據(jù) * @param index 索引 public static Boolean lUpdateIndex(String key, Long index, Object value) { redisTemplate.opsForList().set(key, index, value); * 移除N個值為value * @param count 移除多少個 public static Long lRemove(String key, Long count, Object value) { return redisTemplate.opsForList().remove(key, count, value); public static Set<String> getAllKey(String keys) { Set<String> key = redisTemplate.keys(keys + "*"); return key; }
到此這篇關(guān)于SpringBoot整合edis之JSON序列化文件夾操作的文章就介紹到這了,更多相關(guān)SpringBoot整合edis JSON序列化內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
springcloud項目快速開始起始模板的實現(xiàn)
本文主要介紹了springcloud項目快速開始起始模板思路的實現(xiàn),文中通過示例代碼介紹的非常詳細(xì),具有一定的參考價值,感興趣的小伙伴們可以參考一下2021-12-12java 中JDBC連接數(shù)據(jù)庫代碼和步驟詳解及實例代碼
這篇文章主要介紹了java 中JDBC連接數(shù)據(jù)庫代碼和步驟詳解及實例代碼的相關(guān)資料,需要的朋友可以參考下2017-02-02eclipse中自動生成構(gòu)造函數(shù)的兩種方法
下面小編就為大家?guī)硪黄猠clipse中自動生成構(gòu)造函數(shù)的兩種方法。小編覺得挺不錯的,現(xiàn)在就分享給大家,也給大家做個參考。一起跟隨小編過來看看吧2017-10-10Spring注解中@Autowired和@Bean的區(qū)別詳解
這篇文章主要詳細(xì)介紹了Spring注解中@Autowired和@Bean二者有什么區(qū)別,文中通過兩個注解的使用場景介紹了二者的區(qū)別,感興趣的同學(xué)可以參考閱讀2023-06-06