芝法酱躺平攻略(7)——SpringBoot下redis的工具类与缓存

前言

在芝法酱躺平攻略(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> {
}
  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
在使用SpringBoot引入Redis工具类时,首先需要在pom.xml文件中添加以下依赖: ```xml <dependencies> <!-- SpringBoot Data Redis --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-redis</artifactId> </dependency> <!-- Redis --> <dependency> <groupId>redis.clients</groupId> <artifactId>jedis</artifactId> </dependency> </dependencies> ``` 接下来,在SpringBoot的配置类中添加Redis的配置信息,通常是application.properties或application.yml文件。示例: ```properties # Redis配置 spring.redis.host=127.0.0.1 spring.redis.port=6379 spring.redis.database=0 spring.redis.password= spring.redis.timeout=30000 ``` 然后,创建一个RedisUtil工具类,用来封装Redis的常用操作方法。可以使用Jedis库对Redis进行操作。示例: ```java import org.springframework.beans.factory.annotation.Autowired; import org.springframework.data.redis.core.StringRedisTemplate; import org.springframework.stereotype.Component; @Component public class RedisUtil { @Autowired private StringRedisTemplate stringRedisTemplate; // 存储键值对 public void set(String key, String value) { stringRedisTemplate.opsForValue().set(key, value); } // 获取键对应的值 public String get(String key) { return stringRedisTemplate.opsForValue().get(key); } // 删除键值对 public void delete(String key) { stringRedisTemplate.delete(key); } // 判断键是否存在 public boolean exists(String key) { return stringRedisTemplate.hasKey(key); } } ``` 最后,可以在其他的Spring组件中使用RedisUtil来进行Redis操作。示例: ```java import org.springframework.beans.factory.annotation.Autowired; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; @RestController @RequestMapping("/example") public class ExampleController { @Autowired private RedisUtil redisUtil; @RequestMapping("/test") public String test() { // 存储键值对 redisUtil.set("key", "value"); // 获取键对应的值 String value = redisUtil.get("key"); return value; } } ``` 这样,就可以在SpringBoot中引入Redis工具类,并进行常用的Redis操作了。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值