1 Spring 缓存抽象
![](https://img.haomeiwen.com/i6262743/b416fc71b8c88ffa.png)
- @Cacheable:对请求参数和结果缓存,下次用同一个参数请求,就不再调用方法,直接从缓存中拿出数据
- @CacheEvict:清空缓存
- @CachePut:更新缓存,保证方法一定会被调用,同时更新缓存中的对应的数据。
- @EnableCaching:开启缓存的注解,开启了才可以使用缓存
2 简单使用
在启动类上面加注解@EnableCaching,开启缓存
@EnableCaching
public class Springboot01CacheApplication {
public static void main(String[] args) {
SpringApplication.run(Springboot01CacheApplication.class, args);
}
}
在调用数据库的方法上面使用@Cacheable
//将数据缓存在一个叫emp的缓存中去,下次查询同一个id的时候,就不会重新查询数据库了
@Cacheable(value = {"emp"})
public Employee getEmp(Integer id){
System.out.println("查询"+id+"号员工");
Employee emp = employeeMapper.getEmpById(id);
return emp;
}
3 @Cacheable的属性
3.1 cacheNames/value
指定缓存组件的名字;将方法的返回结果放在哪个缓存中,是数组的方式,可以指定多个缓存;
@Cacheable(value = {"emp"})
public Employee getEmp(Integer id){
System.out.println("查询"+id+"号员工");
Employee emp = employeeMapper.getEmpById(id);
return emp;
}
3.2 key
缓存是以key-value形式保存的,默认使用的key是方法参数的值,value是方法的返回值。
可以使用spel的方式取出一些值来作为key:
![](https://img.haomeiwen.com/i6262743/ee9d3fdec9961f6b.png)
比如:取出第一个方法的参数作为key
@Cacheable(value = {"emp"},key = "#root.args[0]")
public Employee getEmp(Integer id){
System.out.println("查询"+id+"号员工");
Employee emp = employeeMapper.getEmpById(id);
return emp;
}
3.3 keyGenerator
key的生成器;可以自己指定key的生成器的组件id
key/keyGenerator:二选一使用;
@Cacheable(value = {"emp"},keyGenerator = "myKeyGenerator")
public Employee getEmp(Integer id){
System.out.println("查询"+id+"号员工");
Employee emp = employeeMapper.getEmpById(id);
return emp;
}
@Configuration
public class MyCacheConfig {
@Bean("myKeyGenerator")
public KeyGenerator keyGenerator(){
return new KeyGenerator(){
@Override
public Object generate(Object target, Method method, Object... params) {
return method.getName()+"["+ Arrays.asList(params).toString()+"]";
}
};
}
}
3.4 cacheManager/cacheResolver
指定缓存管理器/指定获取解析器,二者作用相同
3.5 condition
指定符合条件的情况下才缓存
比如:condition = "#a0>1 and #root.methodName eq 'aaa' ":第一个参数的值>1并且方法名是aaa的时候才进行缓存
3.6 unless
当unless指定的条件为true,方法的返回值就不会被缓存
比如:
unless = "#result == null",如果方法的返回值为空,不缓存
unless = "#a0==2":如果第一个参数的值是2,结果不缓存;
3.7 sync
是否使用异步模式,默认false,改成true时,unless不可用
4 缓存的原理
4.1 自动配置类
CacheAutoConfiguration
4.2 缓存的配置类
/*
* org.springframework.boot.autoconfigure.cache.GenericCacheConfiguration
* org.springframework.boot.autoconfigure.cache.JCacheCacheConfiguration
* org.springframework.boot.autoconfigure.cache.EhCacheCacheConfiguration
* org.springframework.boot.autoconfigure.cache.HazelcastCacheConfiguration
* org.springframework.boot.autoconfigure.cache.InfinispanCacheConfiguration
* org.springframework.boot.autoconfigure.cache.CouchbaseCacheConfiguration
* org.springframework.boot.autoconfigure.cache.RedisCacheConfiguration
* org.springframework.boot.autoconfigure.cache.CaffeineCacheConfiguration
* org.springframework.boot.autoconfigure.cache.GuavaCacheConfiguration
* org.springframework.boot.autoconfigure.cache.SimpleCacheConfiguration【默认】
* org.springframework.boot.autoconfigure.cache.NoOpCacheConfiguration
*/
SimpleCacheConfiguration 给容器中注册了一个CacheManager:ConcurrentMapCacheManager,可以获取和创建ConcurrentMapCache类型的缓存组件;他的作用将数据保存在ConcurrentMap中
4.3 @Cacheable运行流程
- 根据cacheNames指定的名字获取Cache(缓存组件),第一次获取缓存如果没有Cache组件会自动创建。
- 根据key去chche中查找值,如果没有就执行方法,并将方法执行的结果放进缓存中。之后再调用直接使用缓存中的数据
4.4 key的生成策略
key是可以指定的,不指定的话,有一个默认的key生成策略SimpleKeyGenerator
SimpleKeyGenerator生成key的默认策略:
- 如果没有参数:key=new SimpleKey();
- 如果有一个参数:key=参数的值
- 如果有多个参数:key=new SimpleKey(params);
5 @CachePut
既调用方法,又更新缓存
//因为是先调用方法,后更新缓存,所以可以使用#result.id,
//@Cacheable的key则不可以使用#result.id,因为@Cacheable是要先查缓存的
@CachePut(value = "emp",key = "#result.id")
public Employee updateEmp(Employee employee){
System.out.println("updateEmp:"+employee);
employeeMapper.updateEmp(employee);
return employee;
}
6 @CacheEvict
- key:指定要清除的数据
- allEntries = true:指定清除这个缓存中所有的数据
- beforeInvocation = false:缓存的清除是否在方法之前执行
默认false代表缓存清除操作是在方法执行之后执行;如果出现异常缓存就不会清除
//清除emp里面对应key=当前id的缓存
@CacheEvict(value="emp",key = "#id")
public void deleteEmp(Integer id){
System.out.println("deleteEmp:"+id);
employeeMapper.deleteEmpById(id);
}
//清除emp里面所有的缓存
@CacheEvict(value="emp",allEntries = true)
public void deleteEmp(Integer id){
System.out.println("deleteEmp:"+id);
employeeMapper.deleteEmpById(id);
}
//清除缓存操作是在方法运行之前执行,无论方法是否出现异常,缓存都清除
@CacheEvict(value="emp",beforeInvocation = true)
public void deleteEmp(Integer id){
System.out.println("deleteEmp:"+id);
employeeMapper.deleteEmpById(id);
int i = 10/0;
}
7 @Caching
@Target({ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Inherited
@Documented
public @interface Caching {
Cacheable[] cacheable() default {};
CachePut[] put() default {};
CacheEvict[] evict() default {};
}
// @Caching 定义复杂的缓存规则
@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);
}
8 @CacheConfig
//写在类上面
@CacheConfig(cacheNames="emp") //抽取缓存的公共配置
以上是默认的使用ConcurrentMapCacheManager,数据保存在ConcurrentMap,接下来使用别的缓存
1 使用redis缓存
2 简单使用redis
2.1导包
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
2.2 配置
spring.redis.host=192.168.60.137
2.3 使用
@Autowired
StringRedisTemplate stringRedisTemplate; //操作k-v都是字符串的
@Autowired
RedisTemplate redisTemplate; //k-v都是对象的
Redis常见的五大数据类型
tring(字符串)、List(列表)、Set(集合)、Hash(散列)、ZSet(有序集合)
stringRedisTemplate.opsForValue()[String(字符串)]
stringRedisTemplate.opsForList()[List(列表)]
stringRedisTemplate.opsForSet()[Set(集合)]
stringRedisTemplate.opsForHash()[Hash(散列)]
stringRedisTemplate.opsForZSet()[ZSet(有序集合)]
比如:
stringRedisTemplate.opsForValue().append("msg","hello");
String msg = stringRedisTemplate.opsForValue().get("msg");
stringRedisTemplate.opsForList().leftPush("mylist","1");
stringRedisTemplate.opsForList().leftPush("mylist","2");
redis保存对象
首先对象要implements Serializable
- 可以自己将对象转换为json,再像保存string一样保存
- 或者改变默认的序列化规则
@Configuration
public class MyRedisConfig {
@Bean
public RedisTemplate<Object, Employee> empRedisTemplate(
RedisConnectionFactory redisConnectionFactory)
throws UnknownHostException {
RedisTemplate<Object, Employee> template = new RedisTemplate<Object, Employee>();
template.setConnectionFactory(redisConnectionFactory);
Jackson2JsonRedisSerializer<Employee> ser = new Jackson2JsonRedisSerializer<Employee>(Employee.class);
template.setDefaultSerializer(ser);
return template;
}
使用:
@Autowired
RedisTemplate<Object, Employee> empRedisTemplate;
@Test
public void test02(){
Employee empById = employeeMapper.getEmpById(1);
empRedisTemplate.opsForValue().set("emp-01",empById);
}
3 使用redis作为缓存
- 引入redis的starter,容器中保存的是 RedisCacheManager;默认的ConcurrentMapCacheManager就不再起作用了。
- RedisCacheManager 帮我们创建 RedisCache 来作为缓存组件;RedisCache通过操作redis缓存数据
其实就可以直接使用了
有问题:保存对象时,保存的是序列化后的内容,希望它保存的是json的内容:
解决:
@Configuration
public class MyRedisConfig {
@Bean
public RedisTemplate<Object, Employee> empRedisTemplate(
RedisConnectionFactory redisConnectionFactory)
throws UnknownHostException {
RedisTemplate<Object, Employee> template = new RedisTemplate<Object, Employee>();
template.setConnectionFactory(redisConnectionFactory);
Jackson2JsonRedisSerializer<Employee> ser = new Jackson2JsonRedisSerializer<Employee>(Employee.class);
template.setDefaultSerializer(ser);
return template;
}
@Bean
public RedisCacheManager employeeCacheManager(RedisTemplate<Object, Employee> empRedisTemplate){
RedisCacheManager cacheManager = new RedisCacheManager(empRedisTemplate);
//key多了一个前缀
//使用前缀,默认会将CacheName作为key的前缀
cacheManager.setUsePrefix(true);
return cacheManager;
}
注意:每一个需要存储的对象,都需要设置对应cacheManager,其中的一个需要 @Primary 设置为默认的cacheManager。在使用缓存的时候,选择对应的cacheManager
@Configuration
public class MyRedisConfig {
@Bean
public RedisTemplate<Object, Employee> empRedisTemplate(
RedisConnectionFactory redisConnectionFactory)
throws UnknownHostException {
RedisTemplate<Object, Employee> template = new RedisTemplate<Object, Employee>();
template.setConnectionFactory(redisConnectionFactory);
Jackson2JsonRedisSerializer<Employee> ser = new Jackson2JsonRedisSerializer<Employee>(Employee.class);
template.setDefaultSerializer(ser);
return template;
}
@Bean
public RedisTemplate<Object, Department> deptRedisTemplate(
RedisConnectionFactory redisConnectionFactory)
throws UnknownHostException {
RedisTemplate<Object, Department> template = new RedisTemplate<Object, Department>();
template.setConnectionFactory(redisConnectionFactory);
Jackson2JsonRedisSerializer<Department> ser = new Jackson2JsonRedisSerializer<Department>(Department.class);
template.setDefaultSerializer(ser);
return template;
}
//CacheManagerCustomizers可以来定制缓存的一些规则
@Primary //将某个缓存管理器作为默认的
@Bean
public RedisCacheManager employeeCacheManager(RedisTemplate<Object, Employee> empRedisTemplate){
RedisCacheManager cacheManager = new RedisCacheManager(empRedisTemplate);
//key多了一个前缀
//使用前缀,默认会将CacheName作为key的前缀
cacheManager.setUsePrefix(true);
return cacheManager;
}
@Bean
public RedisCacheManager deptCacheManager(RedisTemplate<Object, Department> deptRedisTemplate){
RedisCacheManager cacheManager = new RedisCacheManager(deptRedisTemplate);
//key多了一个前缀
//使用前缀,默认会将CacheName作为key的前缀
cacheManager.setUsePrefix(true);
return cacheManager;
}
}
使用时选择对应的cacheManager
@Cacheable(cacheNames = "dept",cacheManager = "deptCacheManager")
public Department getDeptById(Integer id){
System.out.println("查询部门"+id);
Department department = departmentMapper.getDeptById(id);
return department;
}
ps:不使用 @Cacheable,手动缓存
@Qualifier("deptCacheManager")
@Autowired
RedisCacheManager deptCacheManager;
// 使用缓存管理器得到缓存,进行api调用
public Department getDeptById(Integer id){
System.out.println("查询部门"+id);
Department department = departmentMapper.getDeptById(id);
//获取某个缓存
Cache dept = deptCacheManager.getCache("dept");
dept.put(id,department);
return department;
}
网友评论