前言
在芝法酱躺平攻略(6)中,我们已经使用了redis的相关功能,这一节我们将更细致的探索下redis在SpringBoot下的使用
一、redis的常见使用
1.1 做缓存加速
我们知道,redis是一个内存数据库,访问的速度远远高于类似mysql的硬盘数据库。可以把一些经常访问的数据放在redis中,加速系统运行速度。
1.2 做分布式的session
在正式的项目中,系统往往是多个微服务组成的,微服务前面有一个网关,客户端的访问先经过网关再分发到各微服务。这样一来,SpringBoot原有的session就不好用了。不过我们可以借助redis实现这个功能
1.3 做简单的消息队列
redis还可以做简单的消息队列。在一些小型项目中,项目组往往不想部署专业的消息队列,用redis实现一个简单的也能满足使用。
1.4 分布式事务
如果是微服务架构,一个事务需要多个微服务共同完成,则需要使用redis做分布式事务的中间件
二、redis的配置与工具类
2.1 pom引用
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<parent>
<artifactId>framework-web</artifactId>
<groupId>indi.zhifa.recipe</groupId>
<version>1.0-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>framework-redis</artifactId>
<packaging>jar</packaging>
<dependencies>
<dependency>
<groupId>indi.zhifa.recipe</groupId>
<artifactId>framework-common</artifactId>
</dependency>
<!--******************redis*****************************-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
<!-- ******************lombok****************************-->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<scope>provided</scope>
</dependency>
</dependencies>
</project>
2.2 配置类
@Data
@Configuration
@ConfigurationProperties(prefix = "redis")
public class RedisProperty {
Long cacheExpire;
}
@RequiredArgsConstructor
@Configuration
@EnableCaching
public class RedisConfig {
private final ApplicationContext mApplicationContext;
private final RedisProperty mRedisProperty;
private final RedisConnectionFactory mRedisConnectionFactory;
FastJsonConfig getRedisFastJson(){
FastJsonConfig config = new FastJsonConfig();
config.setWriterFeatures(
// 保留 Map 空的字段
JSONWriter.Feature.WriteMapNullValue,
JSONWriter.Feature.WriteNullListAsEmpty,
// 写入类名
JSONWriter.Feature.WriteClassName,
// 将 Boolean 类型的 null 转成 false
JSONWriter.Feature.WriteNullBooleanAsFalse,
JSONWriter.Feature.WriteEnumsUsingName);
config.setReaderFeatures(
JSONReader.Feature.SupportClassForName,
// 支持autoType
JSONReader.Feature.SupportAutoType);
return config;
}
@Bean
FastJsonRedisSerializer getFastJsonRedisSerializer() {
FastJsonRedisSerializer fastJsonRedisSerializer = new FastJsonRedisSerializer(Object.class);
fastJsonRedisSerializer.setFastJsonConfig(getRedisFastJson());
return fastJsonRedisSerializer;
}
@Bean
public CacheManager cacheManager(RedisConnectionFactory connectionFactory,FastJsonRedisSerializer pFastJsonRedisSerializer) {
RedisCacheConfiguration config = RedisCacheConfiguration.defaultCacheConfig().entryTtl(Duration.ofSeconds(mRedisProperty.getCacheExpire()))
.serializeKeysWith(RedisSerializationContext.SerializationPair.fromSerializer(new StringRedisSerializer()))
.serializeValuesWith(RedisSerializationContext.SerializationPair.fromSerializer(pFastJsonRedisSerializer));
return RedisCacheManager.builder(RedisCacheWriter.nonLockingRedisCacheWriter(mRedisConnectionFactory))
.cacheDefaults(config)
.build();
}
@Bean
public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory factory,FastJsonRedisSerializer pFastJsonRedisSerializer) {
RedisTemplate<String, Object> redisTemplate = new RedisTemplate<String, Object>();
redisTemplate.setConnectionFactory(factory);
redisTemplate.setEnableTransactionSupport(true);
//序列化设置 ,这样计算是正常显示的数据,也能正常存储和获取
redisTemplate.setKeySerializer(new StringRedisSerializer());
redisTemplate.setValueSerializer(pFastJsonRedisSerializer);
redisTemplate.setHashKeySerializer(new StringRedisSerializer());
redisTemplate.setHashValueSerializer(pFastJsonRedisSerializer);
return redisTemplate;
}
@Bean
public StringRedisTemplate stringRedisTemplate(
RedisConnectionFactory factory) {
StringRedisTemplate stringRedisTemplate = new StringRedisTemplate();
stringRedisTemplate.setConnectionFactory(factory);
return stringRedisTemplate;
}
}
2.3 工具类
@Component
public class RedisUtil {
private final RedisTemplate<String, Object> _redisTemplate;
private final HashOperations<String, String, Object> _hashOperations;
private final HashMapper<Object, String, Object> _hashMapper;
@Autowired
public RedisUtil(RedisTemplate<String, Object> v_redisTemplate) {
_redisTemplate = v_redisTemplate;
_hashOperations = _redisTemplate.opsForHash();
_hashMapper = new Jackson2HashMapper(true);
}
// =============================common============================
/**
* 指定缓存失效时间
*
* @param key 键
* @param time 时间(秒)
* @return
*/
public 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;
}
}
/**
* 根据key 获取过期时间
*
* @param key 键 不能为null
* @return 时间(秒) 返回0代表为永久有效
*/
public long getExpire(String key) {
return _redisTemplate.getExpire(key, TimeUnit.SECONDS);
}
/**
* 判断key是否存在
*
* @param key 键
* @return true 存在 false不存在
*/
public boolean hasKey(String key) {
if(!StringUtils.hasLength(key)){
return false;
}
return _redisTemplate.hasKey( key);
}
/**
* 删除缓存
*
* @param key 可以传一个值 或多个
*/
@SuppressWarnings("unchecked")
public void delete(String... key) {
if (key != null && key.length > 0) {
if (key.length == 1) {
_redisTemplate.delete(key[0]);
} else {
_redisTemplate.delete((Collection<String>) CollectionUtils.arrayToList(key));
}
}
}
// ============================String=============================
/**
* 普通缓存获取
*
* @param key 键
* @return 值
*/
public Object get(String key) {
return key == null ? null : _redisTemplate.opsForValue().get(key);
}
/**
* 获取hashKey对应的所有键值
*
* @param key 键
* @return 对应的多个键值
*/
public <T> T get(String key, Class<T> pValClass) {
Object rtn = _redisTemplate.opsForValue().get(key);
if(null == rtn){
return null;
}
if(rtn.getClass().equals(pValClass)){
return (T)rtn;
}
else if(rtn instanceof JSONObject){
T value = JSON.to(pValClass,rtn);
return value;
}else if(rtn instanceof String){
if(pValClass == String.class){
return (T)rtn.toString();
}
T value = JSON.parseObject((String)rtn,pValClass);
return value;
}
else {
throw new ServiceException("redis 数据错误,无法完成转换");
}
}
/**
* 普通缓存放入
*
* @param key 键
* @param value 值
* @return true成功 false失败
*/
public boolean set(String key, Object value) {
try {
_redisTemplate.opsForValue().set(key, value);
return true;
} catch (Exception e) {
e.printStackTrace();
return false;
}
}
/**
* Description: 如果key存在, 则返回false, 如果不存在,
* 则将key=value放入redis中, 并返回true
* @author: bixuejun(bxjgood@163.com)
* @date: 2021/11/26 14:36
* @param
* @return
*/
public boolean setIfAbsent(String key, String value) {
return _redisTemplate.opsForValue().setIfAbsent( key, value);
}
/**
* 普通缓存放入并设置时间
*
* @param key 键
* @param value 值
* @param time 时间(秒) time要大于0 如果time小于等于0 将设置无限期
* @return true成功 false 失败
*/
public boolean set(String key, Object value, long time) {
try {
if (time > 0) {
_redisTemplate.opsForValue().set(key, value, time, TimeUnit.SECONDS);
} else {
set(key, value);
}
return true;
} catch (Exception e) {
e.printStackTrace();
return false;
}
}
/**
* 递增
*
* @param key 键
* @param delta 要增加几(大于0)
* @return
*/
public long increment(String key, long delta) {
if (delta < 0) {
throw new RuntimeException("递增因子必须大于0");
}
return _redisTemplate.opsForValue().increment(key, delta);
}
/**
* 递减
*
* @param key 键
* @param delta 要减少几(小于0)
* @return
*/
public long decrement(String key, long delta) {
if (delta < 0) {
throw new RuntimeException("递减因子必须大于0");
}
return _redisTemplate.opsForValue().decrement(key, delta);
}
// ================================Map=================================
/**
* HashGet
*
* @param key 键 不能为null
* @param item 项 不能为null
* @return 值
*/
public Object hget(String key, String item) {
return _redisTemplate.opsForHash().get(key, item);
}
/**
* 获取hashKey对应的所有键值
*
* @param key 键
* @return 对应的多个键值
*/
public Map<Object, Object> hmget(String key) {
return _redisTemplate.opsForHash().entries(key);
}
/**
* 获取hashKey对应的所有键值
*
* @param key 键
* @return 对应的多个键值
*/
public <T> Map<String, T> hmget(String key, Class<T> pValClass) {
Map<Object, Object> rtn = _redisTemplate.opsForHash().entries(key);
if(null == rtn){
return null;
}
Map<String, T> result = new HashMap<>();
for(Map.Entry<Object, Object> data : rtn.entrySet()){
String subKey = data.getKey().toString();
T value = JSON.to(pValClass,data.getValue());
result.put(subKey,value);
}
return result;
}
/**
* 使用hget获取Redis对象
*
* @param key 键
* @return 返回对象
*/
public <T> T hmgetObj(String key) {
Map<String, Object> mpData = _hashOperations.entries(key);
if(mpData == null||mpData.size()==0){
return null;
}
return (T) _hashMapper.fromHash(mpData);
}
/**
* HashSet
* @param key 键
* @param map 对应多个键值
* @return true 成功 false 失败
*/
public boolean hmset(String key, Map<String, Object> map) {
try {
_redisTemplate.opsForHash().putAll(key, map);
return true;
} catch (Exception e) {
e.printStackTrace();
return false;
}
}
/**
* 使用hset对指定键放入对象的所有值
*
* @param key 键
* @param v_obj 对象
* @param <T>对象类型
* @return 是否成功
*/
public <T> boolean hmsetObject(String key, T v_obj) {
try {
Map<String, Object> mappedHash = _hashMapper.toHash(v_obj);
_hashOperations.putAll(key, mappedHash);
return true;
} catch (Exception e) {
e.printStackTrace();
return false;
}
}
/**
* HashSet 并设置时间
*
* @param key 键
* @param map 对应多个键值
* @param time 时间(秒)
* @return true成功 false失败
*/
public boolean hmset(String key, Map<String, Object> map, long time) {
try {
_redisTemplate.opsForHash().putAll(key, map);
if (time > 0) {
expire(key, time);
}
return true;
} catch (Exception e) {
e.printStackTrace();
return false;
}
}
/**
* 使用hset对指定键放入对象的所有值,并设置过期时间
*
* @param key 键
* @param v_obj 对象
* @param <T>对象类型
* @return 是否成功
*/
public <T> boolean hmsetObject(String key, T v_obj, long time) {
try {
Map<String, Object> mappedHash = _hashMapper.toHash(v_obj);
_hashOperations.putAll(key, mappedHash);
if (time > 0) {
expire(key, time);
}
return true;
} catch (Exception e) {
e.printStackTrace();
return false;
}
}
/**
* 向一张hash表中放入数据,如果不存在将创建
*
* @param key 键
* @param item 项
* @param value 值
* @return true 成功 false失败
*/
public boolean hset(String key, String item, Object value) {
try {
_redisTemplate.opsForHash().put(key, item, value);
return true;
} catch (Exception e) {
e.printStackTrace();
return false;
}
}
/**
* 向一张hash表中放入数据,如果不存在将创建
*
* @param key 键
* @param item 项
* @param value 值
* @param time 时间(秒) 注意:如果已存在的hash表有时间,这里将会替换原有的时间
* @return true 成功 false失败
*/
public boolean hset(String key, String item, Object value, long time) {
try {
_redisTemplate.opsForHash().put(key, item, value);
if (time > 0) {
expire(key, time);
}
return true;
} catch (Exception e) {
e.printStackTrace();
return false;
}
}
/**
* 删除hash表中的值
*
* @param key 键 不能为null
* @param item 项 可以使多个 不能为null
*/
public void hdel(String key, Object... item) {
_redisTemplate.opsForHash().delete(key, item);
}
/**
* 判断hash表中是否有该项的值
*
* @param key 键 不能为null
* @param item 项 不能为null
* @return true 存在 false不存在
*/
public boolean hHasKey(String key, String item) {
return _redisTemplate.opsForHash().hasKey(key, item);
}
/**
* hash递增 如果不存在,就会创建一个 并把新增后的值返回
*
* @param key 键
* @param item 项
* @param by 要增加几(大于0)
* @return
*/
public double hincr(String key, String item, double by) {
return _redisTemplate.opsForHash().increment(key, item, by);
}
/**
* hash递减
*
* @param key 键
* @param item 项
* @param by 要减少记(小于0)
* @return
*/
public double hdecr(String key, String item, double by) {
return _redisTemplate.opsForHash().increment(key, item, -by);
}
// ============================set=============================
/**
* 根据key获取Set中的所有值
*
* @param key 键
* @return
*/
public Set<Object> sGet(String key) {
try {
return _redisTemplate.opsForSet().members(key);
} catch (Exception e) {
e.printStackTrace();
return null;
}
}
/**
* 根据value从一个set中查询,是否存在
*
* @param key 键
* @param value 值
* @return true 存在 false不存在
*/
public boolean sHasKey(String key, Object value) {
try {
return _redisTemplate.opsForSet().isMember(key, value);
} catch (Exception e) {
e.printStackTrace();
return false;
}
}
/**
* 将数据放入set缓存
*
* @param key 键
* @param values 值 可以是多个
* @return 成功个数
*/
public long sSet(String key, Object... values) {
try {
return _redisTemplate.opsForSet().add(key, values);
} catch (Exception e) {
e.printStackTrace();
return 0;
}
}
/**
* 将set数据放入缓存
*
* @param key 键
* @param time 时间(秒)
* @param values 值 可以是多个
* @return 成功个数
*/
public long sSetAndTime(String key, long time, Object... values) {
try {
Long count = _redisTemplate.opsForSet().add(key, values);
if (time > 0){
expire(key, time);
}
return count;
} catch (Exception e) {
e.printStackTrace();
return 0;
}
}
/**
* 获取set缓存的长度
*
* @param key 键
* @return
*/
public long sGetSetSize(String key) {
try {
return _redisTemplate.opsForSet().size(key);
} catch (Exception e) {
e.printStackTrace();
return 0;
}
}
/**
* 移除值为value的
*
* @param key 键
* @param values 值 可以是多个
* @return 移除的个数
*/
public long setRemove(String key, Object... values) {
try {
Long count = _redisTemplate.opsForSet().remove(key, values);
return count;
} catch (Exception e) {
e.printStackTrace();
return 0;
}
}
// ===============================list=================================
/**
* 获取list缓存的内容
*
* @param key 键
* @param start 开始
* @param end 结束 0 到 -1代表所有值
* @return
*/
public List<Object> lGet(String key, long start, long end) {
try {
return _redisTemplate.opsForList().range(key, start, end);
} catch (Exception e) {
e.printStackTrace();
return null;
}
}
/**
* 获取list缓存的长度
*
* @param key 键
* @return
*/
public long getListSize(String key) {
try {
return _redisTemplate.opsForList().size(key);
} catch (Exception e) {
e.printStackTrace();
return 0;
}
}
/**
* 通过索引 获取list中的值
*
* @param key 键
* @param index 索引 index>=0时, 0 表头,1 第二个元素,依次类推;index<0时,-1,表尾,-2倒数第二个元素,依次类推
* @return
*/
public Object listGet(String key, long index) {
try {
return _redisTemplate.opsForList().index(key, index);
} catch (Exception e) {
e.printStackTrace();
return null;
}
}
/**
* 根据索引修改list中的某条数据
*
* @param key 键
* @param index 索引
* @param value 值
* @return
*/
public boolean listSet(String key, long index, Object value) {
try {
_redisTemplate.opsForList().set(key, index, value);
return true;
} catch (Exception e) {
e.printStackTrace();
return false;
}
}
/**
* 把值放入队尾
*
* @param key 键
* @param value 值
* @return
*/
public boolean rPush(String key, Object value) {
try {
_redisTemplate.opsForList().rightPush(key, value);
return true;
} catch (Exception e) {
e.printStackTrace();
return false;
}
}
/**
* 把值放入队首
*
* @param key 键
* @param value 值
* @return
*/
public boolean lPush(String key, Object value) {
try {
_redisTemplate.opsForList().leftPush(key, value);
return true;
} catch (Exception e) {
e.printStackTrace();
return false;
}
}
/**
* 批量插入队尾
*
* @param key
* @param value
* @return
*/
public boolean rPushAll(String key, List<Object> value){
try {
_redisTemplate.opsForList().rightPushAll(key, value);
return true;
} catch (Exception e) {
e.printStackTrace();
return false;
}
}
/**
* 批量插入队首
*
* @param key
* @param value
* @return
*/
public boolean lPushAll(String key, List<Object> value){
try {
_redisTemplate.opsForList().leftPushAll(key, value);
return true;
} catch (Exception e) {
e.printStackTrace();
return false;
}
}
/**
* 从队首推出一个元素
*
* @param key 键
* @return 队首元素
*/
public Object lPop(String key){
try {
Object obj = _redisTemplate.opsForList().leftPop(key);
return obj;
} catch (Exception e) {
e.printStackTrace();
return null;
}
}
/**
* 从队尾推出一个元素
*
* @param key 键
* @return 队首元素
*/
public Object rPop(String key){
try {
Object obj = _redisTemplate.opsForList().rightPop(key);
return obj;
} catch (Exception e) {
e.printStackTrace();
return null;
}
}
/*********************************pipeline*****************/
public <T> List<Object> executePipelined(RedisCallback<T> pFnCallBack){
return _redisTemplate.executePipelined(pFnCallBack,_redisTemplate.getValueSerializer());
}
public List<String> keys(String pPattern){
return _redisTemplate.keys(pPattern).stream().collect(Collectors.toList());
}
public void batchRemove(List<String> pKeyList){
new RedisCallback<Integer>() {
@Override
public Integer doInRedis(RedisConnection connection) throws DataAccessException {
connection.openPipeline();
for (String key : pKeyList) {
connection.expire(key.getBytes(),1);
}
return 0;
}
};
}
}
添加一个测试用的接口
package indi.zhifa.recipe.bailan.framework.controller.api;
@Api(tags = "2.redis测试")
@RequestMapping("/api/test/redis")
@Slf4j
@ZfRestController
@RequiredArgsConstructor
public class RedisTestApi {
private final RedisUtil mRedisUtil;
private final String TEST_REDIS_HASH_KEY = "TEST_HASH:";
private final String TEST_REDIS_SINGLE_KEY = "TEST_SINGLE:";
@Transactional(rollbackFor = Exception.class)
@Operation(summary = "设置全部")
@PostMapping("/hash/{key}")
public String hmset(
@Parameter(description = "redis-key") @PathVariable(name = "key") String pKey,
@Parameter(description = "类型配置") @RequestBody Map<String,Object> pData){
mRedisUtil.hmset(TEST_REDIS_HASH_KEY+pKey,pData);
return "设置成功";
}
@Transactional(rollbackFor = Exception.class)
@Operation(summary = "设置")
@PutMapping ("/hash/{key}")
public String hset(@Parameter(description = "类型Id") @PathVariable(name = "key") String pKey,
@Parameter(description = "类型Id") @RequestParam(name = "item") String pItem,
@Parameter(description = "类型配置") @RequestBody Object pData){
mRedisUtil.hset(TEST_REDIS_HASH_KEY+pKey,pItem,pData);
return "设置成功";
}
@Operation(summary = "设置")
@GetMapping("/hash/{key}")
public Map hmget(@Parameter(description = "类型Id") @PathVariable(name = "key") String pKey){
Map rtn = mRedisUtil.hmget(TEST_REDIS_HASH_KEY+pKey);
return rtn;
}
@Operation(summary = "hash-获取单个值")
@PutMapping ("/hash/{key}/{item}")
public Object hget(@Parameter(description = "类型Id") @PathVariable(name = "key") String pKey,
@Parameter(description = "类型Id") @RequestParam(name = "item") String pItem){
Object data = mRedisUtil.hget(TEST_REDIS_HASH_KEY+pKey,pItem);
return data;
}
@Transactional(rollbackFor = Exception.class)
@Operation(summary = "hash-删除键")
@DeleteMapping ("/hash/{key}/{item}")
public String hdel(@Parameter(description = "类型Id") @PathVariable(name = "key") String pKey,
@Parameter(description = "类型Id") @RequestParam(name = "item") String pItem){
mRedisUtil.hdel(TEST_REDIS_HASH_KEY+pKey,pItem);
return "删除成功";
}
@Transactional(rollbackFor = Exception.class)
@Operation(summary = "普通-设置值")
@PostMapping("/single/{key}")
public String set(
@Parameter(description = "redis-key") @PathVariable(name = "key") String pKey,
@Parameter(description = "类型配置") @RequestBody Object pData){
mRedisUtil.set(TEST_REDIS_SINGLE_KEY+pKey,pData);
return "设置成功";
}
@Operation(summary = "普通-获取值")
@GetMapping("/single/{key}")
public Object get(@Parameter(description = "类型Id") @PathVariable(name = "key") String pKey){
Object rtn = mRedisUtil.get(TEST_REDIS_SINGLE_KEY+pKey);
return rtn;
}
@Transactional(rollbackFor = Exception.class)
@Operation(summary = "普通-删除键")
@DeleteMapping ("/single/{key}/{item}")
public String delete(@Parameter(description = "类型Id") @PathVariable(name = "key") String pKey){
mRedisUtil.delete(TEST_REDIS_SINGLE_KEY+pKey);
return "删除成功";
}
}
这里要注意下,因为加上了事务注解,在函数没有结束调用时,是不提交redis的。比如这样写,只会查到之前的数据:
@Transactional(rollbackFor = Exception.class)
@Operation(summary = "设置全部")
@PostMapping("/hash/{key}")
public Map hmset(
@Parameter(description = "redis-key") @PathVariable(name = "key") String pKey,
@Parameter(description = "类型配置") @RequestBody Map<String,Object> pData){
mRedisUtil.hmset(TEST_REDIS_HASH_KEY+pKey,pData);
Map curData = mRedisUtil.hmget(TEST_REDIS_HASH_KEY+pKey);
return curData;
}
三、springboot下redis缓存的注解使用
springboot自带了一种简单的缓存实现,只需要在service上加上对应注解,就可以简单的使用缓存。
3.1 catch注解种类
我们在dependency中,可以查到Spring提供的缓存注解
注解 | 作用 | 样例 | 解释 |
---|---|---|---|
CachePut | 把数据放入缓存 | @CachePut(value = “TestRedisMemoEntity”,key = “#pId”) | value表示redis键的前缀,key = "#pId"表示取参数名为pId的为键。该注解可以用在创建和修改的接口中 |
Cacheable | 可以从缓存取数据 | @Cacheable(value = “TestRedisMemoEntity”,key = “#pId”) | 可以从缓存中读数据 |
CacheEvict | 从缓存中移除数据 | @CacheEvict(value = “TestRedisMemoEntity”,key = “#pId”) | 把对应id的数据从缓存中移除 |
CacheConfig | 对类全局配置 | @CacheConfig(cacheNames = “TestRedisMemoEntity”) | 放在类上,相当于对每一个接口都添加了(value = “TestRedisMemoEntity”) |
EnableCaching | 启用缓存 | @EnableCaching | 放在主类上或者redis的配置类,开启缓存 |
3.2 catch的配置
在【2.2 配置类】中已经展示了相关的配置代码,这里强调一些要点。
默认的redis序列化使用的是JDK,这里更推荐使用Json做为序列化工具,这样在出BUG时才可以更好的调试,因为json更可读些。
如果使用fastjson作为json库,要注意fastjson在新版本中需要做一些配置才可以支持json向Java普通类的转换,而且必须在写入json串时必须写入类信息。为此,fastjson配置类需要加入下列代码:
FastJsonConfig getRedisFastJson(){
FastJsonConfig config = new FastJsonConfig();
config.setWriterFeatures(
......
/***************这一句很重要***************/
JSONWriter.Feature.WriteEnumsUsingName);
/***************************************/
config.setReaderFeatures(
/***************这两句也很重要*************/
JSONReader.Feature.SupportClassForName,
// 支持autoType
JSONReader.Feature.SupportAutoType
/***************************************/
);
return config;
}
3.3 样例代码
下面,我们来展示使用缓存的样例代码。
为了省事,直接在原先enum-client-test写了
3.3.1 修改原来的IZfDbService
public interface IZfDbService<T> extends IService<T> {
......原先不变
/**************下列接口可用于缓存操作****************/
T check(Serializable pId);
T savePull(Long pId,T pEntity);
T updatePull(Long pId, T pEntity);
boolean deleteById(Serializable pId);
}
实现类的修改
public class ZfDbServiceImpl<M extends BaseMapper<T>, T> extends ServiceImpl<M, T> implements IZfDbService<T> {
......原先不变
@Override
public T savePull(Long pId,T pEntity) {
boolean success = super.save(pEntity);
if(success){
return pEntity;
}else{
throw new ServiceException(getEntityName()+"存储"+pId+" 失败");
}
}
@Override
public T updatePull(Long pId, T pEntity) {
boolean success = super.updateById(pEntity);
if(success){
return pEntity;
}else {
throw new ServiceException(getEntityName()+"更新"+pId+" 失败");
}
}
@Override
public boolean deleteById(Serializable pId) {
check(pId);
removeById(pId);
return true;
}
}
3.3.2 测试类
测试实体
package indi.zhifa.recipe.bailan.enumsclient.entity.po;
@Data
@TableName("test_redis_memo")
@Schema(title = "类型项", description = "类型项")
public class TestRedisMemoEntity extends BaseEntity implements Serializable {
Integer testNumA;
Integer testNumB;
String testStr;
}
3.3.3 测试mapper
package indi.zhifa.recipe.bailan.enumsclient.dao.mapper;
public interface TestRedisMemoMapper extends BaseMapper<TestRedisMemoEntity> {
}
3.3.4 测试 dbService接口和实现
package indi.zhifa.recipe.bailan.enumsclient.dao.service;
public interface ITestRedisMemoDbService extends IZfDbService<TestRedisMemoEntity> {
}
dbService实现
package indi.zhifa.recipe.bailan.enumsclient.dao.service.impl;
@CacheConfig(cacheNames = "test_redis_memo")
@Service
public class TestRedisMemoDbServiceImpl extends ZfDbServiceImpl<TestRedisMemoMapper, TestRedisMemoEntity> implements ITestRedisMemoDbService {
@Cacheable(key = "#pId")
public TestRedisMemoEntity check(Serializable pId){
return super.check(pId);
}
@CachePut(key = "#pId")
public TestRedisMemoEntity savePull(Long pId,TestRedisMemoEntity pEntity){
return super.savePull(pId,pEntity);
}
@CachePut(key = "#pId")
public TestRedisMemoEntity updatePull(Long pId, TestRedisMemoEntity pEntity) {
return super.updatePull(pId,pEntity);
}
@CacheEvict(key = "#pId")
public boolean removeById(Long pId){
return super.removeById(pId);
}
}
3.3.5 业务接口和实现
package indi.zhifa.recipe.bailan.enumsclient.service;
public interface IRedisTestService {
TestRedisMemoEntity info(Long pId);
Page<TestRedisMemoEntity> page(int pCurrent, int pSize, Integer pAMin, Integer pAMax);
TestRedisMemoEntity create(RedisMemoDto pRedisMemoDto);
TestRedisMemoEntity edit(Long pId, RedisMemoDto pRedisMemoDto);
boolean delete(Long pId);
}
业务实现
package indi.zhifa.recipe.bailan.enumsclient.service.impl;
@RequiredArgsConstructor
@Service
public class RedisTestServiceImpl implements IRedisTestService {
private final ITestRedisMemoDbService mTestRedisMemoDbService;
@Override
public TestRedisMemoEntity info(Long pId) {
TestRedisMemoEntity testRedisMemoEntity = mTestRedisMemoDbService.check(pId);
return testRedisMemoEntity;
}
@Override
public Page<TestRedisMemoEntity> page(int pCurrent, int pSize, Integer pAMin, Integer pAMax) {
Page<TestRedisMemoEntity> pageCfg = new Page<TestRedisMemoEntity>(pCurrent,pSize);
LambdaQueryWrapper<TestRedisMemoEntity> queryWrapper = Wrappers.<TestRedisMemoEntity>lambdaQuery()
.ge(null != pAMin,TestRedisMemoEntity::getTestNumA,pAMin)
.le(null!= pAMax,TestRedisMemoEntity::getTestNumA,pAMax);
Page<TestRedisMemoEntity> pageData = mTestRedisMemoDbService.page(pageCfg,queryWrapper);
return pageData;
}
@Transactional(rollbackFor = Exception.class)
@Override
public TestRedisMemoEntity create(RedisMemoDto pRedisMemoDto) {
TestRedisMemoEntity testRedisMemoEntity = DbDtoEntityUtil.createFromDto(pRedisMemoDto,TestRedisMemoEntity.class);
mTestRedisMemoDbService.savePull(testRedisMemoEntity.getId(),testRedisMemoEntity);
return testRedisMemoEntity;
}
@Transactional(rollbackFor = Exception.class)
@Override
public TestRedisMemoEntity edit(Long pId, RedisMemoDto pRedisMemoDto) {
TestRedisMemoEntity orgTestRedisMemo = mTestRedisMemoDbService.check(pId);
TestRedisMemoEntity testRedisMemoEntity = DbDtoEntityUtil.editByDto(orgTestRedisMemo,pRedisMemoDto,TestRedisMemoEntity.class);
mTestRedisMemoDbService.updatePull(pId,testRedisMemoEntity);
return testRedisMemoEntity;
}
@Transactional(rollbackFor = Exception.class)
@Override
public boolean delete(Long pId) {
return mTestRedisMemoDbService.deleteById(pId);
}
}
3.3.6 controller
package indi.zhifa.recipe.bailan.enumsclient.controller.api;
@Api(tags = "redis缓存测试")
@RequestMapping("/api/test/redis")
@Slf4j
@ZfRestController
@RequiredArgsConstructor
public class RedisTestMemoApi {
private final IRedisTestService mRedisTestService;
@Operation(summary = "info")
@GetMapping("/{id}")
TestRedisMemoEntity info(@Parameter(description = "id") @PathVariable(name = "id") Long pId){
TestRedisMemoEntity testRedisMemoEntity = mRedisTestService.info(pId);
return testRedisMemoEntity;
}
@Operation(summary = "page")
@GetMapping("/page")
Page<TestRedisMemoEntity> page(
@Parameter(description = "current") @RequestParam(name = "current") int pCurrent,
@Parameter(description = "size") @RequestParam(name = "size") int pSize,
@Parameter(description = "aMin") @RequestParam(name = "aMin") Integer pAMin,
@Parameter(description = "aMax") @RequestParam(name = "aMax") Integer pAMax){
Page<TestRedisMemoEntity> testRedisMemoEntityPage = mRedisTestService.page(pCurrent,pSize,pAMin,pAMax);
return testRedisMemoEntityPage;
}
@Operation(summary = "create")
@PostMapping("")
TestRedisMemoEntity create(@RequestBody RedisMemoDto pRedisMemoDto){
TestRedisMemoEntity testRedisMemoEntity = mRedisTestService.create(pRedisMemoDto);
return testRedisMemoEntity;
}
@Operation(summary = "edit")
@PutMapping("/{id}")
TestRedisMemoEntity edit(
@Parameter(description = "id") @PathVariable(name = "id") Long pId,
@RequestBody RedisMemoDto pRedisMemoDto){
TestRedisMemoEntity testRedisMemoEntity = mRedisTestService.edit(pId,pRedisMemoDto);
return testRedisMemoEntity;
}
@Operation(summary = "info")
@DeleteMapping ("/{id}")
boolean delete(@Parameter(description = "id") @PathVariable(name = "id") Long pId){
return mRedisTestService.delete(pId);
}
}
3.3.7 测试过程
-1) 创建-create
-2)查看redis
-3)查看
-4)观察IDEA输出log,发现没有查SQL
-5)修改数据
-6)再次查看
-7)删除
-8)查看redist是否删除缓存
确实删除了
-9) 再度查看
3.4 关于mapper层级缓存
mybatis-plus 继承自mybatis,原理上可以使用mybatis的2级缓存。但这里小编十分不推荐,因为该2级缓存是以sql查询为键的,也就是缓存的sql的结果。一方面,当数据更新时,会出现脏读问题。另一方面对于缓存的量也很不好控制。可能只在于应对重复查询请求时比较有效。
当然也可能是小编学艺不精,没有get到该做法的精髓。
具体做法,如下:
3.4.1 在配置上开启2级缓存
mybatis-plus:
typeEnumsPackage: indi.zhifa.recipe.bailan.enumsclient.entity.enums
#call-setters-on-nulls: true
global-config:
configuration:
#缓存开启
cache-enabled: true
3.4.2 实现Cache
@Slf4j
public class MybatisPlusRedisCache implements Cache {
private final String mName;
private final String mId;
private final RedisUtil mRedisUtil;
private final AppProperty mAppProperty;
private final String CATCH_PREFIX = "CATCH:";
public MybatisPlusRedisCache(String pId){
if(!StringUtils.hasText(pId)){
throw new ServiceException("缓存需要一个id");
}
mName = pId;
mId = "mapper_cache:"+ MD5.create().digestHex16(mName);
mRedisUtil = SpringUtil.getBean(RedisUtil.class);
mAppProperty = SpringUtil.getBean(AppProperty.class);
}
@Override
public String getId() {
return mId;
}
@Override
public void putObject(Object key, Object value) {
String keyMd5 = MD5.create().digestHex16(key.toString());
mRedisUtil.hset(mId,keyMd5+":"+key,value,mAppProperty.getExpireTime());
}
@Override
public Object getObject(Object key) {
String keyMd5 = MD5.create().digestHex16(key.toString());
return mRedisUtil.hget(mId,keyMd5+":"+key);
}
@Override
public Object removeObject(Object key) {
String keyMd5 = MD5.create().digestHex16(key.toString());
mRedisUtil.hdel(mId,keyMd5);
return null;
}
@Override
public void clear() {
List<String> keys = mRedisUtil.keys(mId+"*");
mRedisUtil.batchRemove(keys);
}
@Override
public int getSize() {
return 0;
}
}
3.4.3 在mapper上添加注解
@CacheNamespace(implementation= MybatisPlusRedisCache.class,eviction=MybatisPlusRedisCache.class, blocking = true)
public interface TestRedisMemoMapper extends BaseMapper<TestRedisMemoEntity> {
}