SpringBoot(4) ------>整合CacheManager与Redis

1、向pom文件中添加依赖

     <!--springboot中的redis依赖-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-redis</artifactId>
        </dependency>

2、application.yml添加配置

spring:
  redis:
    host: localhost     # Redis服务器地址
    database: 0         # Redis数据库索引(默认为0)
    port: 6379          # Redis服务器连接端口
    password: ld123456  # Redis服务器连接密码(默认为空)

3、RedisConfig配置类

package com.donleo.mybatis.config;

import com.fasterxml.jackson.annotation.JsonAutoDetect;
import com.fasterxml.jackson.annotation.PropertyAccessor;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.springframework.cache.CacheManager;
import org.springframework.cache.annotation.CachingConfigurerSupport;
import org.springframework.cache.annotation.EnableCaching;
import org.springframework.cache.interceptor.KeyGenerator;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.cache.RedisCacheConfiguration;
import org.springframework.data.redis.cache.RedisCacheManager;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.serializer.Jackson2JsonRedisSerializer;
import org.springframework.data.redis.serializer.RedisSerializationContext;
import org.springframework.data.redis.serializer.RedisSerializer;
import org.springframework.data.redis.serializer.StringRedisSerializer;

import java.util.Arrays;

/**
 * @author liangd
 * date 2020-12-04 16:15
 * code Redis配置类
 */
@Configuration
@EnableCaching
public class RedisConfig extends CachingConfigurerSupport {
    /**
     * 配置redisTemplate 代替默认配置
     * @param redisConnectionFactory  RedisConnectionFactory
     * @return  RedisTemplate
     */
    @Bean
    public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory redisConnectionFactory) {
        RedisTemplate<String, Object> template = new RedisTemplate<>();
        template.setConnectionFactory(redisConnectionFactory);
        Jackson2JsonRedisSerializer serializer = new Jackson2JsonRedisSerializer(Object.class);
        ObjectMapper mapper = new ObjectMapper();
        mapper.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
        mapper.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL);
        serializer.setObjectMapper(mapper);

        template.setValueSerializer(serializer);
        //使用StringRedisSerializer来序列化和反序列化redis的key值
        template.setKeySerializer(new StringRedisSerializer());
        template.afterPropertiesSet();
        return template;
    }

    /**
     *
     * 配置 cacheManager 代替默认的cacheManager (缓存管理器)
     * @param factory RedisConnectionFactory
     * @return  CacheManager
     */
    @Bean
    public CacheManager cacheManager(RedisConnectionFactory factory) {
        RedisSerializer<String> redisSerializer = new StringRedisSerializer();
        Jackson2JsonRedisSerializer serializer = new Jackson2JsonRedisSerializer(Object.class);
        ObjectMapper objectMapper = new ObjectMapper();
        objectMapper.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
        objectMapper.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL);
        serializer.setObjectMapper(objectMapper);
        // 配置序列化
        RedisCacheConfiguration config = RedisCacheConfiguration.defaultCacheConfig()
                .serializeKeysWith(RedisSerializationContext.SerializationPair.fromSerializer(redisSerializer))
                .serializeValuesWith(RedisSerializationContext.SerializationPair.fromSerializer(serializer));

        RedisCacheManager cacheManager = RedisCacheManager.builder(factory)
                .cacheDefaults(config)
                .build();
        return cacheManager;
    }

    /**
     * 配置KeyGenerator
     * @return
     */
    @Bean
    @Override
    public KeyGenerator keyGenerator() {
        //lambda 表达式
        return (target, method, params) -> method.getName() + Arrays.asList(params);
    }
    /*
        // 1. 不需要参数,返回值为 5
            () -> 5

        // 2. 接收一个参数(数字类型),返回其2倍的值
            x -> 2 * x

        // 3. 接受2个参数(数字),并返回他们的差值
            (x, y) -> x – y

        // 4. 接收2个int型整数,返回他们的和
            (int x, int y) -> x + y

        // 5. 接受一个 string 对象,并在控制台打印,不返回任何值(看起来像是返回void)
            (String s) -> System.out.print(s)
     */
     /*   @Bean
       @Override
       public KeyGenerator keyGenerator() {
           return (target,  method,  params)->{
               return method.getName()+ Arrays.asList(params);
           };
       }*/
}

4、自定义Redis工具类

  1)接口层

package com.donleo.mybatis.service;

/**
 * @author liangd
 * date 2020-12-04 16:19
 * code 自定义封装redis接口定义
 */
public interface IRedisService {
    /**
     * 指定缓存失效时间
     * @param key 键
     * @param time 时间(秒)
     * @return
     */
    boolean expire(String key, long time);

    /**
     * 根据key 获取过期时间
     * @param key 键 不能为null
     * @return 时间(秒) 返回0代表为永久有效
     */
    long getExpire(String key);

    /**
     * 判断key是否存在
     * @param key 键
     * @return true 存在 false不存在
     */

    boolean hasKey(String key);

    /**
     * 删除缓存
     * @param key 可以传一个值 或多个
     */

    void del(String... key);

    /**
     * 普通缓存获取
     * @param key 键
     * @return*/
    Object get(String key);

    /**
     * 普通缓存放入
     * @param key 键
     * @param value 值
     * @return true成功 false失败
     */
    boolean set(String key, Object value);

    /**
     * 普通缓存放入并设置时间
     * @param key 键
     * @param value 值
     * @param time 时间(秒) time要大于0 如果time小于等于0 将设置无限期
     * @return true成功 false 失败
     */
    boolean set(String key, Object value, long time);
}

  2)实现层

package com.donleo.mybatis.service.impl;

import com.donleo.mybatis.service.IRedisService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Service;
import org.springframework.util.CollectionUtils;

import java.util.concurrent.TimeUnit;

/**
 * @author liangd
 * date 2020-12-04 16:21
 * code 自定义封装Redis接口定义
 */
@Service
public class RedisServiceImpl implements IRedisService {

    @Autowired
    private RedisTemplate redisTemplate;

    /**
     * 指定缓存失效时间
     *
     * @param key  键
     * @param time 时间(秒)
     * @return
     */
    @Override
    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代表为永久有效
     */
    @Override
    public long getExpire(String key) {
        return redisTemplate.getExpire(key, TimeUnit.SECONDS);
    }

    /**
     * 判断key是否存在
     *
     * @param key 键
     * @return true 存在 false不存在
     */
    @Override
    public boolean hasKey(String key) {
        try {
            return redisTemplate.hasKey(key);
        } catch (Exception e) {
            e.printStackTrace();
            return false;
        }
    }

    /**
     * 删除缓存
     *
     * @param key 可以传一个值 或多个
     */
    @Override
    @SuppressWarnings("unchecked")
    public void del(String... key) {
        if (key != null && key.length > 0) {
            if (key.length == 1) {
                redisTemplate.delete(key[0]);
            } else {
                redisTemplate.delete(CollectionUtils.arrayToList(key));
            }
        }
    }

    //============================String=============================

    /**
     * 普通缓存获取
     *
     * @param key 键
     * @return*/
    @Override
    public Object get(String key) {
        return key == null ? null : redisTemplate.opsForValue().get(key);
    }

    /**
     * 普通缓存放入
     *
     * @param key   键
     * @param value 值
     * @return true成功 false失败
     */
    @Override
    public boolean set(String key, Object value) {
        try {
            redisTemplate.opsForValue().set(key, value);
            return true;
        } catch (Exception e) {
            e.printStackTrace();
            return false;
        }
    }

    /**
     * 普通缓存放入并设置时间
     *
     * @param key   键
     * @param value 值
     * @param time  时间(秒) time要大于0 如果time小于等于0 将设置无限期
     * @return true成功 false 失败
     */
    @Override
    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;
        }
    }
}

5、测试-->通过Redis工具类缓存

package com.donleo.mybatis.service.impl;

import com.donleo.mybatis.common.CommonResult;
import com.donleo.mybatis.dao.IDeptMapper;
import com.donleo.mybatis.model.Dept;
import com.donleo.mybatis.service.IDeptService;
import com.donleo.mybatis.service.IRedisService;
import org.springframework.stereotype.Service;

import javax.annotation.Resource;
import java.util.List;

/**
 * @author liangd
 * date 2020-12-03 11:09
 * code 部门逻辑层(使用自定义RedisTemplate设置缓存)
 */
@Service
public class DeptServiceImpl implements IDeptService {

    @Resource
    private IDeptMapper deptMapper;

    @Resource
    private IRedisService redisService;

    @Override
    public CommonResult findAll() {
        List<Dept> list;
        boolean b = redisService.hasKey("deptList:");
        if (b) {
            Object object = redisService.get("deptList:");
            list = (List<Dept>) object;
        } else {
            list = deptMapper.selectAll();
            redisService.set("deptList:",list);
        }
        return CommonResult.success(list);
    }

    @Override
    public Integer add(Dept dept) {
        dept.setId(null);
        deptMapper.insert(dept);
        return dept.getId();
    }

    @Override
    public Integer delete(Integer id) {
        redisService.del("dept:"+id);
        redisService.del("deptList:");
        return deptMapper.deleteByPrimaryKey(id);
    }

    @Override
    public Integer update(Dept dept) {
        return deptMapper.updateByPrimaryKeySelective(dept);
    }

    @Override
    public Dept findById(Integer id) {
        //判断redis中是否存在当前key,加一个冒号生成文件夹
        boolean b = redisService.hasKey("dept:" + id);
        Dept dept;
        //如果存在,从redis中查询,否则从数据库中查询
        if (b) {
            dept = (Dept) redisService.get("dept:" + id);
        } else {
            dept = deptMapper.selectByPrimaryKey(id);
            //放入resis中
            redisService.set("dept:"+id, dept);
        }
        return dept;
    }
}

6、测试-->通过CacheManager缓存(1)

package com.donleo.mybatis.service.impl;

import com.donleo.mybatis.common.CommonResult;
import com.donleo.mybatis.dao.IRoleMapper;
import com.donleo.mybatis.model.Role;
import com.donleo.mybatis.service.IRoleService;
import org.springframework.cache.annotation.*;
import org.springframework.stereotype.Service;

import javax.annotation.Resource;
import java.util.List;

/**
 * @author liangd
 * date 2020-12-04 17:42
 * code 角色逻辑实现层(使用注解CacheManager设置缓存)
 * 使用@CacheConfig配置缓存名
 */
@Service
@CacheConfig(cacheNames = "role")
public class RoleServiceImpl implements IRoleService {

    @Resource
    private IRoleMapper roleMapper;

    /**
     * 使用@Caching注解可以让我们在一个方法或者类上同时指定多个Spring Cache相关的注解。
     * 其拥有三个属性:cacheable、put和evict,分别用于指定@Cacheable、@CachePut和@CacheEvict。
     *
     * @param role
     * @return
     */
    @Override
    @Caching(
            put = @CachePut(key = "#role.id"),
            evict = @CacheEvict(key = "'findAllRole[]'")
    )
    public CommonResult add(Role role) {
        role.setId(null);
        try {
            roleMapper.insert(role);
        } catch (Exception e) {
            return CommonResult.failed();
        }
        return CommonResult.success(role.getId());
    }

    /**
     * 使用@CacheEvict移除key
     * @param id
     * @return
     */
    @Override
    @CacheEvict(key = "#id")
    public Integer delete(Integer id) {
        return roleMapper.deleteByPrimaryKey(id);
    }

    @Override
    public Integer update(Role role) {
        return roleMapper.updateByPrimaryKeySelective(role);
    }

    /**
     * 使用@Cacheable标注的方法,Spring在每次执行前都会检查Cache中是否存在相同key的缓存元素,
     * 如果存在就不再执行该方法,而是直接从缓存中获取结果进行返回,否则才会执行并将返回结果存入指定的缓存中。
     *
     * @param id
     * @return
     */
    @Override
    @Cacheable(key = "#id")
    public Role findById(Integer id) {
        return roleMapper.selectByPrimaryKey(id);
    }

    /**
     * 使用@CachePut 也可以声明一个方法支持缓存功能。
     * 与@Cacheable不同的是使用@CachePut标注的方法在执行前不会去检查缓存中是否存在之前执行过的结果,
     * 而是每次都会执行该方法,并将执行结果以键值对的形式存入指定的缓存中。
     *
     * @return
     * keyGenerator将方法名作为key,存进缓存,例如findAllRole[],删除key时需要添加单引号
     */
    @Override
    @CachePut(keyGenerator = "keyGenerator")
    public CommonResult findAllRole() {
        List<Role> list = roleMapper.selectAll();
        return CommonResult.success(list);
    }
}

7、测试-->通过CacheManager缓存(2)

package com.donleo.cache.service.impl;

import com.donleo.cache.common.CommonResult;
import com.donleo.cache.mapper.IRoleMapper;
import com.donleo.cache.model.Role;
import com.donleo.cache.service.IRoleService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cache.annotation.*;
import org.springframework.stereotype.Service;

import java.util.List;

/**
 * @author liangd
 * date 2020-12-10 09:49
 * code
 * .@CacheConfig抽取缓存的公共配置,在这里配置了cacheNames就不需要再每个方法上面指定value属性了
 */
@Service
@CacheConfig(cacheNames = "role")
public class RoleServiceImpl implements IRoleService {

    @Autowired
    private IRoleMapper roleMapper;

    /**
     * .@Cacheable标注的方法执行之前先来检查缓存中有没有这个数据,默认按照参数的值作为key去查询缓存,
     * 如果没有就运行方法并将结果放入缓存;以后再来调用就可以直接使用缓存中的数据;
     *
     * .@Cacheable 将方法的运行结果进行缓存,以后再要相同的数据,直接从缓存中获取,不用调用方法;
     * <p>
     * CacheManager管理多个Cache组件的,对缓存的真正CRUD操作在Cache组件中,每一-个缓存组件有自己唯- --一个名字;
     * <p>
     * <p>
     * 原理:
     * 1、自动配置类 CacheAutoConfiguration
     * 2、缓存的配置类
     * org.springframework.boot.autoconfigure.cache.GenericCacheConfiguration
     * org.springframework.boot.autoconfigure.cache.JCacheCacheConfiguration
     * org.springframework.boot.autoconfigure.cache.EhCacheCacheConfiguration
     * org.springframework.boot.autoconfigure.cache.HazelcastCacheConfiguration
     * org.springframework.boot.autoconfigure.cache.InfinispanCacheConfiguration
     * org.springframework.boot.autoconfigure.cache.CouchbaseCacheConfiguration
     * org.springframework.boot.autoconfigure.cache.RedisCacheConfiguration
     * org.springframework.boot.autoconfigure.cache.CaffeineCacheConfiguration
     * org.springframework.boot.autoconfigure.cache.GuavaCacheConfiguration
     * org.springframework.boot.autoconfigure.cache.SimpleCacheConfiguration【默认】
     * org.springframework.boot.autoconfigure.cache.NoOpCacheConfiguration
     * <p>
     * 3、哪个配置类默认生效:SimpleCacheConfiguration;
     * <p>
     * 4、给容器中注册了一个CacheManager:ConcurrentMapCacheManager
     * 5、可以获取和创建ConcurrentMapCache类型的缓存组件;他的作用将数据保存在ConcurrentMap中;
     * <p>
     * 运行流程:
     * <p>
     * .@Cacheable:
     * 1、方法运行之前,先去查询Cache(缓存组件),按照cacheNames指定的名字获取;
     *  (CacheManager先获取相应的缓存),第一次获取缓存如果没有Cache组件会自动创建。
     * 2、去Cache中查找缓存的内容,使用一个key,默认就是方法的参数;
     *      key是按照某种策略生成的;默认是使用keyGenerator生成的,默认使用SimpleKeyGenerator生成key;
     *      SimpleKeyGenerator生成key的默认策略;
     *          如果没有参数;key=new SimpleKey();
     *          如果有一个参数:key=参数的值
     *          如果有多个参数:key=new SimpleKey(params);
     * 3、没有查到缓存就调用目标方法;
     * 4、将目标方法返回的结果,放进缓存中
     * <p>
     * <p>
     * 核心:
     * 1)、使用CacheManager【ConcurrentMapCacheManager】按照名字得到Cache【ConcurrentMapCache】组件
     * 2)、key使用keyGenerator生成的,默认是SimpleKeyGenerator
     * <p>
     * 几个属性:
     * a)cacheNames/value:指定缓存组件的名字
     *      cacheNames = {"role"}可以使用多个参数,是数组的形式,可以指定多个缓存
     * b)key:缓存数据使用的key,可以用他来指定。默认是使用方法参数的值
     *      编写SpEl:   #id  #a0,#po,#argrs[0]  "0"代表参数的索引
     *      #result  方法执行后的返回值
     *      #root.methodName   方法名
     *      key = "#root.methodName+'['+#id+']'"
     * c)keyGenerator: key的生成器、可以自己指定key的生成器的组件Id
     *      key/keyGenerator 二选一
     *      keyGenerator = "myKeyGenerator"
     * d)cacheManager:指定缓存管理器或者cacheResolver:获取解析器
     *      cacheManager/cacheResolver 二选一
     * e)condition:指定符合缓存的条件
     *      condition = "#id>0 and #root.methodName eq 'aaa'" 可以多条件判断
     * f)unless: 否定缓存,当unless的条件为true,方法结果不会被缓存,可以获取结果进行判断
     *      unless = "#result==null",结果为null,就不缓存
     * g)sync:是否使用异步模式
     *      默认false   同步
     *      为true时,unless不支持
     */

    @Override
    @Cacheable(value = {"role"}, key = "#id",
            condition = "#id>0", unless = "#result==null")
    public Role findById(Integer id) {
        return roleMapper.selectById(id);
    }

    /**
     * .@CachePut既调用方法、又更新数据,达到同步更新缓存
     * <p>
     * 运行时机:
     * 1、先调用目标方法
     * 2、将目标方法的结果缓存起来
     *
     * 条件:存取Id的key要保持一致
     *     key = "#role.id"     传入员工的Id
     *     key = "#result.id"   使用返回员工的Id
     * 注意: @Cacheable不能使用#result
     *      因为 @Cacheable在目标方法执行之前需要得到这个key,所以不能用#result
     */
    @Override
    @CachePut(value = "role", key = "#result.id")
    public Role update(Role role) {
        roleMapper.updateById(role);
        return role;
    }

    /**
     * .@CacheEvict 缓存清除
     *
     *  key:指定要清除的数据
     *  allEntries:指定清除这个缓存库的所有数据,默认为false
     *  beforeInvocation:在执行方法之前清除,默认为false,在方法之后执行
     *
     */
    @Override
    @CacheEvict(/*value = "role",*/key = "#id")
    public Integer delete(Integer id) {
        return roleMapper.deleteById(id);
    }

    /**
     * .@Caching 定义复杂缓存规则
     */
    @Override
    @Caching(
            cacheable = {
                    @Cacheable(key = "#role.roleName")
            },
            put = {
                    @CachePut(key = "#role.id"),
                    @CachePut(key = "#role.roleCode")
            }
    )
    public CommonResult add(Role role) {
        role.setId(null);
        try {
            roleMapper.insert(role);
        } catch (Exception e) {
            return CommonResult.failed();
        }
        return CommonResult.success(role.getId());
    }

    @Override
    public CommonResult findAllRole() {
        List<Role> roleList = roleMapper.selectList(null);
        return CommonResult.success(roleList);
    }
}
作者:donleo123
本文如对您有帮助,还请多推荐下此文,如有错误欢迎指正,相互学习,共同进步。
原文地址:https://www.cnblogs.com/donleo123/p/14286977.html