我们在做后端服务的时候,要非常注重性能,比如我们一个项目,大家回频繁地打开某一个页面的时候,回频繁地去后台请求数据库访问数据,重复地查询一样的数据,这样一想,在数据库里那部分数据没有被更新的情况下还重复地去查询,是不是觉得有点浪费资源了呢?是的,那么我们就要想办法把那些不必要每次都从数据库里拿的数据(更新频率低)做缓存,不要每次访问页面都去查询一次。

我们使用 Redis 来实现缓存

1.引入需要的依赖

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-cache</artifactId>
</dependency>

关于Redis的配置这里就不重复啰嗦了

2.往Redis里做的缓存的单位并不是单纯的一个对象,或者一个字符串这么简单,而是缓存了某个接口的全部返回内容

例如我有一个获取商品列表的接口,那么我的 Redis 缓存的就是这个接口返回的数据,所以我们是对接口进行操作的

但是我们要先把返回的内容进行序列化,必须是可以序列化的对象才能被缓存到 Redis 里

/**
 * http请求返回的最外层对象
 * @author: 林之谦
 * @date: 2018/7/27
 * @description:
 */
@Data
//@JsonInclude(JsonInclude.Include.NON_NULL)
public class ResultVO<T> implements Serializable {


    private static final long serialVersionUID = 1451589387303987463L;

    /** 错误码 */
    private Integer code;

    /** 提示信息 */
    private String msg;

    /** 返回的具体内容 */
    private T data;
}

所以我这里举例的返回对象是 ResultVO 这个类,那么我们要让它实现 Serializable接口,并且给一个 UID

这里生成UID 推荐一个插件叫 GenerateSeriaVersionUID,设置了快捷键之后可以直接生成 UID

3.在接口上加 cache 的注解,他就会自动地把这个接口的返回结果缓存到 Redis 里,下次再访问这个接口的时候它就会先去 Redis 里查一下有没有要的数据,有的话就不再进入这个接口,而是直接从 Redis 里获取那些数据,提高了效率,也节约了资源

如果一个类里要在 Redis 里使用相同的文件名的话,可以给类加一个注解,如下

@CacheConfig(cacheNames = "product")
public class BuyerProductController {
       ...
}

那么给接口的注解里就不用加 cacheNames 这一项了,只需要 key,如下

@Cacheable(key = "123")
public ResultVO list(){
    ...
}

如果不给类加那个注解的话那么就要填写完整

@Cacheable(cacheNames = "product", key = "123")
public ResultVO list(){
    ...
}

此外我们还可以加一个过虑,就是当这个接口返回正确时才缓存,不正确时不缓存,看一下我们上面的 ResultVO 类,有一个属性叫错误码,返回正确时为 0,那么我们可以使用 unless 这个注解项,如下

@Cacheable(cacheNames = "product", key = "123",unless = "#result.getCode() != 0")
public ResultVO list(){
    ...
}

里面的 #result 表示返回的对象,那么这个注解的意思时  它会进行缓存除非状态码不为 0 ,所以只有状态码为 0 时才会缓存

所以这个类的完整代码如下

/**
 * @author: 林之谦
 * @date: 2018/7/27
 * @description:
 */

@RestController
@RequestMapping("/buyer/product")
@CacheConfig(cacheNames = "product")
public class BuyerProductController {

    @Autowired
    private ProductInfoService productService;

    @Autowired
    private ProductCategoryService categoryService;

    @GetMapping("/list")
    @Cacheable( key = "123",unless = "#result.getCode() != 0")
    public ResultVO list(){

        System.out.println("【进入数据库查询商品信息】");
        // 1.查询所有的上架商品
        List<ProductInfo> productInfoList = productService.findUpAll();

        // 2.查询类目(一次性查询)
        //传统方法
//        for (ProductInfo productInfo : productInfoList){
//            categoryTypeList.add(productInfo.getCategoryType());
//        }
        //精简的做法 (java8,lambda)
        List<Integer> categoryTypeList = productInfoList.stream()
                .map(e->e.getCategoryType())
                .collect(Collectors.toList());

        List<ProductCategory> productCategoryList = categoryService.findByCategoryTypeIn(categoryTypeList);


        // 3.数据拼装
        List<ProductVO> productVOList = new ArrayList<>();

        for (ProductCategory productCategory : productCategoryList){
            ProductVO productVO = new ProductVO();
            productVO.setCategoryType(productCategory.getCategoryType());
            productVO.setCategoryName(productCategory.getCategoryName());

            List<ProductInfoVO> productInfoVOList = new ArrayList<>();
            for(ProductInfo productInfo : productInfoList){
                if(productInfo.getCategoryType().equals(productCategory.getCategoryType())){
                    ProductInfoVO productInfoVO = new ProductInfoVO();
                    BeanUtils.copyProperties(productInfo,productInfoVO);
                    productInfoVOList.add(productInfoVO);
                }
            }
            productVO.setProductInfoVOList(productInfoVOList);

            productVOList.add(productVO);
        }

        return ResultVOUtil.success(productVOList);
    }
}

4.那么如果我们数据更新了怎么办?没关系,我们再更新数据的接口上做操作,让它更新数据的时候刷新一下缓存

我们有两个注解

@CacheEvict  这个注解会在方法执行后去清除注解里指定 key 的缓存

@CachePut   这个注解跟 Cacheable一样会把返回的内容做缓存,但是不一样的是,它不会在方法执行前去判断是否执行方法,而是永远都执行方法,然后更新掉

以上两种方法都是可以的,一般都把他们用在发生数据更新的地方

注意 key 要对应好,我们上面的注解给的 key 是 123,那么我们要清除缓存时这个 key 要一样

@CacheEvict(key = "123")
    public ModelAndView save(@Valid ProductForm form,
                             BindingResult bindingResult,
                             Map<String,Object> map){
        if(bindingResult.hasErrors()){
            map.put("msg",bindingResult.getFieldError().getDefaultMessage());
            map.put("url","/sell/seller/product/index");
            return new ModelAndView("common/error",map);
        }
        ProductInfo productInfo = new ProductInfo();
        try {
            // 如果productId 为空是新增
            if(!StringUtils.isEmpty(form.getProductId())){
                productInfo = productInfoService.findOne(form.getProductId());
            }else{
                form.setProductId(KeyUtil.genUniqueKey());
            }
            BeanUtils.copyProperties(form,productInfo);
            productInfoService.save(productInfo);
        }catch (SellException e){
            map.put("msg",e.getMessage());
            map.put("url","/sell/seller/product/index");
            return new ModelAndView("common/error",map);
        }
        map.put("url","/sell/seller/product/list");
        return new ModelAndView("common/success",map);
    }

再举个 CachePut的例子吧

@Override
    @Cacheable(key = "111")
    public ProductInfo findOne(String productId) {
        System.out.println("【进入查询数据库商品信息】");
        return repository.findOne(productId);
    }

    @Override
    @CachePut(key = "111")
    public ProductInfo save(ProductInfo productInfo) {
        return repository.save(productInfo);
    }

以上就是使用 Redis 缓存的基本操作,但是注意不要滥用缓存