当前位置: 移动技术网 > IT编程>开发语言>Java > SpringBoot 缓存篇

SpringBoot 缓存篇

2020年07月12日  | 移动技术网IT编程  | 我要评论

一、预备知识

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设置。

JSR

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);
    }

测试:
在这里插入图片描述
多次查询同一员工数据只执行一次数据库查询。

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;
    }

测试:
在这里插入图片描述
更新员工信息后缓存随之修改。查询仍然从缓存中获取。

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);
    }

}

在这里插入图片描述
在这里插入图片描述

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();
    }

}

测试
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

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

如对本文有疑问, 点击进行留言回复!!

相关文章:

验证码:
移动技术网