示例源码下载:https://github.com/AlgerFan/springBootExample ,欢迎 star。

一、JSR107

Java Caching 定义了 5 个核心接口,分别是 CacheingProvider,CacheManager,Cache,Entry 和 Expiry。

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

二、Spring 缓存抽象

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

  • Cache 接口为缓存的组件规范定义,包含缓存的各种操作集合;
  • Cache 接口下 Spring 提供了各种 xxxCache 的实现;如 RedisCache, EhCacheCache,
    ConcurrentMapCache 等;
  • 每次调用需要缓存功能的方法时,Spring 会检查检查指定参数的指定的目标方法是否
    已经被调用过;如果有就直接从缓存中获取方法调用后的结果,如果没有就调用方法
    并缓存结果后返回给用户。下次调用直接从缓存中获取。
  • 使用 Spring 缓存抽象时我们需要关注以 下两点;
    • 确定方法需要被缓存以及他们的缓存策略
    • 从缓存中读取之前缓存存储的数据

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

cache01.png

Cache 缓存接口,定义缓存操作。实现有:RedisCacheEhCacheCacheConcurrentMapCache
CacheManager 缓存管理器,管理各种缓存(Cache)组件
@Cacheable 主要针对方法配置,能够根据方法的请求参数对其结果进行缓存
@CacheEvict 清空缓存
@CachePut 保证方法被调用,又希望结果被缓存。
@EnableCaching 开启基于注解的缓存
keyGenerator 缓存数据时 key 生成策略
serialize 缓存数据时 value 序列化策略

四、环境基本搭建及@Cacheable

1、创建 javaBean 封装数据

2、整合 MyBatis 操作数据库

  • 配置数据源信息
  • 使用注解版的 MyBatis;
  • @MapperScan 指定需要扫描的 mapper 接口所在的包

3、引入依赖:

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

4、启动类加上注解

@MapperScan("cn.algerfan.springboot.mapper")
@SpringBootApplication
//开启基于注解的缓存
@EnableCaching
public class SpringBoot13CacheApplication {

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

}

5、service

@Service
public class EmployeeService {

    @Autowired
    EmployeeMapper employeeMapper;
    
    @Cacheable(value = {"emp"}/*,keyGenerator = "myKeyGenerator",condition = "#a0>1",unless = "#a0==2"*/)
    public Employee getEmp(Integer id){
        System.out.println("查询"+id+"号员工");
        Employee emp = employeeMapper.getEmpById(id);
        return emp;
    }
}

6、直接使用注解即可

  • @Cacheable
  • @CacheEvict
  • @CachePut

默认使用的是 ConcurrentMapCacheManager==ConcurrentMapCache;将数据保存在 ConcurrentMap<Object, Object> 中
开发中使用缓存中间件;Redis、Memcached、ehcache。

五、缓存工作原理&@Cacheable 运行流程

1、缓存工作原理

缓存是将方法的运行结果进行缓存;以后再要相同的数据,直接从缓存中获取,不用调用方法;
CacheManager 管理多个 Cache 组件的,对缓存的真正 CRUD 操作在 Cache 组件中,每一个缓存组件有自己的一个名字;

1)自动配置类:CacheAutoConfiguration
2)缓存的配置类:GenericCacheConfiguration、JCacheCacheConfiguration、EhCacheCacheConfiguration、SimpleCacheConfiguration 等等
3)哪个配置类默认生效:SimpleCacheConfiguration;
4)给容器中注册了一个 CacheManager:ConcurrentMapCacheManager
5)可以获取和创建 ConcurrentMapCache 类型的缓存组件;他的作用将数据保存在 ConcurrentMap 中;

2、@Cacheable 运行流程

1)方法运行之前,先去查询 Cache(缓存组件),按照 cacheNames 指定的名字获取;
(CacheManager 先获取相应的缓存),第一次获取缓存如果没有 Cache 组件会自动创建。

2)去 Cache 中查找缓存的内容,使用一个 key,默认就是方法的参数;
key 是按照某种策略生成的;默认是使用 keyGenerator 生成的,默认使用 SimpleKeyGenerator 生成 key;SimpleKeyGenerator 生成 key 的默认策略;

  • 如果没有参数;key=new SimpleKey();
  • 如果有一个参数:key= 参数的值
  • 如果有多个参数:key=new SimpleKey(params);

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

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

5)@Cacheable 标注的方法执行之前先来检查缓存中有没有这个数据,默认按照参数的值作为 key 去查询缓存,如果没有就运行方法并将结果放入缓存;以后再来调用就可以直接使用缓存中的数据;

6)几个属性:

  • cacheNames/value:指定缓存组件的名字;将方法的返回结果放在哪个缓存中,是数组的方式,可以指定多个缓存;
  • key:缓存数据使用的 key;可以用它来指定。默认是使用方法参数的值 1-方法的返回值
    编写 SpEL; #id;参数 id 的值 #a0 #p0 #root.args[0]
  • keyGenerator:key 的生成器;可以自己指定 key 的生成器的组件 id
    key/keyGenerator:二选一使用;
  • cacheManager:指定缓存管理器;或者 cacheResolver 指定获取解析器
  • condition:指定符合条件的情况下才缓存;
    condition = "#id>0"
    condition = "#a0>1":第一个参数的值》1 的时候才进行缓存
  • unless:否定缓存;当 unless 指定的条件为 true,方法的返回值就不会被缓存;可以获取到结果进行判断
    unless = "#result == null"
    unless = "#a0==2": 如果第一个参数的值是 2,结果不缓存;
  • sync:是否使用异步模式

六、@CachePut

既调用方法,又更新缓存数据;同步更新缓存
修改了数据库的某个数据,同时更新缓存;
运行时机:
1、先调用目标方法
2、将目标方法的结果缓存起来

测试步骤:
1、查询 1 号员工;查到的结果会放在缓存中;
key:1 value:lastName:张三
2、以后查询还是之前的结果
3、更新 1 号员工;【lastName:zhangsan;gender:0】
将方法的返回值也放进缓存了;
key:传入的 employee 对象 值:返回的 employee 对象;
4、查询 1 号员工

@Cacheable(value = {"emp"}/*,keyGenerator = "myKeyGenerator",condition = "#a0>1",unless = "#a0==2"*/)
public Employee getEmp(Integer id){
    System.out.println("查询"+id+"号员工");
    Employee emp = employeeMapper.getEmpById(id);
    return emp;
}	
@CachePut(value = "emp",key = "#result.id")
public Employee updateEmp(Employee employee){
    System.out.println("updateEmp:"+employee);
    employeeMapper.updateEmp(employee);
    return employee;
}

七、@CacheEvict

key:指定要清除的数据

  • allEntries = true:指定清除这个缓存中所有的数据
  • beforeInvocation = false:缓存的清除是否在方法之前执行,默认代表缓存清除操作是在方法执行之后执行;如果出现异常缓存就不会清除
  • beforeInvocation = true: 代表清除缓存操作是在方法运行之前执行,无论方法是否出现异常,缓存都清除
@CacheEvict(value="emp",beforeInvocation = true, key = "#id",)
public void deleteEmp(Integer id){
    System.out.println("deleteEmp:"+id);
    employeeMapper.deleteEmpById(id);
}

八、@Caching&@CacheConfig

@Caching 定义复杂的缓存规则

@Caching(
        cacheable = {
                @Cacheable(value="emp",key = "#lastName")
        },
        put = {
                @CachePut(value="emp",key = "#result.id"),
                @CachePut(value="emp",key = "#result.email")
        }
)

@CacheConfig 抽取缓存的公共配置

@CacheConfig(cacheNames="emp"/*,cacheManager = "employeeCacheManager"*/)
@Service
public class EmployeeService {

九、整合 Redis 实现缓存

原理:CacheManager===Cache 缓存组件来实际给缓存中存取数据

  • 引入 Redis 的 starter,容器中保存的是 RedisCacheManager;
  • RedisCacheManager 帮我们创建 RedisCache 来作为缓存组件;RedisCache 通过操作 Redis 缓存数据的;
  • 默认保存数据 k-v 都是 Object。

1、引入 spring-boot-starter-data-redis

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

2、application.yml 配置 Redis 连接地址

spring.redis.host=127.0.0.1

3、使用 ReditTemplate 操作 Redis

Redis 常见的五大数据类型:String(字符串)、List(列表)、Set(集合)、Hash(散列)、ZSet(有序集合)

​1)redisTemplate.opsForValue();//操作字符串
​2)redisTemplate.opsForHash();//操作 hash
​3)redisTemplate.opsForList();//操作 list
​4)redisTemplate.opsForSet();//操作 set
​5)redisTemplate.opsForZSet();//操作有序 set

@RunWith(SpringRunner.class)
@SpringBootTest
public class SpringBoot1ValidateApplicationTests {

    @Autowired
    EmployeeMapper employeeMapper;
    @Autowired
    StringRedisTemplate stringRedisTemplate;  //操作k-v都是字符串的
    @Autowired
    RedisTemplate redisTemplate;  //k-v都是对象的
    @Autowired
    RedisTemplate<Object, Employee> empRedisTemplate;

    @Test
    public void test01(){
        //给redis中保存数据
        stringRedisTemplate.opsForValue().append("msg","hello");
		String msg = stringRedisTemplate.opsForValue().get("msg");
		System.out.println(msg);

		stringRedisTemplate.opsForList().leftPush("mylist","1");
		stringRedisTemplate.opsForList().leftPush("mylist","2");
    }

    //测试保存对象
    @Test
    public void test02(){
        Employee empById = employeeMapper.getEmpById(1);
        //默认如果保存对象,使用jdk序列化机制,序列化后的数据保存到redis中
        redisTemplate.opsForValue().set("emp-01",empById);
        empRedisTemplate.opsForValue().set("emp-01",empById);
    }

}

4、配置缓存、CacheManagerCustomizers

默认使用 jdk 序列化机制保存对象,也可以将数据以 JSON 的方式保存
(1)自己将对象转为 JSON
(2)redisTemplate 默认的序列化规则;改变默认的序列化规则;

@Configuration
public class MyRedisConfig {

    @Bean
    public RedisTemplate<Object, Employee> empRedisTemplate(
            RedisConnectionFactory redisConnectionFactory) {
        RedisTemplate<Object, Employee> template = new RedisTemplate<Object, Employee>();
        template.setConnectionFactory(redisConnectionFactory);
        Jackson2JsonRedisSerializer<Employee> ser = new Jackson2JsonRedisSerializer<Employee>(Employee.class);
        template.setDefaultSerializer(ser);
        return template;
    }
}

标题:(12)Spring Boot2.0 与缓存
作者:AlgerFan
地址:https://www.algerfan.cn/articles/2019/01/15/1547532314012.html
版权声明:本文为博主原创文章,转载请附上博文链接!