zl程序教程

您现在的位置是:首页 >  后端

当前栏目

springboot-cache的简单使用

SpringBoot 使用 简单 cache
2023-06-13 09:14:15 时间

springboot-cache 的简单使用

springboot-cache介绍

一、前言

Spring Cache 对 Cahce 进行了抽象,提供了 @Cacheable、@CachePut、@CacheEvict 等注解。Spring Boot 应用基于 Spring Cache,既提供了基于内存实现的缓存管理器,可以用于单体应用系统,也集成了 Redis 等缓存服务器,可以用于大型系统或者分布式系统。

二、关于 Cache

应用系统需要通过 Cache 来缓存不经常改变的数据以提高系统性能和增加系统吞吐量,避免直接访问数据库等低速的存储系统。缓存的数据通常存放在访问速度更快的内存中或者是低延迟存取的存储器、服务器上。缓存系统存储大都是不经常改变的业务数据,如用户权限、字典数据、配置信息等等。

springboot-cache的注解讲解

1、@Cacheable注解

@Cacheable注解的作用是Spring在调用该方法之前,首先在缓存中查找方法的返回值,默认的key是根据参数值生成,如果存在,直接返回缓存中的值,否则执行该方法,并将返回值保存到缓存中

@Cacheable运行流程:

1.方法运行之前,先去查询Cache(缓存组件),按照cacheNames指定的名字获取;

​ (CacheManager先获取相应的缓存),第一次获取缓存如果没有Cache组件会自动创建。

2.去Cache中查找缓存的内容,使用一个key,默认就是方法的参数值;

  • ​ key是按照某种策略生成的;默认是使用keyGenerator生成的,
  • Spring默认加载的是SimpleCacheManage,SimpleKeyGenerator生成key的默认策略是:
  • ​ 如果没有参数;key=new SimpleKey()
  • ​ 如果有一个参数:key=参数的值
  • ​ 如果有多个参数:key=new SimpleKey(params)

3.没有查到缓存就调用目标方法;

4.将目标方法返回的结果,放进缓存中

@Cacheable属性说明:

1.acheNames/value:该属性值必须提供,指定缓存组件的名字,将方法的返回结果放在哪个缓存中,是数组的 方式,可以指定多个缓存;

如:cacheNames = "product"或者cacheNames = {“product1”,“product2”}

2.key:缓存数据使用的key,不指定key则默认是使用方法参数的值该属性值支持SpEL表达式

3.cacheManager:指定缓存管理器;或者cacheResolver指定获取解析器

4.condition:指定符合条件的情况下才缓存 condition = “#a0=1” 当方法中第一个参数=1的时候才进行缓存

5.unless:否定缓存;当unless指定的条件为true,方法的返回值就不会被缓存;可以获取到结果进行判断

​ unless = “#result == null”

​ unless = “#a0==2”:如果第一个参数的值是2,结果不缓存;

6.sync:是否使用异步模式

使用样例:

    /*
     * @Author crush
     * 原理:
     * 运行流程:
     * @Cacheable
     * 1 方法执行之前  先去查询Cache (缓存组件) 按照cacheNames 指定的名字获取
     * 先去获取相应的缓存  第一次获取缓存如果没有 Cache组件会自动创建
     * 2 去Cache 中查找缓存的内容 使用一个key 默认就是方法的参数
     * 3 没有查到就返回结果
     * 4 将目标方法返回的结果 放进缓存中
     *
     * @Cacheable 标注的方法 执行之前 先来检查缓存中有没有这个数据 默认按照参数的值作为key去查询缓存
     * 如果没有就运行方法并将结果放入缓存
     * 属性:
     * cacheNames :指定缓存组的名字
     * key:缓存数据用的key
     * keyGenerator : 也可以用这个自定义key的值
     * condition: 指定符合条件下菜缓存 condition="#id>0"
     * // #a0> 1 表示第一个参数大与1
     * unless:否定缓存
     *      unless="#a0==2" 如果第一个参数是2 就不缓存
     * sync:是否使用异步模式
     **/
    @Cacheable(cacheNames = "userInfo",key = "#user_id",condition = "#id=1") // #a0> 1 表示第一个参数大与1
    public UserInfo selectByUserId(String user_id) {
        System.out.println("根据id查询用户");
        return userInfoMapper.selectByUserId(user_id);
    }

2、自定义Key生成器

除了通过SPEL表达式之外,还可以通过自定义key生成器的方式,Spring缓存模块提供了org.springframework.cache.interceptor.KeyGenerator接口用于缓存key的生成声明,因此我们可以自定义一个MyKeyGenerator类并实现了KeyGenerator接口 ,使用如下:

import org.springframework.cache.interceptor.KeyGenerator;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;


import java.lang.reflect.Method;
import java.util.Arrays;

@Configuration
public class MyCacheConfig {

    @Bean
    public KeyGenerator myKeyGenerator(){
        return new KeyGenerator(){
            public Object generate(Object target, Method method, Object... params) {
                return method.getName()+"["+ Arrays.asList(params).toString()+"]";
            }
        };
    }
}

该方法测试用,关于缓存key的生成方式,网上有很多种策略。

使用时只需要修改注解的key属性即可:

@Cacheable(cacheNames = "product",keyGenerator = "myKeyGenerator")

3、 @CachePut

@CachePut注解的作用简单的说一句话:既调用方法,又缓存数据。

	/*
     * @Author crush
     * @CachePut 即调用方法 有更新缓存数据
     * 运行时机:
     *  1 先调用目标方法
     *  2 将目标方法的结果缓存起来
     *  测试:
     *   1 先去查询
     *   2  然后再更新
     *   3  然后再执行查询 发现还是更新之前的数据
     *   4 发现他们缓存的时候 使用的key 是不一样的  这个时候要使用相同的key
     *
     **/
@CachePut(/*value = "userInfo", */key = "#userInfo.user_id")
public UserInfo updateUser(UserInfo userInfo) {
    System.out.println("更新用户信息");
    userInfoMapper.updateUser(userInfo);
    return userInfo;
}

4、@CacheEvict注解

该注解的作用根据指定的key或者是allEntries属性值移除缓存中特性的键值对。

/*
* @Author crush
* @CacheEvict 缓存清除
*  可以通过key 来指定 要清除的值.
* allEntries =true 指定清除这个缓存中的所有数据
* beforeInvocation =false
*    默认代表缓存清除操作是在方法执行之后清除 如果出现异常 缓存就不会清除
*
* beforeInvocation =true
*     缓存的清除是否在方法之前执行  无论方法是否出现异常 缓存都清除
**/

案例:

@CacheEvict(/*value = "userInfo",*/ key = "#user_id")
public void delete(Integer user_id) {
    System.out.println("删除了" + user_id + "用户");
}

5、@Caching注解

该注解是一个分组注解,作用是可以同时应用多个其他注解,该注解提供了3个属性cacheable,put,evict分别用于组合@Cacheable、@CachePut、@CacheEvict三个注解

/*
* @Author crush
* 这个就是一个组合的
**/
@Caching(
    cacheable = {
        @Cacheable(/*value = "userInfo",*/key = "#user_name")
    },
    put = {
        @CachePut(/*value = "userInfo",*/key = "#result.user_id"),
        @CachePut(/*value = "userInfo",*/key = "#result.user_password")
    }
)
public UserInfo selectByUserName(String user_name) {
    return userInfoMapper.selectByUserName(user_name);
}

springboot-cache 使用

步骤:

1、环境搭建:

DROP TABLE IF EXISTS `t_user`;
CREATE TABLE `t_user`  (
  `user_id` varchar(12) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL COMMENT '编号',
  `user_name` varchar(50) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL COMMENT '用户名',
  `user_password` varchar(12) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL COMMENT '密码',
  PRIMARY KEY (`user_id`) USING BTREE
) ENGINE = InnoDB CHARACTER SET = utf8 COLLATE = utf8_general_ci ROW_FORMAT = Dynamic;

INSERT INTO `t_user` VALUES ('1', 'crush', '456789');
INSERT INTO `t_user` VALUES ('2', 'hehe', '456789');

2、导入pom依赖

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

3、项目结构

4、pojo层UserInfo类

import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import lombok.experimental.Accessors;

import java.io.Serializable;

/**
 * @version 1.0
 * @author: crush
 * @date: 2021-04-10 16:21
 */
@Data
@AllArgsConstructor
@NoArgsConstructor
@Accessors(chain = true) 
//  lombok 注解 写习惯了  写了这个注解可以链式写
// UserInfo userInfo = new UserInfo();
//     userInfo.setUser_id("123").setUser_name("crush").setUser_password("abcdefg");
public class UserInfo implements Serializable {
    private String user_id;
    private String user_name;
    private String user_password;
}

5、yaml配置文件:

server:
  port: 8787
spring:
  datasource:
    type: com.alibaba.druid.pool.DruidDataSource  #ali的数据源
    driver-class-name: com.mysql.cj.jdbc.Driver
    druid:
      username: root
      password: 123456
      url: jdbc:mysql://localhost:3306/studentdb?serverTimezone=UTC&useSSL=false&characterEncoding=utf8&serverTimezone=GMT
mybatis:
  type-aliases-package: com.crush.pojo
  mapper-locations: classpath:mapper/*.xml
#  configuration:
#    use-actual-param-name: true 驼峰命名

6、mapper层:

@Mapper
public interface UserInfoMapper {

    @Select("select * from t_user where user_id=#{user_id}")
    UserInfo selectByUserId(String user_id);


    @Update("UPDATE t_user SET user_name=#{user_name},user_password=#{user_password} WHERE user_id=#{user_id}")
    int updateUser(UserInfo userInfo);


    @Select("select * from t_user where user_name=#{user_name}")
    UserInfo selectByUserName(String user_name);
}

7、service和serviceImpl

package com.crush.service;

import com.crush.mapper.UserInfoMapper;
import com.crush.pojo.UserInfo;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cache.annotation.*;
import org.springframework.stereotype.Service;

/**
 * @version 1.0
 * @author: crush
 * @date: 2021-04-10 16:52
 */
@CacheConfig(cacheNames = "userInfo") /* 在类上 可以定义一个公共的配置*/
@Service
public class UserInfoService {

    @Autowired
    UserInfoMapper userInfoMapper;


    /*
     * @Author crush
     * 原理:
     * 1 自动配置类 :CacheAutoConfiguration
     * 2 缓存配置类
     * 3 哪个配置类生效
     * 4 给容器内注册了一个CacheManager:ConcurrentMapCacheManager
     * 5 可以获取何创建ConcurrentMapCacheManager 类型的缓存组件 他的作用就是讲数据保存在ConcureentMapzhp
     * 运行流程:
     * @Cacheable
     * 1 方法执行之前  先去查询Cache (缓存组件) 按照cacheNames 指定的名字获取
     * 先去获取相应的缓存  第一次获取缓存如果没有 Cache组件会自动创建
     * 2 去Cache 中查找缓存的内容 使用一个key 默认就是方法的参数
     * 3 没有查到就返回结果
     * 4 将目标方法返回的结果 放进缓存中
     *
     * @Cacheable 标注的方法 执行之前 先来检查缓存中有没有这个数据 默认按照参数的值作为key去查询缓存
     * 如果没有就运行方法并将结果放入缓存
     *
     * 属性:
     * cacheNames :指定缓存组的名字
     * key:缓存数据用的key  默认的key是根据参数值生成,如果存在 直接返回缓存中的值,否则执行该方法,并将返回值保存到缓存中
     * keyGenerator : 也可以用这个自定义key的值
     *
     * condition: 指定符合条件下菜缓存 condition="#id>0"
     * // #a0> 1 表示第一个参数大与1
     * unless:否定缓存
     *      unless="#a0==2" 如果第一个参数是2 就不缓存
     * sync:是否使用异步模式
     **/
    @Cacheable(/*cacheNames = "userInfo",*/key = "#user_id"/*,condition = "#id=1"*/) // #a0> 1 表示第一个参数大与1
    public UserInfo selectByUserId(String user_id) {
        System.out.println("根据id查询用户");
        return userInfoMapper.selectByUserId(user_id);
    }

    /*
     * @Author crush
     * @CachePut 即调用方法 有更新缓存数据
     * 运行时机:
     *  1 先调用目标方法
     *  2 将目标方法的结果缓存起来
     *  测试:
     *   1 先去查询
     *   2  然后再更新
     *   3  然后再执行查询 发现还是更新之前的数据
     *   4 发现他们缓存的时候 使用的key 是不一样的  这个时候要使用相同的key
     *
     **/
    @CachePut(/*value = "userInfo", */key = "#userInfo.user_id")
    public UserInfo updateUser(UserInfo userInfo) {
        System.out.println("更新用户信息");
        userInfoMapper.updateUser(userInfo);

        return userInfo;
    }


    /*
     * @Author crush
     * @CacheEvict 缓存清除
     *  可以通过key 来指定 要清除的值.
     * allEntries =true 指定清除这个缓存中的所有数据
     
     * beforeInvocation =false
     *    默认代表缓存清除操作是在方法执行之后清除 如果出现异常 缓存就不会清除
   	 *    这句话的意思是 如果这个方法执行时出现异常  缓存就不会清除
     *
     * beforeInvocation =true
     *     缓存的清除是否在方法之前执行  无论方法是否出现异常 缓存都清除
     **/
    @CacheEvict(/*value = "userInfo",*/ key = "#user_id")
    public void delete(Integer user_id) {
        System.out.println("删除了" + user_id + "用户");
    }

    /*
     * @Author crush
     * 这个就是一个组合的
     **/
    @Caching(
            cacheable = {
                    @Cacheable(/*value = "userInfo",*/key = "#user_name")
            },
            put = {
                    @CachePut(/*value = "userInfo",*/key = "#result.user_id"),
                    @CachePut(/*value = "userInfo",*/key = "#result.user_password")
            }
    )
    public UserInfo selectByUserName(String user_name) {
        return userInfoMapper.selectByUserName(user_name);
    }
}

8、Controller层

import com.crush.pojo.UserInfo;
import com.crush.service.UserInfoService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RestController;

/**
 * @version 1.0
 * @author: crush
 * @date: 2021-04-10 16:20
 */
@RestController
public class HelloRedisController {


    @Autowired
    UserInfoService userInfoService;

    @GetMapping("users/{user_id}")
    public UserInfo user(@PathVariable("user_id") String user_id) {
        return userInfoService.selectByUserId(user_id);
    }

    @GetMapping("users")
    public UserInfo update(UserInfo userInfo) {
        UserInfo userInfo1 = userInfoService.updateUser(userInfo);
        return userInfo1;
    }

    @GetMapping("dteUsers/{user_id}")
    public String delete(@PathVariable("user_id") Integer user_id) {
        userInfoService.delete(user_id);
        return "删除成功";
    }

    @GetMapping("usersName/{user_name}")
    public UserInfo selectByName(@PathVariable("user_name") String user_name) {
        return userInfoService.selectByUserName(user_name);
    }

}

9、主启动类:

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

@SpringBootApplication
@EnableCaching
@MapperScan("com.crush.mapper")
public class SpringbootRedisApplication {

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

测试:

@Cacheable 测试

第一次测试查询:

第二次执行查询方法

@CachePut 测试

执行更新用户方法

执行完更新用户方法之后 再次执行 查询方法 看控制台会不会有输出

@CachePut 测试

进行第二次测试 再次执行查询方法 发现控制台没有输出