SpringBoot结合SpringCache操作Redis实现数据缓存

SpringBoot结合SpringCache操作Redis实现数据缓存

1.系统环境

  • Redis 版本:5.0.7
  • SpringBoot 版本:2.2.2.RELEASE

2.参考地址

  • Redus 官方网址:https://redis.io/
  • https://github.com/my-dlq/blog-example/tree/master/springboot/springboot-redis-cache-example

3.缓存概念知识

3.1 什么是缓存

我们日常生活中,经常会接触听到缓存这个词,例如,浏览器清空缓存,处理器缓存大小,磁盘缓存等等。经过分类,可以将缓存分为:

  • 硬件缓存: 一般指的是机器上的 CPU、硬盘等等组件的缓存区间,一般是利用的内存作为一块中转区域,都通过内存交互信息,减少系统负载,提供传输效率。
  • 客户端缓存: 一般指的是某些应用,例如浏览器、手机App、视频缓冲等等,都是在加载一次数据后将数据临时存储到本地,当再次访问时候先检查本地缓存中是否存在,存在就不必去远程重新拉取,而是直接读取缓存数据,这样来减少远端服务器压力和加快载入速度。
  • 服务端缓存: 一般指远端服务器上,考虑到客户端请求量多,某些数据请求量大,这些热点数据经常要到数据库中读取数据,给数据库造成压力,还有就是 IO、网络等原因有一定延迟,响应客户端较慢。所以,在一些不考虑实时性的数据中,经常将这些数据存在内存中(内存速度非常快),当请求时候,能够直接读取内存中的数据及时响应。

3.2 为什么使用缓存

​ 用缓存,主要有解决 高性能高并发减少数据库压力缓存本质就是将数据存储在内存中,当数据没有发生本质变化的时候,我们应尽量避免直接连接数据库进行查询,因为并发高时很可能会将数据库压塌,而是应去缓存中读取数据,只有缓存中未查找到时再去数据库中查询,这样就大大降低了数据库的读写次数,增加系统的性能和能提供的并发量

4.缓存的优缺点

4.1 优点

  • 加快了响应速度
  • 减少了对数据库的读操作,数据库的压力降低。

4.2 缺点

  • 内存容量相对硬盘小。
  • 缓存中的数据可能与数据库中数据不一致。
  • 因为内存断电就清空数据,存放到内存中的数据可能丢失。

5.Redis概念知识

5.1 什么是Redis

​ Redis 是一个高性能的 Key-Value 数据库,它是完全开源免费的,而且 Redis 是一个 NoSQL 类型数据库,是为了解决 高并发、高扩展,大数据存储 等一系列的问题而产生的数据库解决方案,是一个非关系型的数据库。但是,它也是不能替代关系型数据库,只能作为特定环境下的扩充。

5.2 为什么使用 Redis 作为缓存

  • 支持高可用: Redis 支持 master\slave 主\从机制、sentinal 哨兵模式、cluster 集群模式,这样大大保证了 Redis 运行的稳定和高可用行。
  • 支持多种数据结构: Redis 不仅仅支持简单的 Key/Value 类型的数据,同时还提供 list、set、zset、hash 等数据结构的存储。
  • 支持数据持久化: 可以将内存中的数据持久化在磁盘中,当宕机或者故障重启时,可以再次加载进如 Redis,从而不会或减少数据的丢失。
  • 有很多工具与插件对其支持: Redis 已经在业界广泛使用,已经是成为缓存的首选目标,所以很多语言和工具对其支持,我们只需要简单的操作就可以轻松使用。
    在这里插入图片描述

5.3 Redis支持的数据类型

Redis 支持的数据结构类型包括:

  • 字符串(string)
  • 哈希表(hash)
  • 列表(list)
  • 集合(set)
  • 有序集合(zset)

6.缓存后可能遇见的问题

6.1 缓存穿透

在这里插入图片描述
缓存穿透: 指查询一个一定不存在的数据,由于缓存是不命中时需要从数据库查询,查不到数据则不写入缓存,这将导致这个不存在的数据每次请求都要到数据库去查询,造成缓存穿透。

缓存穿透几种解决办法:

  • 缓存空值,在从 DB 查询对象为空时,也要将空值存入缓存,具体的值需要使用特殊的标识, 能和真正缓存的数据区分开,另外将其过期时间设为较短时间
  • 使用布隆过滤器,布隆过滤器能判断一个 key 一定不存在(不保证一定存在,因为布隆过滤器结构原因,不能删除,但是旧值可能被新值替换,而将旧值删除后它可能依旧判断其可能存在),在缓存的基础上,构建布隆过滤器数据结构,在布隆过滤器中存储对应的 key,如果存在,则说明 key 对应的值为空。

6.2 缓存击穿

在这里插入图片描述
缓存击穿: 某个 key 非常热点,访问非常频繁,处于集中式高并发访问的情况,当这个 key 在失效的瞬间,大量的请求就击穿了缓存,直接请求数据库,就像是在一道屏障上凿开了一个洞。

缓存击穿几种解决办法:

  • 设置二级缓存,或者设置热点缓存永不过期,需要根据实际情况进行配置。
  • 使用互斥锁,在执行过程中,如果缓存过期,那么先获取分布式锁,在执行从数据库中加载数据,如果找到数据就存入缓存,没有就继续该有的动作,在这个过程中能保证只有一个线程操作数据库,避免了对数据库的大量请求。

6.3 缓存雪崩

在这里插入图片描述
缓存雪崩: 当缓存服务器重启、或者大量缓存集中在某一个时间段失效,这样在失效的时候,也会给后端系统(比如DB)带来很大压力,造成数据库后端故障,从而引起应用服务器雪崩。

缓存雪崩几种解决办法:

  • 缓存组件设计高可用,缓存高可用是指,存储缓存的组件的高可用,能够防止单点故障、机器故障、机房宕机等一系列问题。例如 Redis sentinel 和 Redis Cluster,都实现了高可用。
  • 请求限流与服务熔断降级机制,限制服务请求次数,当服务不可用时快速熔断降级。
  • 设置缓存过期时间一定的随机分布,避免集中在同一时间缓存失效。
  • 定时更新缓存策略,对于实时性要求不高的数据,定时进行更新。

6.4 缓存一致性

使用缓存很大可能导致数据不一致问题,如下:

  • 更熟数据库成功 -> 更新缓存失败 -> 数据不一致
  • 更新缓存成功 -> 更新数据库失败 -> 数据不一致
  • 更新数据库成功 -> 淘汰缓存失败 -> 数据不一致
  • 汰缓存成功 -> 更新数据库失败 -> 查询缓存mis

所以使用缓存时候,应该结合实际情况,考虑缓存的数据是否有一致性需求。

7.SpringBoot结合Redis实现缓存

7.1 maven引入相关依赖

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

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

        <!--pool2-->
        <dependency>
            <groupId>org.apache.commons</groupId>
            <artifactId>commons-pool2</artifactId>
        </dependency>

其余的

  <!--mybatis-plus-->
        <dependency>
            <groupId>com.baomidou</groupId>
            <artifactId>mybatis-plus-boot-starter</artifactId>
            <version>3.3.1</version>
        </dependency>

        <!-- mybatis-plus代码生成器 -->
        <dependency>
            <groupId>com.baomidou</groupId>
            <artifactId>mybatis-plus-generator</artifactId>
            <version>3.3.1.tmp</version>
        </dependency>

        <!-- mybatisPlus Freemarker 模版引擎 -->
        <!--freemarker 作为 MyBatis-Plus 自动生成代码时作为模板使用-->
        <dependency>
            <groupId>org.freemarker</groupId>
            <artifactId>freemarker</artifactId>
        </dependency>

        <!-- mysql连接 -->
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <scope>runtime</scope>
        </dependency>

        <!--lang3-->
        <dependency>
            <groupId>org.apache.commons</groupId>
            <artifactId>commons-lang3</artifactId>
        </dependency>

        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>fastjson</artifactId>
            <version>1.2.54</version>
        </dependency>

7.2 配置Redis参数

7.2.1 Redis单机配置
#redis单点配置
spring:
  datasource:
    url: jdbc:mysql://localhost:3306/goods_system?useUnicode=true&useJDBCCompliantTimezoneShift=true&useLegacyDatetimeCode=false&serverTimezone=UTC&useSSL=true&characterEncoding=UTF-8
    username: root
    password: root
    driver-class-name: com.mysql.cj.jdbc.Driver
  redis:
    host: localhost             #redis地址
    port: 6379                  #redis端口
    database: 0                 #redis索引(0-15,默认为0)
    timeout: 1000               #redis连接超时时间
    lettuce:                    #使用lettuce连接池
      pool:
        max-active: 20          #连接池最大连接数(使用负值表示没有限制)
        max-wait: -1            #连接池最大阻塞等待时间(使用负值表示没有限制)
        min-idle: 0             #连接池中的最小空闲连接
        max-idle: 10            #连接池中的最大空闲连接
7.2.2 Redis哨兵配置
spring:
  sentinel:                   #哨兵配置
    master: "my-master"
    nodes: "192.168.10.261:6379,192.168.10.262:6379,192.168.10.263:6379"
  database: 0                 #redis索引(0-15,默认为0)
  timeout: 1000               #redis连接超时时间
  lettuce:                    #使用lettuce连接池
    pool:
      max-active: 20          #连接池最大连接数(使用负值表示没有限制)
      max-wait: -1            #连接池最大阻塞等待时间(使用负值表示没有限制)
      min-idle: 0             #连接池中的最小空闲连接
      max-idle: 10            #连接池中的最大空闲连接
7.2.3 Redis集群配置
#redis集群配置
spring:
  redis:
    cluster:
      max-redirects: 5
      nodes: "192.168.10.261:6379,192.168.10.262:6379,192.168.10.263:6379"
    database: 0                 #redis索引(0-15,默认为0)
    timeout: 1000               #redis连接超时时间
    lettuce:                    #使用lettuce连接池
      pool:
        max-active: 20          #连接池最大连接数(使用负值表示没有限制)
        max-wait: -1            #连接池最大阻塞等待时间(使用负值表示没有限制)
        min-idle: 0             #连接池中的最小空闲连接
        max-idle: 10            #连接池中的最大空闲连接

7.3 配置 Spring 缓存管理器

package com.example.springbootrediscache.config;

import org.springframework.cache.CacheManager;
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.serializer.GenericJackson2JsonRedisSerializer;
import org.springframework.data.redis.serializer.RedisSerializationContext;
import org.springframework.data.redis.serializer.StringRedisSerializer;

import java.lang.reflect.Method;
import java.time.Duration;

/**
 * @Author Emperor Kang
 * @ClassName RedisConfig
 * @Description redis配置类
 * @Date 2022/9/19 14:20
 * @Version 1.0
 * @Motto 让营地比你来时更干净
 */
@Configuration
public class RedisConfig {

    /**
     * 配置缓存管理器
     * @param factory  Redis 线程安全连接工厂
     * @return 缓存管理器
     */
    @Bean
    public CacheManager cacheManager(RedisConnectionFactory factory){
        // 生成两套默认配置,通过 Config 对象即可对缓存进行自定义配置
        RedisCacheConfiguration userCacheConfig = RedisCacheConfiguration.defaultCacheConfig()
                // 设置过期时间 10 分钟
                .entryTtl(Duration.ofMinutes(10))
                // 设置缓存前缀
                .prefixKeysWith("cache:user:")
                // 禁止缓存 null 值
                .disableCachingNullValues()
                // 设置 key 序列化
                .serializeKeysWith(keyPair())
                // 设置 value 序列化
                .serializeValuesWith(valuePair());

        RedisCacheConfiguration userInfoCacheConfig = RedisCacheConfiguration.defaultCacheConfig()
                // 设置过期时间 30 秒
                .entryTtl(Duration.ofSeconds(30))
                // 设置缓存前缀
                .prefixKeysWith("cache:user_info:")
                // 禁止缓存 null 值
                .disableCachingNullValues()
                // 设置 key 序列化
                .serializeKeysWith(keyPair())
                // 设置 value 序列化
                .serializeValuesWith(valuePair());

        // 返回 Redis 缓存管理器
        return RedisCacheManager.builder(factory)
                .withCacheConfiguration("user", userCacheConfig)
                .withCacheConfiguration("userInfo", userInfoCacheConfig)
                .build();
    }

    /**
     * 自定义Redis Key生产策略
     * 在使用是, 指定@Cacheable(cacheNames = "user", keyGenerator = "userKeyGenerator")
     * @return
     */
    @Bean(name = "userKeyGenerator")
    public KeyGenerator userKeyGenerator(){
        return new KeyGenerator() {
            @Override
            public Object generate(Object target, Method method, Object... params) {
                StringBuilder builder = new StringBuilder();
                //类名
                builder.append(target.getClass().getName());
                //方法名
                builder.append(method.getName());
                //参数,这里可以挑选用哪参数,不用哪些参数
                for (Object param : params) {
                    builder.append(param);
                }
                return builder.toString();
            }
        };
    }

    /**
     * 配置键序列化
     * @return StringRedisSerializer
     */
    private RedisSerializationContext.SerializationPair<String> keyPair() {
        return RedisSerializationContext.SerializationPair.fromSerializer(new StringRedisSerializer());
    }

    /**
     * 配置值序列化,使用 GenericJackson2JsonRedisSerializer 替换默认序列化
     * @return GenericJackson2JsonRedisSerializer
     */
    private RedisSerializationContext.SerializationPair<Object> valuePair() {
        return RedisSerializationContext.SerializationPair.fromSerializer(new GenericJackson2JsonRedisSerializer());
    }

}

7.4 在服务中使用 SpringCache 的注解

package com.example.springbootrediscache.service.impl;

import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.example.springbootrediscache.entity.User;
import com.example.springbootrediscache.mapper.UserMapper;
import com.example.springbootrediscache.service.IUserService;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cache.annotation.CacheConfig;
import org.springframework.cache.annotation.CacheEvict;
import org.springframework.cache.annotation.CachePut;
import org.springframework.cache.annotation.Cacheable;
import org.springframework.stereotype.Service;

/**
 * <p>
 * 用户表 服务实现类
 * </p>
 *
 * @author zkk
 * @since 2022-09-19
 */
@Service
@CacheConfig(cacheNames = "user")
public class UserServiceImpl extends ServiceImpl<UserMapper, User> implements IUserService {
    @Autowired
    private UserMapper userMapper;

    /**
     * 新增用户
     * @param user 账户
     */
    @Override
    public void addUser(User user) {
        userMapper.insert(user);
    }

    /**
     * 查询用户
     * @param username 用户名
     * @return
     */
    @Override
    @Cacheable(key = "#username")
    //@Cacheable(keyGenerator = "userKeyGenerator")
    public User getUserByUsername(String username) {
        QueryWrapper<User> objectQueryWrapper = new QueryWrapper<>();
        objectQueryWrapper.eq("name",username);
        return userMapper.selectOne(objectQueryWrapper);
    }

    /**
     * 更新用户
     * @param user 用户信息
     * @return
     */
    @Override
    @CachePut(key = "#user.name")
    public User updateUser(User user) {
        QueryWrapper<User> updateWrapper = new QueryWrapper<>();
        updateWrapper.eq("name",user.getName());
        //更新
        userMapper.update(user,updateWrapper);
        //将更新后的值返回
        QueryWrapper<User> objectQueryWrapper = new QueryWrapper<>();
        objectQueryWrapper.eq("name",user.getName());
        return userMapper.selectOne(objectQueryWrapper);
    }

    /**
     * 删除
     * @param username 用户名
     */
    @Override
    @CacheEvict(key = "#username")
    public void deleteByUsername(String username) {
        QueryWrapper<User> objectQueryWrapper = new QueryWrapper<>();
        objectQueryWrapper.eq("name",username);
        userMapper.delete(objectQueryWrapper);
    }
}

注解说明

  • @CacheConfig: 一般配置在类上,指定缓存名称,这个名称是和上面“置缓存管理器”中缓存名称的一致。
  • @Cacheable: 作用于方法上,用于对于方法返回结果进行缓存,如果已经存在该缓存,则直接从缓存中获取,缓存的key可以从入参中指定,缓存的 value 为方法返回值。
  • @CachePut: 作用于方法上,无论是否存在该缓存,每次都会重新添加缓存,缓存的key可以从入参中指定,缓存的value为方法返回值,常用作于更新。
  • @CacheEvict: 作用于方法上,用于清除缓存
  • @Caching: 作用于方法上,用于一次性设置多个缓存。

常用配置参数

  • value: 缓存管理器中配置的缓存的名称,这里可以理解为一个组的概念,缓存管理器中可以有多套缓存配置,每套都有一个名称,类似于组名,这个可以配置这个值,选择使用哪个缓存的名称,配置后就会应用那个缓存名称对应的配置。
  • key: 缓存的 key,可以为空,如果指定要按照 SpEL 表达式编写,如果不指定,则缺省按照方法的所有参数进行组合。
  • condition: 缓存的条件,可以为空,使用 SpEL 编写,返回 true 或者 false,只有为 true 才进行缓存。
  • unless: 不缓存的条件,和 condition 一样,也是 SpEL 编写,返回 true 或者 false,为 true 时则不进行缓存。

实体类

package com.example.springbootrediscache.entity;

import java.time.LocalDateTime;
import java.io.Serializable;
import lombok.EqualsAndHashCode;
import lombok.experimental.Accessors;

/**
 * <p>
 * 用户表
 * </p>
 *
 * @author zkk
 * @since 2022-09-19
 */
@EqualsAndHashCode(callSuper = false)
@Accessors(chain = true)
public class User implements Serializable {

    private static final long serialVersionUID = 1L;

    /**
     * 账号
     */
    private String account;

    /**
     * 密码
     */
    private String password;

    /**
     * 姓名
     */
    private String name;

    /**
     * 电话
     */
    private String phone;

    /**
     * 创建时间
     */
    private LocalDateTime createTime;

    /**
     * 创建人
     */
    private Long createUser;

    /**
     * 状态(0--正常1--冻结)
     */
    private Boolean status;

    /**
     * 删除状态(0,正常,1已删除)
     */
    private Boolean delFlag;

    public String getAccount() {
        return account;
    }

    public void setAccount(String account) {
        this.account = account;
    }

    public String getPassword() {
        return password;
    }

    public void setPassword(String password) {
        this.password = password;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public String getPhone() {
        return phone;
    }

    public void setPhone(String phone) {
        this.phone = phone;
    }

    public LocalDateTime getCreateTime() {
        return createTime;
    }

    public void setCreateTime(LocalDateTime createTime) {
        this.createTime = createTime;
    }

    public Long getCreateUser() {
        return createUser;
    }

    public void setCreateUser(Long createUser) {
        this.createUser = createUser;
    }

    public Boolean getStatus() {
        return status;
    }

    public void setStatus(Boolean status) {
        this.status = status;
    }

    public Boolean getDelFlag() {
        return delFlag;
    }

    public void setDelFlag(Boolean delFlag) {
        this.delFlag = delFlag;
    }
}

controller

package com.example.springbootrediscache.controller;


import com.example.springbootrediscache.entity.User;
import com.example.springbootrediscache.service.IUserService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;

/**
 * <p>
 * 用户表 前端控制器
 * </p>
 *
 * @author zkk
 * @since 2022-09-19
 */
@RestController
@RequestMapping("/sys/user")
public class UserController {

    @Autowired
    private IUserService userService;

    /**
     * 新增
     * @param user
     * @return
     */
    @PostMapping("/add")
    public Object addUser(@RequestBody User user){
        userService.addUser(user);
        return true;
    }

    /**
     * 查询
     * @param username
     * @return
     */
    @GetMapping("/get")
    public Object getUserByUsername(@RequestParam String username){
        return userService.getUserByUsername(username);
    }

    /**
     * 修改
     * @param user
     * @return
     */
    @PostMapping("/update")
    public Object updateUser(@RequestBody User user){
        return userService.updateUser(user);
    }

    /**
     * 删除
     * @param username
     * @return
     */
    @PostMapping("/delete")
    public Object deleteByUsername(@RequestParam("username") String username){
        userService.deleteByUsername(username);
        return true;
    }
}

代码生成器

package com.example.springbootrediscache.generator;

import com.baomidou.mybatisplus.core.exceptions.MybatisPlusException;
import com.baomidou.mybatisplus.core.toolkit.StringPool;
import com.baomidou.mybatisplus.generator.AutoGenerator;
import com.baomidou.mybatisplus.generator.InjectionConfig;
import com.baomidou.mybatisplus.generator.config.*;
import com.baomidou.mybatisplus.generator.config.po.TableInfo;
import com.baomidou.mybatisplus.generator.config.rules.NamingStrategy;
import com.baomidou.mybatisplus.generator.engine.FreemarkerTemplateEngine;
import org.apache.commons.lang3.StringUtils;

import java.util.ArrayList;
import java.util.List;
import java.util.Scanner;

// 演示例子,执行 main 方法控制台输入模块表名回车自动生成对应项目目录中
public class CodeGenerator {

    /**
     * <p>
     * 读取控制台内容
     * </p>
     */
    public static String scanner(String tip) {
        Scanner scanner = new Scanner(System.in);
        StringBuilder help = new StringBuilder();
        help.append("请输入" + tip + ":");
        System.out.println(help.toString());
        if (scanner.hasNext()) {
            String ipt = scanner.next();
            if (StringUtils.isNotBlank(ipt)) {
                return ipt;
            }
        }
        throw new MybatisPlusException("请输入正确的" + tip + "!");
    }

    public static void main(String[] args) {
        // 代码生成器
        AutoGenerator mpg = new AutoGenerator();

        // 全局配置
        GlobalConfig gc = new GlobalConfig();
        String projectPath = System.getProperty("user.dir");
        // gc.setOutputDir(projectPath + "/src/main/java");
        gc.setOutputDir("D:\\work_space\\2022\\spring_cloud_all\\SpringBootRedisCache\\src\\main\\java");
        gc.setAuthor("zkk");
        gc.setOpen(false);
        // gc.setSwagger2(true); 实体属性 Swagger2 注解
        mpg.setGlobalConfig(gc);

        // 数据源配置
        DataSourceConfig dsc = new DataSourceConfig();
        dsc.setUrl("jdbc:mysql://localhost:3306/goods_system?useUnicode=true&useJDBCCompliantTimezoneShift=true&useLegacyDatetimeCode=false&serverTimezone=UTC&useSSL=true&characterEncoding=UTF-8");
        // dsc.setSchemaName("public");
        dsc.setDriverName("com.mysql.cj.jdbc.Driver");
        dsc.setUsername("root");
        dsc.setPassword("root");
        mpg.setDataSource(dsc);

        // 包配置
        PackageConfig pc = new PackageConfig();
        pc.setModuleName(scanner("模块名"));
        pc.setParent("com.example.springbootrediscache");
        mpg.setPackageInfo(pc);

        // 自定义配置
        InjectionConfig cfg = new InjectionConfig() {
            @Override
            public void initMap() {
                // to do nothing
            }
        };

        // 如果模板引擎是 freemarker
        String templatePath = "/templates/mapper.xml.ftl";
        // 如果模板引擎是 velocity
        // String templatePath = "/templates/mapper.xml.vm";

        // 自定义输出配置
        List<FileOutConfig> focList = new ArrayList<>();
        // 自定义配置会被优先输出
        focList.add(new FileOutConfig(templatePath) {
            @Override
            public String outputFile(TableInfo tableInfo) {
                // 自定义输出文件名 , 如果你 Entity 设置了前后缀、此处注意 xml 的名称会跟着发生变化!!
                return "D:\\work_space\\2022\\spring_cloud_all\\SpringBootRedisCache\\src\\main\\resources\\mapper\\" + pc.getModuleName()
                        + "/" + tableInfo.getEntityName() + "Mapper" + StringPool.DOT_XML;
            }
        });
        /*
        cfg.setFileCreate(new IFileCreate() {
            @Override
            public boolean isCreate(ConfigBuilder configBuilder, FileType fileType, String filePath) {
                // 判断自定义文件夹是否需要创建
                checkDir("调用默认方法创建的目录,自定义目录用");
                if (fileType == FileType.MAPPER) {
                    // 已经生成 mapper 文件判断存在,不想重新生成返回 false
                    return !new File(filePath).exists();
                }
                // 允许生成模板文件
                return true;
            }
        });
        */
        cfg.setFileOutConfigList(focList);
        mpg.setCfg(cfg);

        // 配置模板
        TemplateConfig templateConfig = new TemplateConfig();

        // 配置自定义输出模板
        //指定自定义模板路径,注意不要带上.ftl/.vm, 会根据使用的模板引擎自动识别
        // templateConfig.setEntity("templates/entity2.java");
        // templateConfig.setService();
        // templateConfig.setController();

        templateConfig.setXml(null);
        mpg.setTemplate(templateConfig);

        // 策略配置
        StrategyConfig strategy = new StrategyConfig();
        strategy.setNaming(NamingStrategy.underline_to_camel);
        strategy.setColumnNaming(NamingStrategy.underline_to_camel);
        //strategy.setSuperEntityClass("你自己的父类实体,没有就不用设置!");
        strategy.setEntityLombokModel(true);
        strategy.setRestControllerStyle(true);
        // 公共父类
        //strategy.setSuperControllerClass("你自己的父类控制器,没有就不用设置!");
        // 写于父类中的公共字段
        strategy.setSuperEntityColumns("id");
        strategy.setInclude(scanner("表名,多个英文逗号分割").split(","));
        strategy.setControllerMappingHyphenStyle(true);
        strategy.setTablePrefix(pc.getModuleName() + "_");
        mpg.setStrategy(strategy);
        mpg.setTemplateEngine(new FreemarkerTemplateEngine());
        mpg.execute();
    }

}

7.5 启动类添加开启缓存注解

package com.example.springbootrediscache;

import org.mybatis.spring.annotation.MapperScan;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cache.annotation.EnableCaching;

@SpringBootApplication
@MapperScan("com.example.springbootrediscache.mapper")
@EnableCaching
public class SpringBootRedisCacheApplication {

    public static void main(String[] args) {
        SpringApplication.run(SpringBootRedisCacheApplication.class, args);
    }

}

7.6 测试

在这里插入图片描述

8.自定义KeyGenerator方式

8.1 概述

​ SpringBoot 使用 @Cacheable 可以方便的管理缓存数据,在不指定 key 属性的情况下,默认使用 SimpleKeyGenerator 生成 key。除此之外,我们也可以自定义实现 KeyGenerator 接口,生成自己的 key 名称策略

8.2 MySimpleKey 类

MySimpleKey类的作用是存放参数数据,必须实现equals、hashCode。如果需要自定义key格式,同样需要实现toString接口,下面的例子是把参数用逗号分隔。

public class MySimpleKey implements Serializable {
    public static final MySimpleKey EMPTY = new MySimpleKey(new Object[0]);
    private final Object[] params;
    private transient int hashCode;
    public MySimpleKey(Object... elements) {
        Assert.notNull(elements, "Elements must not be null");
        this.params = (Object[])elements.clone();
        this.hashCode = Arrays.deepHashCode(this.params);
    }
    public boolean equals(@Nullable Object other) {
        return this == other || other instanceof MySimpleKey && Arrays.deepEquals(this.params, ((MySimpleKey)other).params);
    }
    public final int hashCode() {
        return this.hashCode;
    }
    public String toString() {
        return StringUtils.arrayToCommaDelimitedString(this.params);
    }
}

8.3 MyKeyGenerator 类

MyKeyGenerator 实现 KeyGenerator 的接口,里面只有一个 generate 方法

public class MyKeyGenerator implements KeyGenerator {
    @Override
    public Object generate(Object o, Method method, Object... objects) {
        if (objects.length == 0) {
            return MySimpleKey.EMPTY;
        } else {
            if (objects.length == 1) {
                Object param = objects[0];
                if (param != null && !param.getClass().isArray()) {
                    return param;
                }
            }
            return new MySimpleKey(objects);
        }
    }
}

定义MyKeyGenerator Bean:

@Component
public class MyRedisConf {
    @Bean
    public MyKeyGenerator myKeyGenerator(){
        return new MyKeyGenerator();
    }
}

8.4 配置keyGenerator

在 @Cacheable 配置 keyGenerator 属性,值就是前面配置的Bean名称

    @Override
    @Cacheable(value = {"REDIS:GETSTRING3"}, keyGenerator = "myKeyGenerator")
    public String getString3(String tag, String name) {
        return tag + " " + name;
    }

9.Spring-Cache key设置

9.1 第一种方式:手动设置

为了便于key的不重复,我们可以手动设置key有类名、方法名、参数等组合

名字位置描述示例
methodNameroot object当前被调用的方法名#root.methodName
methodroot object当前被调用的方法#root.method .name
targetroot object当前被调用的目标对象#root.target
targetClassroot object当前被调用的目标对象类#root.targetClass
argsroot object当前被调用的方法的参数列表#root.args[0]
cachesroot object当前方法调用使用的缓存列表#root.caches[0].name
argument nameevaluation context方法参数的名字,可以直接#参数名,也可以使用#p0或#a0的形式,0代表参数的索引#iban、#a0、#p0
resultevaluation context方法执行后的返回值#result
key = "#root.targetClass.simpleName+':'+#root.methodName+':'+#param"

9.2 第二种方式:自定义keyGenerator

自定义CacheKeyGenerator 实现KeyGenerator

public class CacheKeyGenerator implements KeyGenerator {
    /**
     * (非 Javadoc)
     * <p>
     * Title: generate
     * </p>
     * 
     * @param target
     * @param method
     * @param params
     * @return
     * @see org.springframework.cache.interceptor.KeyGenerator#generate(java.lang.Object,
     *      java.lang.reflect.Method, java.lang.Object[])
     */
    @Override
    public Object generate(Object target, Method method, Object... params) {
        StringBuilder key = new StringBuilder();
        key.append(target.getClass().getSimpleName()).append(":").append(method.getName()).append(":");
        if (params.length == 0) {
            return key.toString();
        }
        for (int i = 0; i < params.length; i++) {
            Object param = params[i];
            if (param == null || param instanceof LogableParam) {
                del(key);
            } else if (ClassUtils.isPrimitiveArray(param.getClass())) {
                int length = Array.getLength(param);
                for (int j = 0; j < length; j++) {
                    key.append(Array.get(param, j));
                    key.append(',');
                }
            } else if (ClassUtils.isPrimitiveOrWrapper(param.getClass()) || param instanceof String) {
                key.append(param);
            } else {
                key.append(param.toString());
            }
            key.append('-');
        }
        del(key);
        return key.toString();
    }
    private StringBuilder del(StringBuilder stringBuilder) {
        if (stringBuilder.toString().endsWith("-")) {
            stringBuilder.deleteCharAt(stringBuilder.length() - 1);
        }
        return stringBuilder;
    }
}

在之前的代码中有相应的自定义KeyGenerator使用,下面仅供参

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值