SpringBoot+Redis+@Cacheable实现缓存功能

时间:2023-01-13 10:00:53

一、pom文件加入Redis与cache的依赖和yml配置

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

        <!-- 如果需要集成redis,需要再加入redis包 -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-redis</artifactId>
        </dependency>
        <!-- spring2.X集成redis所需common-pool2-->
        <dependency>
            <groupId>org.apache.commons</groupId>
            <artifactId>commons-pool2</artifactId>
        </dependency>

        <!-- 数据库驱动 -->
        <!-- 如果是springboot项目可以不用自己指定版本号,spring boot给我们定义了 -->
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
           <!-- <version>5.1.47</version> -->
        </dependency>
        <!-- mybatis-plus -->
        <dependency>
            <groupId>com.baomidou</groupId>
            <artifactId>mybatis-plus-boot-starter</artifactId>
            <version>3.1.1</version>
        </dependency>
        <!--spring-boot起步依赖-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter</artifactId>
        </dependency>
        <!-- lombok -->
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
        </dependency>
server:
  port: 9003
spring:
  datasource:
    username: root
    password: root1234
    url: jdbc:mysql://localhost:3306/test2?useUnicode=true&characterEncoding=utf-8&useSSL=false&serverTimezone=UTC
    driver-class-name: com.mysql.jdbc.Driver
  redis:
    host: 127.0.0.1
    port: 6379
  cache:
    type: redis
    redis:
      # 缓存超时时间(毫秒ms)
      time-to-live: 60000
      # 是否缓存空值
      cache-null-values: true
# mybatis-plu配置
mybatis-plus:
  configuration:
    # map-underscore-to-camel-case为true可以将数据库的带下划线“”给去掉然后映射到实体类的属性上去。
    # sql执行的日志(包含sql操作的参数日志)
    log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
  #指定xml映射位置
  mapper-locations: classpath*:mapper/*.xml
  #指是否对MyBatis xml 配置文件进行存在检查/默认值为false
  check-config-location: true
  #设置别名包扫描路径,通过该属性可以给包中的类注册别名,多个包以逗号分隔
  type-aliases-package: com.it.pojo

二、EnableCaching允许使用注解进行缓存

/**
 * 主启动类
 * EnableCaching允许使用注解进行缓存
 * @author shaohua
 */
@EnableCaching
@SpringBootApplication
@MapperScan(basePackages = "com.it.mapper")
public class Application {
    public static void main(String[] args) {
        SpringApplication.run(Application.class, args);
    }
}

三、Redis配置

@Configuration
public class RedisConfig {
    /**
     * 注册自定义RedisCacheConfiguration组件,解决@Cacheable @Cacheput注解在向Redis中保存的Value是java序列化乱码的问题
     */
    @Bean
    public RedisCacheConfiguration redisCacheConfiguration() {
        RedisCacheConfiguration config = RedisCacheConfiguration.defaultCacheConfig();
        config.entryTtl(Duration.ofSeconds(30));
        config=config.serializeValuesWith(RedisSerializationContext.SerializationPair.fromSerializer(RedisSerializer.json()));
        return config;
    }
}

四、业务逻辑

1.UserController

/**
 * 用户控制层
 *
 * @author hua
 */
@RestController
@RequestMapping(value = "/user")
public class UserController {
    private final UserService userService;

    public UserController (UserService userService) {
        this.userService = userService;
    }

    @PostMapping("/saveUser")
    public User saveUser(@RequestBody User user) {
        return userService.insert(user);
    }

    @GetMapping(value = "/{userId}")
    public ResponseEntity<User> getUser(@PathVariable Integer userId) {
        User user = userService.findUser(userId);
        HttpStatus status = user == null ? HttpStatus.NOT_FOUND: HttpStatus.OK;
        return new ResponseEntity<>(user, status);
    }

    @DeleteMapping(value = "/{userId}")
    public ResponseEntity deleteUser(@PathVariable Integer userId) {
        userService.delete(userId);
        return new ResponseEntity<>(HttpStatus.NO_CONTENT);
    }
}

2.UserService

**
 * UserService
 *
 * @author hua
 */
public interface UserService extends IService<User>{

    /**
     * save use, put redis cache
     * @param user user data
     * @return saved user data
     */
     User insert(User user);

    /**
     * find user by id,redis cacheable
     * @param userId user id
     * @return if exist return the user, else return null
     */
     User findUser(Integer userId);

    /**
     * delete user by id, and remove redis cache
     * @param userId user id
     */
     void delete(Integer userId);
}

3.UserServiceImpl

/**
 * 实现类
 *
 * @author hua
 */
@Service
public class UserServiceImpl extends ServiceImpl<UserMapper, User> implements UserService {

    @Autowired
    private RedisTemplate redisTemplate;

    /**
     * 存储数据库的同时,存储到缓存
     */
    @Override
    @CachePut(value = "user", key = "#result.id", unless = "#result eq null")
    public User insert(User user) {
        this.save(user);
        return user;
    }

    /**
     * 优先从redis缓存中读取(发现控制台并未执行sql)。如果缓存过期,则重新查数据,重新写入缓存
     */
    @Override
    @Cacheable(value = "user", key = "#userId", unless = "#result eq null")
    public User findUser(Integer userId) {
        User user = this.getById(userId);
        return user;
    }

    /**
     * 删除数据时同样清除缓存
     */
    @Override
    @CacheEvict(value = "user", key = "#userId")
    public void delete(Integer userId) {
        this.removeById(userId);
    }
}

4.AdminServiceImpl

/**
 * 用户实现类
 *
 * @author hua
 */
@Service
public class AdminServiceImpl implements AdminService {

    @Autowired
    UserService userService;

    /**
     * 保存String类型的数据,
     */
    @Override
    @CachePut(value = "张三")
    public void insert1() {
    }

    /**
     * 保存对象 形参方式保存
     */
    @Override
    @CachePut(value = "user", key = "#user.id", unless = "#user eq null")
    public void insert2(User user) {
    }
    // key = "#user.id": 取形参user中的id值, 作为key名称
    // value = "user": 形参user, 作为value值
    // unless = "#user eq null :判断 形参user对象是空的,若unless的结果为true,则(方法执行后的功能)不生效;若unless的结果为false,则(方法执行后的)功能生效。
    // 生效则会将形参user对象以json格式字符串保存到redis

    /**
     * 保存对象 返回值方式保存
     */
    @Override
    @CachePut(value = "user", key = "#result.id", unless = "#result eq null")
    public User insert3() {
        LambdaQueryWrapper<User> wrapper = Wrappers.<User>lambdaQuery();
        wrapper.eq(User::getName,"小花花");
        User user = userService.getOne(wrapper);
        return user;
    }
    // key = "#result.id": 取返回值user中的id值, 作为key名称
    // value = "user": 取返回值user, 作为value值
    // unless = "#result eq null: 判断 方法返回值是空的, 若unless的结果为true,则(方法执行后的功能)不生效;若unless的结果为false,则(方法执行后的)功能生效。
    // 生效则会将返回值user对象以json格式字符串保存到redis

    @Override
    @Cacheable(value = CacheTimes.D1, key = "#root.methodName", unless = "#result == null || #result.size() < 1", condition = "#skip != null")
    public List<String> getList(String skip) {
        return Arrays.stream(UUID.randomUUID().toString().split("-")).collect(Collectors.toList());
    }

    /*
        说明:@Cacheable注解有三个参数,value是必须的,还有key和condition。
        第一个参数value:指明了缓存将被存到什么地方.上面的代码保证findPersonByName的返回值Person对象将被存储在"employee"中。
        第二个参数key:指定key,任何存储在缓存中的数据为了高速访问都需要一个key。Spring默认使用被@Cacheable注解的方法的签名来作为key, 当然你可以重写key, 自定义key可以使用SpEL表达式。
        在findEmployee()的注解中"#name"是一个SpEL表达式,他将使用findPersonByName()方法中的name参数作为key。
        第三个参数condition(可选):
        功能1:缓存的条件, 决定是否缓存。@Cacheable的最后一个参数是condition(可选),同样的,也是引用一个SpEL表达式。但是这个参数将指明方法的返回结果是否被缓存。
        功能2:condition则判定了是否走换成逻辑。如果age<25,即condition是false,就不去读取缓存,而是直接执行当前目标方法,返回结果。
        上面的例子中,只有年龄小于25的时候才被缓存
     */
    @Cacheable(value = "employee", key = "#name" ,condition = "#age < 25")
    public User findEmployee(String name, int age) {
        return new User();
    }
}

5.@Cacheable和@CachePut区别

相同点
都是Spring的缓存注解。
不同点
@Cacheable:只会执行一次,当标记在一个方法上时表示该方法是支持缓存的,Spring会在其被调用后将其返回值缓存起来,以保证下次利用同样的参数来执行该方法时可以直接从缓存中获取结果。
@CachePut标注的方法在执行前不会去检查缓存中是否存在之前执行过的结果,而是每次都会执行该方法,并将执行结果以键值对的形式存入指定的缓存中。

五、测试

1.执行saveUser方法

利用@CachePut,存储数据库的同时存储到缓存
SpringBoot+Redis+@Cacheable实现缓存功能
第一次和第二次执行saveUser方法,发现控制台打印了SQL语句,说明执行了SQL存储到MySQL数据了。
SpringBoot+Redis+@Cacheable实现缓存功能
SpringBoot+Redis+@Cacheable实现缓存功能
观察MySQL数据存储情况,存储了2条数据,主键id自增。
SpringBoot+Redis+@Cacheable实现缓存功能
SpringBoot+Redis+@Cacheable实现缓存功能
观察Redis数据存储情况,存储了2条数据,主键id自增。
SpringBoot+Redis+@Cacheable实现缓存功能
SpringBoot+Redis+@Cacheable实现缓存功能

2.执行getUser方法

利用@Cacheable,优先从redis缓存中读取(发现控制台并未执行sql)。如果缓存过期,则重新查数据,重新写入缓存。
发现执行多次,控制台均未打印SQL,说明均是从Redis读取数据(前提是Redis缓存未过期)。
SpringBoot+Redis+@Cacheable实现缓存功能
SpringBoot+Redis+@Cacheable实现缓存功能

六、项目目录及源码下载

SpringBoot+Redis+@Cacheable实现缓存功能
源码下载,欢迎Star哦~ springboot-cacheable

参考资料
Spring 缓存的使用
SpringBoot通过@Cacheable注解实现缓存功能
Springboot+redis+@Cacheable实现缓存
SpringBoot2使用@Cacheable注解时,Redis中保存的Value为java序列化乱码问题的解决办法及源码分析
@Cacheable和@CachePut区别