Spring的缓存技术
Spring的缓存抽象
Spring为不同的缓存提供了一层抽象。通过在Java的方法上面使用注解,加了注解的方法就会将该方法执行的结果缓存起来。在下一次使用相同参数调用该方法时就判断如果是缓存过的就将缓存结果返回,如果是没有缓存过的就会执行方法。所以Spring的缓存是基于AOP实现的。
Spring缓存注解
@EnableCaching
- 开启缓存支持
@Cacheable
这个注解的作用负责将返回的数据进行缓存,当我们第一次访问的时候,会将查出来的数据缓存起来,当之后在发起访问的时候,会先去查看缓存中是否存在该条数据。该注解有如下介个参数支持:
| value | 缓存的名称,在Spring配置文件中定义,必须指定至少一个 | 例如:
@Cacheable(value="mycache") |
| --- | --- | --- |
| key | 缓存的key,可以为空,如果指定要按照SpEL表达式编写,如果不指定,则缺省按照方法的所有参数进行组合 | 例如:
@Cacheable(value="mycache",key="#userName") |
| condition | 缓存的条件,可以为空,使用SpEL编写,返回true或者false,只有为true才进行缓存 | 例如:
@Cacheable(value="mycache",condition="#userName.length()>2") |
@CacheEvict
清空指定的缓存,可以清空value,也可以清空value.key的数据。它支持的参数如下:
| value | 缓存的名称,在Spring配置文件中定义,必须指定至少一个 | 例如:
@CachEvict(value="mycache") |
| --- | --- | --- |
| key | 缓存的key,可以为空,如果指定要按照SpEL表达式编写,如果不指定,则缺省按照方法的所有参数进行组合 | 例如:
@CachEvict(value="mycache",key="#userName") |
| condition | 缓存的条件,可以为空,使用SpEL编写,返回true或者false,只有为true才进行缓存 | 例如:
@CachEvict(value="mycache",condition="#userName.length()>2") |
| allEntries | 是否清空所有缓存内容,默认为false,如果指定为true,则方法调用后将立即清空所有缓存 | 例如:
@CachEvict(value="mycache",allEntries=true) |
| beforeInvocation | 是否在方法执行前就清空,默认为false,如果指定为true,则在方法还没有执行的时候就清空缓存,默认情况下,如果方法执行抛出异常,则不会清空缓存 | 例如:
@CachEvict(value="mycache",beforeInvocation=true) |
@CachePut
这个注解的作用和Cacheable类似,只不过它的作用相当于update。加上这个注解后,每次访问数据都不会从缓存中拿取数据,而是从业务方法里查询然后缓存。Cacheable和CachePut结合使用,因为当数据变化后,就需要将原来之前的数据给清空掉,否则的话就会产生脏数据。它的参数和Cacheable一样。
| value | 缓存的名称,在Spring配置文件中定义,必须指定至少一个 | 例如:
@CachePut(value="mycache") |
| --- | --- | --- |
| key | 缓存的key,可以为空,如果指定要按照SpEL表达式编写,如果不指定,则缺省按照方法的所有参数进行组合 | 例如:
@CachePut(value="mycache",key="#userName") |
| condition | 缓存的条件,可以为空,使用SpEL编写,返回true或者false,只有为true才进行缓存 | 例如:
@CachePut(value="mycache",condition="#userName.length()>2") |
@Caching
这个注解就是联合使用。
//
// Source code recreated from a .class file by IntelliJ IDEA
// (powered by Fernflower decompiler)
//
package org.springframework.cache.annotation;
import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Inherited;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
@Target({ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Inherited
@Documented
public @interface Caching {
Cacheable[] cacheable() default {};
CachePut[] put() default {};
CacheEvict[] evict() default {};
}
talk is cheap,show me the code
package com.lucky.spring.service.impl;
import com.lucky.spring.service.DataService;
import org.springframework.cache.annotation.CacheEvict;
import org.springframework.cache.annotation.Cacheable;
import org.springframework.stereotype.Service;
import java.util.ArrayList;
import java.util.List;
/**
* Created by zhangdd on 2020/8/9
*/
@Service
public class DataServiceImpl implements DataService {
@Cacheable(value = "person")
@Override
public List<String> findAllName() {
//模拟从数据库查询数据
List<String> data = new ArrayList<>();
data.add("张飞");
data.add("赵云");
data.add("许褚");
return data;
}
@CacheEvict(value = "person")
@Override
public void reloadPerson(){
}
}
- findAllName()方法模拟从数据库读取数据,并使用@Cacheable注解表明查询结果会缓存起来
- reloadPerson()方法,用来清理缓存
package com.lucky.spring;
import com.lucky.spring.service.DataService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.CommandLineRunner;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cache.annotation.EnableCaching;
@SpringBootApplication
@EnableCaching
@Slf4j
public class Application implements CommandLineRunner {
@Autowired
private DataService dataService;
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
@Override
public void run(String... args) throws Exception {
log.info("count {}", dataService.findAllName().size());
for (int i = 0; i < 5; i++) {
log.info("再次执行查询");
dataService.findAllName();
}
dataService.reloadPerson();
log.info("read after refresh");
dataService.findAllName().forEach(p -> {
log.info("person:{}", p);
});
}
}
打印结果如下:
2020-08-09 15:17:31.564 INFO 63660 --- [ main] c.l.spring.service.impl.DataServiceImpl : mock query from db
2020-08-09 15:17:31.565 INFO 63660 --- [ main] com.lucky.spring.Application : count 3
2020-08-09 15:17:31.565 INFO 63660 --- [ main] com.lucky.spring.Application : 再次执行查询
2020-08-09 15:17:31.565 INFO 63660 --- [ main] com.lucky.spring.Application : 再次执行查询
2020-08-09 15:17:31.566 INFO 63660 --- [ main] com.lucky.spring.Application : 再次执行查询
2020-08-09 15:17:31.566 INFO 63660 --- [ main] com.lucky.spring.Application : 再次执行查询
2020-08-09 15:17:31.566 INFO 63660 --- [ main] com.lucky.spring.Application : 再次执行查询
2020-08-09 15:17:31.568 INFO 63660 --- [ main] c.l.spring.service.impl.DataServiceImpl : clear cache data
2020-08-09 15:17:31.568 INFO 63660 --- [ main] com.lucky.spring.Application : read after refresh
2020-08-09 15:17:31.568 INFO 63660 --- [ main] c.l.spring.service.impl.DataServiceImpl : mock query from db
2020-08-09 15:17:31.568 INFO 63660 --- [ main] com.lucky.spring.Application : person:张飞
2020-08-09 15:17:31.568 INFO 63660 --- [ main] com.lucky.spring.Application : person:赵云
2020-08-09 15:17:31.568 INFO 63660 --- [ main] com.lucky.spring.Application : person:许褚
- 首先查询得到数据集合的大小,打印了 service方法里的内容
mock query from db
- 在继续循环中调用该方法,就没有在执行了,而是从缓存中获取到的数据
- 执行清理缓存后,再次查询,可以看到 service 方法里的内容又被执行打印
使用Spring的缓存抽象时使用Redis作为其缓存
直接使用Spring的缓存抽象,缓存数据是被缓存在JVM中的。在集群场景下,不同的节点上缓存的数据就会又不一致性。
添加相关依赖
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-cache</artifactId>
</dependency>
配置cache属性
spring.cache.type=redis
spring.cache.cache-names=person
#有效期5s
spring.cache.redis.time-to-live=5000
spring.cache.redis.cache-null-values=false
spring.redis.host=localhost
其他不变,看下redis里存储的数据。到这里 数据已经缓存到redis了。