欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页  >  IT编程

SpringBoot 缓存篇

程序员文章站 2022-04-29 10:41:40
一、预备知识1.1 JSR107缓存规范Java Caching 5个核心接口:CachingProvider:定义了创建、配置、获取、管理和控制多个CacheManager。一个应用可以在运行期访问多个CachingProvider。CacheManager:定义了创建、配置、获取、管理和控制多个唯一命名的Cache,这些Cache存在于CacheManager的上下文中。一个CacheManager仅被一个CachingProvider所拥有。Cache:是一个类似Map的数据结构并临时存储...

一、预备知识

1.1 JSR107缓存规范

Java Caching 5个核心接口:

  • CachingProvider:定义了创建、配置、获取、管理和控制多个CacheManager。一个应用可以在运行期访问多个CachingProvider。
  • CacheManager:定义了创建、配置、获取、管理和控制多个唯一命名的Cache,这些Cache存在于CacheManager的上下文中。一个CacheManager仅被一个CachingProvider所拥有。
  • Cache:是一个类似Map的数据结构并临时存储以Key为索引的值。一个Cache仅被一个CacheManager所拥有。
  • Entry:是一个存储在Cache中的key-value对。
  • Expiry:每一个存储在Cache中的条目有一个定义的有效期。一旦超过这个时间,条目为过期的状态。一旦过期,条目将不可访问、更新和删除。缓存有效期可以通过ExpiryPolicy设置。

SpringBoot 缓存篇

1.2 SpEL上下文数据

名称 位置 描述 示例
methodName root对象 当前被调用的方法 #root.methodname
method root对象 当前被调用的方法 #root.method.name
target root对象 当前被调用的目标对象实例 #root.target
targetClass root对象 当前被调用的目标对象的类 #root.targetClass
args root对象 当前被调用的方法的参数列表 #root.args[0]
caches root对象 当前方法调用使用的缓存列表 #root.caches[0].name
Argument Name 执行上下文 当前被调用的方法的参数,如findArtisan(Artisan artisan),可以通过#artsian.id获得参数 #artsian.id
result 执行上下文 法执行后的返回值(仅当方法执行后的判断有效,如 unless cacheEvict的beforeInvocation=false) #result

注意:

  1. 当我们要使用root对象的属性作为key时我们也可以将“#root”省略,因为Spring默认使用的就是root对象的属性。 如
    @Cacheable(key = "targetClass + methodName +#p0")
  2. 使用方法参数时我们可以直接使用“#参数名”或者“#p参数index”。 如:
    @Cacheable(value="users", key="#id")
    @Cacheable(value="users", key="#p0")

SpEL提供了多种运算符

类型 运算符
关系 <,>,<=,>=,==,!=,lt,gt,le,ge,eq,ne
算术 +,- ,* ,/,%,^
逻辑 &&,
条件 ?: (ternary),?: (elvis)
正则表达式 matches
其他类型 ?.,?[…],![…],^ […],$[…]

二、Spring缓存抽象

Spring从3.1开始定义了org.springframework.cache.Cacheorg.springframework.cache.CacheManager接口来统一不同的缓存技术,并支持使用JCache(JSR-107)注解简化我们开发。

  • Cache接口为缓存的组件规范定义,包含缓存的各种操作集合。
  • Cache接口下Spring提供了各种xxxCache的实现,如RedisCache、EhCacheCache 、ConcurrentMapCache等。
  • 每次调用需要缓存功能的方法时,Spring会检查检查指定参数的指定的目标方法是否已经被调用过,如果有就直接从缓存中获取方法调用后的结果,如果没有就调用方法并缓存结果后返回给用户。下次调用直接从缓存中获取。

使用Spring缓存抽象时我们需要关注以下两点:

  1. 确定方法需要被缓存以及他们的缓存策略
  2. 从缓存中读取之前缓存存储的数据

三、几个重要概念&缓存注解

名称 解释
Cache 缓存接口,定义缓存操作。实现有:RedisCache、EhCacheCache、ConcurrentMapCache等
CacheManager 缓存管理器,管理各种缓存(cache)组件
@Cacheable 主要针对方法配置,能够根据方法的请求参数对其进行缓存
@CacheEvict 清空缓存
@CachePut 保证方法被调用,又希望结果被缓存。与@Cacheable区别在于是否每次都调用方法,常用于更新
@EnableCaching 开启基于注解的缓存
keyGenerator 缓存数据时key生成策略
serialize 缓存数据时value序列化策略
@CacheConfig 统一配置本类的缓存注解的属性

@Cacheable/@CachePut/@CacheEvict 主要的参数

名称 解释 实例
value 缓存的名称,在 spring 配置文件中定义,必须指定至少一个 @Cacheable(value=”mycache”) 或者@Cacheable(value={”cache1”,”cache2”})
key 缓存的 key,可以为空,如果指定要按照 SpEL 表达式编写,如果不指定,则缺省按照方法的所有参数进行组合 @Cacheable(value=”testcache”,key=”#userName”)
condition 缓存的条件,可以为空,使用 SpEL 编写,返回 true 或者 false,只有为 true 才进行缓存/清除缓存,在调用方法之前之后都能判断 @Cacheable(value=”testcache”,condition=”#userName.length()>2”)
allEntries (@CacheEvict ) 是否清空所有缓存内容,缺省为 false,如果指定为true,则方法调用后将立即清空所有缓存 @CachEvict(value=”testcache”,allEntries=true)
beforeInvocation (@CacheEvict) 是否在方法执行前就清空,缺省为 false,如果指定为 true,则在方法还没有执行的时候就清空缓存,缺省情况下,如果方法执行抛出异常,则不会清空缓存 @CachEvict(value=”testcache”,beforeInvocation=true)
unless (@CachePut) (@Cacheable) 用于否决缓存的,不像condition,该表达式只在方法执行之后判断,此时可以拿到返回值result进行判断。条件为true不会缓存,fasle才缓存 @Cacheable(value=”testcache”,unless=”#result == null”)

四、基本环境搭建

  • 环境:Spring Boot 2.3.1
  • IDE:IDEA

4.1 导入依赖

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

4.2 创建数据库&表

sql文件:

SET FOREIGN_KEY_CHECKS=0;

-- ----------------------------
-- Table structure for department
-- ----------------------------
DROP TABLE IF EXISTS `department`;
CREATE TABLE `department` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `departmentName` varchar(255) DEFAULT NULL,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;

-- ----------------------------
-- Table structure for employee
-- ----------------------------
DROP TABLE IF EXISTS `employee`;
CREATE TABLE `employee` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `lastName` varchar(255) DEFAULT NULL,
  `email` varchar(255) DEFAULT NULL,
  `gender` int(2) DEFAULT NULL,
  `d_id` int(11) DEFAULT NULL,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;

4.3 配置数据源信息

application.yml

spring:
  datasource:
    # 数据源基本配置
    username: root
    password: "00000000"
    url: jdbc:mysql://192.168.168.128/springboot_cache?useSSL=true&useUnicode=true&characterEncoding=UTF-8&serverTimezone=GMT%2B8
  redis:
    host: 192.168.168.128
    password: "00000000"
mybatis:
  configuration:
    # 开启驼峰命名匹配规则
    map-underscore-to-camel-case: true
    # 配置Sql语句日志
logging:
  level:
    com:
      example:
        cache:
          mapper: debug

五、开始使用

5.1 在启动类注解@EnableCaching开启缓存

@EnableCaching // 开启基于注解的缓存
@MapperScan("com.example.cache.mapper") // 指定要扫描的mapper接口所在的包
@SpringBootApplication
public class CacheApplication {

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

}

5.2 缓存 @Cacheable

  • @Cacheable 注解会先查询是否已经有缓存,有会使用缓存,没有则会执行方法并缓存。
  • 此处的 value 是必需的,它指定了你的缓存存放在哪块命名空间。
  • 此处的key是使用的spEL表达式。这里有一个小坑,如果你把 methodName 换成 method 运行会报错,观察它们的返回类型,原因在于 methodName 是 String 而 methoh 是 Method 。
  • 此处的 Employee 实体类一定要实现序列化Serializable,否则会报 java.io.NotSerializableException 异常。
    // 主要针对方法配置,能够根据方法的请求参数对其结果进行缓存
    @Cacheable(value = "emp", key = "#id")
    public Employee getEmp(Integer id) {
        System.out.println("查询" + id + "号员工");
        return employeeMapper.getEmpById(id);
    }

测试:
SpringBoot 缓存篇
多次查询同一员工数据只执行一次数据库查询。

5.3 自定义缓存配置生成Key

配置类

@Configuration
public class MyCacheConfig {

    @Bean("myKeyGenerator")
    public KeyGenerator keyGenerator() {
        return (o, method, objects) -> method.getName() + Arrays.asList(objects).toString();
    }

}

修改注解

    @Cacheable(value = "emp", keyGenerator = "myKeyGenerator")
    public Employee getEmp(Integer id) {
        System.out.println("查询" + id + "号员工");
        return employeeMapper.getEmpById(id);
    }

5.4 更新 @CachePut

  • @CachePut 注解的作用主要针对方法配置,能够根据方法的请求参数对其结果进行缓存。
  • @CachePut 注解每次都会触发真实方法的调用。
  • 需要注意的是该注解的 value 和 key 必须与要更新的缓存相同,也就是与 @Cacheable 相同。
  • 运行时机:先调用方法,后将目标方法的结果缓存起来。
    // 保证方法被调用,同时结果被缓存
    @CachePut(value = "emp", key = "#employee.id")
    public Employee updateEmp(Employee employee) {
        System.out.println("更新了员工:" + employee.getLastName());
        employeeMapper.updateEmp(employee);
        return employee;
    }

测试:
SpringBoot 缓存篇
更新员工信息后缓存随之修改。查询仍然从缓存中获取。

5.5 清除 @CacheEvict

  • @CachEvict 的作用主要针对方法配置,能够根据一定的条件对缓存进行清空。
属性 解释 示例
allEntries 是否清空所有缓存内容,缺省为 false,如果指定为true,则方法调用后将立即清空所有缓存 @CachEvict(value=”testcache”,allEntries=true)
beforeInvocation 是否在方法执行前就清空,缺省为 false,如果指定为 true,则在方法还没有执行的时候就清空缓存,缺省情况下,如果方法执行抛出异常,则不会清空缓存 @CachEvict(value=”testcache”,beforeInvocation=true)
  1. 清除一条缓存,key为要清空的数据
    @CacheEvict(value = "emp", key = "#id")
    public void deleteEmp(Integer id) {
        System.out.println("删除了员工:" + id);
        employeeMapper.deleteEmpById(id);
    }
  1. 方法调用后清空所有缓存
    @CacheEvict(value = "accountCache", allEntries = true)
    public void deleteAll() {
        System.out.println("删除了所有员工");
        employeeMapper.deleteAll();
    }
  1. 方法调用前清空所有缓存
	@CacheEvict(value = "accountCache", beforeInvocation = true)
    public void deleteAll() {
        System.out.println("删除了所有员工");
        employeeMapper.deleteAll();
    }

5.6 组合 @Caching

  • 有时候我们可能组合多个 Cache 注解使用,此时就需要 @Caching 组合多个注解标签了。
    @Caching(
            cacheable = {
                    @Cacheable(value = "emp", key = "#lastName")
            },
            put = {
                    @CachePut(value = "emp", key = "#result.id"),
                    @CachePut(value = "emp", key = "#result.email")
            },
            evict = {
                    @CacheEvict(value = "emp", key = "#lastName")
            }
    )
    public Employee getEmpByLastName(String lastName) {
        return employeeMapper.getEmpByLastName(lastName);
    }

5.7 配置 @CacheConfig

  • 当我们需要缓存的地方越来越多,你可以使用 @CacheConfig 注解来统一指定value的值,这时可省略 value ,如果你在你的方法依旧写上了 value ,那么依然以方法的value值为准。
@CacheConfig(cacheNames = "emp") // 抽取缓存的公共配置
@Service
public class EmployeeService {
	...
}

六、整合Redis实现缓存

6.1 部署Redis

  1. 拉取镜像
docker pull redis
  1. 创建Redis容器并设置密码
docker run --name redis -p 6379:6379 redis --requirepass 123456

6.2 导入依赖

导入此依赖时,不再需要spring-boot-starter-cache

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

6.3 测试保存数据

@SpringBootTest
class CacheApplicationTests {

    @Autowired
    StringRedisTemplate stringRedisTemplate; // 操作k-v都是字符串的

    @Autowired
    RedisTemplate redisTemplate; // 操作k-v都是对象的

    @Test
    void Test02() {
        // redis中保存数据
        stringRedisTemplate.opsForValue().append("msg", "Hello");
        // redis中获取数据
        String msg = stringRedisTemplate.opsForValue().get("msg");
        System.out.println(msg);
    }

    @Test
    void Test03() {
        // redis中保存对象
        Employee empById = employeeMapper.getEmpById(1);
        // 默认使用jdk序列化机制保存对象
        redisTemplate.opsForValue().set("emp-01", empById);
    }

}

6.4 自定义序列化机制

将数据以 Json 的方式保存

@Configuration
public class MyRedisConfig {

    // 自定义Employee序列化机制
    @Bean
    public RedisTemplate<Object, Employee> empRedisTemplate(RedisConnectionFactory redisConnectionFactory) throws UnknownHostException {
        RedisTemplate<Object, Employee> template = new RedisTemplate();
        template.setConnectionFactory(redisConnectionFactory);
        Jackson2JsonRedisSerializer<Employee> serializer = new Jackson2JsonRedisSerializer<Employee>(Employee.class);
        template.setDefaultSerializer(serializer);
        return template;
    }

    // 自定义Department序列化机制
    @Bean
    public RedisTemplate<Object, Department> deptRedisTemplate(RedisConnectionFactory redisConnectionFactory) throws UnknownHostException {
        RedisTemplate<Object, Department> template = new RedisTemplate();
        template.setConnectionFactory(redisConnectionFactory);
        Jackson2JsonRedisSerializer<Department> serializer = new Jackson2JsonRedisSerializer<Department>(Department.class);
        template.setDefaultSerializer(serializer);
        return template;
    }

}

测试

@SpringBootTest
class CacheApplicationTests {

    @Resource
    EmployeeMapper employeeMapper;

    @Resource
    DepartmentMapper departmentMapper;

    @Autowired
    StringRedisTemplate stringRedisTemplate; // 操作k-v都是字符串的

    @Autowired
    RedisTemplate redisTemplate; // 操作k-v都是对象的

    @Autowired
    RedisTemplate<Object, Employee> empRedisTemplate;

    @Autowired
    RedisTemplate<Object, Department> deptRedisTemplate;

    @Test
    void Test03() {
        // redis中保存对象
        Employee empById = employeeMapper.getEmpById(1);
        // 默认使用jdk序列化机制保存对象
//        redisTemplate.opsForValue().set("emp-01", empById);
        empRedisTemplate.opsForValue().set("emp-01", empById);
    }

    @Test
    void Test04() {
        // redis中保存对象
        Department deptById = departmentMapper.getDeptById(1);
        // 使用Json保存对象
        deptRedisTemplate.opsForValue().set("dept-01", deptById);
    }

}

SpringBoot 缓存篇
SpringBoot 缓存篇

6.5 自定义CacheManager

@Configuration
public class MyRedisConfig {
    // 自定义CacheManager
    @Bean
    public CacheManager cacheManager(RedisConnectionFactory factory) {
        RedisCacheConfiguration cacheConfiguration = RedisCacheConfiguration.defaultCacheConfig()
                .entryTtl(Duration.ofDays(1))
                .disableCachingNullValues()
                .serializeValuesWith(RedisSerializationContext.SerializationPair.fromSerializer(new GenericJackson2JsonRedisSerializer()));
        return RedisCacheManager.builder(factory).cacheDefaults(cacheConfiguration).build();
    }

}

测试
SpringBoot 缓存篇
SpringBoot 缓存篇
SpringBoot 缓存篇
SpringBoot 缓存篇

本文地址:https://blog.csdn.net/weixin_41105242/article/details/107187153