Java缓存接口标准JSR-107:Java Caching定义了5个核心接口,分别是CachingProvider(缓存提供者), CacheManager(缓存管理器), Cache(缓存组件), Entry (key-value对)和Expiry(条目有效期),他们的关系如下图所示。但是该缓存规范,整合难度较大。Spring从3.1开始定义了org.springframework.cache.Cache和org.springframework.cache.CacheManager接口来统一不同的缓存技术;
1、接口、缓存注解、key/value生成策略
Cache | 缓存接口,定义缓存操作。实现:RedisCache、EhCache等(根据缓存技术不同来实现Cache接口) |
CacheManager | 缓存接口,缓存管理器,管理各种缓存(Cache)组件 |
@Cacheable | 可缓存的,标注方法,能够根据方法的请求参数对其结果进行缓存 |
@CacheEvict | 清空缓存 |
@CachePut | 更新缓存 |
@EnableCaching | 开启基于注解的缓存 |
keyGenerator | 缓存数据时key生成策略 |
serialize | 缓存数据时value序列化策略 |
2、使用cache缓存步骤(不使用缓存时,每次访问都会连接数据库)
a、引入依赖
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-cache</artifactId>
</dependency>
b、开启基于注解的缓存 @EnableCaching
c、标注缓存注解@Cacheable:标注方法可缓存;@CacheEvict:缓存清除;@CachePut:缓存更新等;支持spel表达式,如下图;参考官方文档:https://docs.spring.io/spring/docs/5.1.0.BUILD-SNAPSHOT/spring-framework-reference/integration.html#cache
package com.example.cache.service;
import com.example.cache.bean.Employee;
import com.example.cache.mapper.EmployeeMapper;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cache.annotation.*;
import org.springframework.stereotype.Service;
//@Service:将service加载到容器中
//CacheConfig:公共的缓存配置;cacheNames = "emp":之后的value="emp"就可以不用写
@CacheConfig(cacheNames = "emp")
@Service
public class EmployeeService {
@Autowired
EmployeeMapper employeeMapper;
/**
* @Cacheable:将方法的运行结果进行保存,以后再要相同的数据,直接从缓存中获取,不用调用方法
* CacheManager管理多个Cache组件,对缓存的真正CRUD操作在Cache组件中,每个缓存组件有自己唯一的名字
* 属性:(缓存数据时使用key-value的形式)
* cacheNames/value:指定缓存组件的名字;将方法的返回结果放在哪个缓存中,是数组的方式,可以指定多个缓存
* key:缓存数据使用的key;可以用她来指定。默认是使用方法参数的值 键值对:1-方法的返回值
* 参数id的值:#id相当于#root.args[0]
* keyGenerator:key的生成器;可以自己指定key的生成器组件id
* ——key和keyGenerator不可同时使用
* cacheManager:指定缓存管理器(从哪个缓存管理器中取);cacheResolver:指定缓存解析器
* condition:指定符合条件的情况下才缓存;condition="#id>0":id大于0的时候才缓存
* unless:否定缓存;当unless指定的true,方法的返回值就不会被缓存;可以获取到结果进行判断
* unless="#result == null":结果为空时不缓存
* sync:缓存是否使用异步模式
* @param id
* @return
*/
@Cacheable(cacheNames = {"emp"})
public Employee getEmp(Integer id){
System.out.println("查询"+id+"号员工");
Employee emp = employeeMapper.getEmpById(id);
return emp;
}
/**
* @CachePut:既调用方法,又更新缓存数据;
* 修改了数据库的某个数据,同时更新缓存
* 运行过程:
* 1、先调用目标方法
* 2、将目标方法的结果缓存起来
* 更新后重新查询出的数据是更新前的数据:
* ——(key默认使用方法参数的值)查询是的缓存是@Cacheable缓存的值,key是1;@CachePut更新后缓存的值:key是传入的employee对象
* ——所以要统一key:key="#employee.id";key="#result.id";
*/
@CachePut(value = "emp",key="#employee.id")
public Employee updateEmp(Employee employee){
employeeMapper.updateEmp(employee);
return employee;
}
/**
*@CacheEvict缓存清除
* key:指定要清楚的数据
* allEntries=true:指定清除这个缓存中的所有数据
* beforeInvocation = false;缓存的清除是否在方法之前执行;false:在方法执行之后清除
*/
@CacheEvict(value="emp",key="#id")
public void deleteEmp(Integer id){
}
/**
* 指定多个缓存规则
*/
@Caching(
cacheable = {
@Cacheable(value = "emp",key = "#lastName")
},
put={
@CachePut(value = "emp",key="#result.id"),
@CachePut(value = "emp",key = "#result.email")
}
)
public Employee getEmpByLastName(String lastName) {
return employeeMapper.getEmpByLastName(lastName);
}
}
3、整合redis进行缓存;开发中常使用缓存中间件:redis、memcahed、ehcache;
引入redis启动器
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
配置redis信息
spring.redis.host=127.0.0.1
spring.redis.password=
使用redis
@RunWith(SpringRunner.class)
@SpringBootTest
public class SpringbootCacheApplicationTests implements Serializable {
@Autowired
EmployeeMapper employeeMapper;
@Autowired
StringRedisTemplate stringRedisTemplate;//key-value都是操作字符串
@Autowired
RedisTemplate redisTemplate;//key-value都是对象
//@Autowired
//RedisTemplate<Object, Employee> empRedisTemplate;
/**
* Redis常见的五大数据类型
* String(字符串)、List(列表)、Set(集合)、Hash(散列)、ZSet(有序集合)
* stringRedisTemplate.opsForValue()[操作String(字符串)]
* stringRedisTemplate.opsForList()[操作List(列表)]
* stringRedisTemplate.opsForSet()[操作Set(集合)]
* stringRedisTemplate.opsForHash()[操作Hash(散列)]
* stringRedisTemplate.opsForZSet()[操作ZSet(有序集合)]
*/
@Test
public void test01(){
//stringRedisTemplate.opsForValue().append("msg","hello");
String msg = stringRedisTemplate.opsForValue().get("msg");
System.out.println(msg);
}
//测试保存对象(需要序列化)
@Test
public void test02(){
Employee empById = employeeMapper.getEmpById(1);
//默认如果保存对象,使用jdk序列化机制,序列化后的数据保存到redis
redisTemplate.opsForValue().set("emp-01",empById);
//将数据以json的方式保存:1、自己将对象转化为json;2、redisTemplate默认的序列化规则:改变默认的序列化规则
//empRedisTemplate.opsForValue().set("emp-01",empById);
}
}
自定义序列化器,改变默认的序列化规则;使其保存的数据为json格式,而不是序列化格式。
package com.example.cache.config;
import com.example.cache.bean.Employee;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.serializer.Jackson2JsonRedisSerializer;
import java.net.UnknownHostException;
import java.util.LinkedHashSet;
import java.util.List;
@Configuration
public class MyRedisConfig {
//RedisTemplate<Object, Employee>:序列化Employee
@Bean
public RedisTemplate<Object, Employee> empRedisTemplate(RedisConnectionFactory redisConnectionFactory) throws UnknownHostException {
RedisTemplate<Object, Employee> template = new RedisTemplate();
template.setConnectionFactory(redisConnectionFactory);
//序列化器
Jackson2JsonRedisSerializer<Employee> ser = new Jackson2JsonRedisSerializer<Employee>(Employee.class);
template.setDefaultSerializer(ser);
return template;
}
}
原理:默认使用缓存管理器CacheManager来创建Cache缓存组件,来给缓存中存取数据。加入redis后,开启了RedisCacheConfiguration。RedisCacheManager帮我们创建RedisCache来作为缓存组件。
4、缓存运行流程
@Cacheable:
a、方法运行之前,先去查询Cache(缓存组件),按照cacheNames指定的名字获取;(CacheManager先获取相应的缓存),如果没有Cache组件,第一次获取缓存会自动创建
b、去Cache中查找缓存的内容,使用一个key,默认就是方法的参数;
key是按照某种策略生成的;默认是使用keyGenerator生成的;默认使用SimpleKeyGenerator生成key
SimpleKeyGenerator生成key的默认策略:
如果没有参数:key=new SimpleKey();如果有一个参数,key=参数的值;如果有多个参数:key= new SimpleKey(params);
c、没有查到缓存就调用目标方法;
d、将目标方法返回的结果放到缓存中
@Cacheable标注的方法执行之前来检查缓存中有没有这个数据,默认按照参数的值作为key去查询缓存,如果没有就运行方法,并将结果放入缓存。以后再来调用,就可以使用缓存中的数据;
5、缓存原理
a、自动配置类CacheAutoConfiguration给容器中导入组件
b、各缓存的配置类
c、默认生效的配置类:SimpleCacheConfiguration
d、给容器中注册了一个CacheManager:ConcurrentManager;可以获取和创建ConcurrentMapCache类型的缓存组件,他的作用是将数据保存在ConcurrentMap中。