在SpringBoot中,当我们需要使用缓存时:
org.springframework.boot
spring-boot-starter-cache
@SpringBootApplication
@MapperScan(basePackages = {"com.lzj.cache.mapper"}) //扫描mapper接口
@EnableCaching //开启缓存
public class SpringbootCacheApplication {
public static void main(String[] args) {
SpringApplication.run(SpringbootCacheApplication.class, args);
}
}
@Cacheable 将方法的运行结果进行缓存;第二次再要相同的数据,直接从缓存中获取,不再调用方法;
@CacheEvict 移除缓存
@CachePut 修改了数据库的某个数据,同时更新缓存
下面对这三个注解进行解释:
@Cacheable:
Cacheable中的几个属性:
1、cacheNames/value:指定缓存组件的名字, 数组的方式,可以指定多个缓存组件名称。
2、key: 缓存数据使用的key,默认使用方法参数的值作为key。也可以自己指定,通过编写
SpEL指定key的值;如:#root.methodName 、#id等
3、keyGenerator: key的生成器,可以自己编写key的生成器组件。
注意:在使用时key和keyGenerator二选一。
4、cacheManager: 指定缓存管理器。
5、condition: 指定符合条件的情况下才缓存;如: condition = "#id>0" "#a0>1"才进行缓存
6、unless: 否定缓存; 当unless指定的条件为true,方法的返回值就不会缓存;
如:可以获取到结果进行判断unless = "#result == null " 当方法结果为null时,不缓存。
7、sync: 是否使用异步模式
@CacheEvit:
@CacheEvit:缓存清除
@CacheEvit和@Cacheable的相同属性就不再赘述。
1、allEntries = true 每次删除,将指定缓存中的所有数据全都删除
2、beforeInvocation=false ,缓存的清除是否是在方法之前执行,默认false, 即在方法之后清除,当方法执
行出现异常时,缓存不会清除。
beforeInvocation=true ,方法之前清除,无论方法执行是否出现异常,缓存都会清除。
@CachePut:
运行时机:先调用目标方法,将目标方法的结果缓存起来
属性与@Cacheable相同
此外还有@CacheConfig:可以标注在类上,抽取出相同的属性,简化代码。
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface CacheConfig {
String[] cacheNames() default {};
String keyGenerator() default "";
String cacheManager() default "";
String cacheResolver() default "";
}
@Caching组合注解:将@Cacheable、@CachePut、@CacheEvit组合使用,可以写出复杂的注解逻辑。
public @interface Caching {
Cacheable[] cacheable() default {};
CachePut[] put() default {};
CacheEvict[] evict() default {};
}
@Mapper
public interface EmployeeMapper {
@Select("SELECT * FROM employee WHERE id = #{id}")
Employee getEmpById(Integer id);
@Insert("INSERT INTO employee(lastName, email, gender, d_id) VALUES (#{lastName}, #{gender}, #{email}, #{dId})")
int insertEmp(Employee employee);
@Update("UPDATE employee set lastName=#{lastName}, email=#{email}, gender=#{gender},d_id=#{dId}")
int updateEmp(Employee employee);
@Delete("DELETE FROM employee WHERE id = #{id}")
int deleteEmp(Integer id);
@Select("SELECT * FROM employee WHERE lastName = #{lastName}")
Employee getEmpByLastName(String lastName);
}
@Service
public class EmpService {
@Autowired
private EmployeeMapper employeeMapper;
@Cacheable(cacheNames= {"emp"}, unless="#result == null") //当结果为空时不缓存
public Employee getEmp(Integer id) {
return employeeMapper.getEmpById(id);
}
//key="#result.id" 或者 key="#employee.id" 都行
@CachePut(cacheNames = {"emp"}, key = "#employee.id") //注意:修改缓存时,key要和放入的相同。getEmp方法缓存默认以id,该方法也应为id,否则不能成功更新缓存,而是以对象为id,将该方法的结果存入缓存。
public Employee updateEmp(Employee employee) {
System.out.println("updateEmp: " + employee);
employeeMapper.updateEmp(employee);
return employee;
}
@CacheEvict(cacheNames = {"emp"})
public void deleteEmp(Integer id) {
System.out.println("删除【" + id + "】");
employeeMapper.deleteEmp(id);
}
@RestController
public class EmpController {
@Autowired
private EmpService empService;
@GetMapping("/getEmp/{id}")
public Employee getEmp(@PathVariable("id") Integer id) {
return empService.getEmp(id);
}
@GetMapping("/updateEmp")
public Employee updateEmp(Employee employee) {
return empService.updateEmp(employee);
}
@GetMapping("/deleteEmp/{id}")
public String deleteEmp(@PathVariable("id") Integer id) {
empService.deleteEmp(id);
return "success";
}
}
当获取根据id获取Emp时,第一次会从数据库中查询数据,第二次则直接从缓存中获取。
第二次访问http://localhost:8080/getEmp/1:
没有出现SQL语句,说明从缓存中取得数据。
1、当我们导入了springboot-cache的启动器后,它里面有一个类:org.springframework.boot.autoconfigure.cache.CacheAutoConfiguration。该类生效的前提:容器中没有任何CacheManager。
于是它为我们导入了各种各样的缓存配置类:
static class CacheConfigurationImportSelector implements ImportSelector {
CacheConfigurationImportSelector() {
}
public String[] selectImports(AnnotationMetadata importingClassMetadata) {
CacheType[] types = CacheType.values();
String[] imports = new String[types.length];
for(int i = 0; i < types.length; ++i) {
imports[i] = CacheConfigurations.getConfigurationClass(types[i]);
}
return imports;
}
}
当打上断点调试后,可以得到如下缓存配置类:
0 = "org.springframework.boot.autoconfigure.cache.GenericCacheConfiguration"
1 = "org.springframework.boot.autoconfigure.cache.JCacheCacheConfiguration"
2 = "org.springframework.boot.autoconfigure.cache.EhCacheCacheConfiguration"
3 = "org.springframework.boot.autoconfigure.cache.HazelcastCacheConfiguration"
4 = "org.springframework.boot.autoconfigure.cache.InfinispanCacheConfiguration"
5 = "org.springframework.boot.autoconfigure.cache.CouchbaseCacheConfiguration"
6 = "org.springframework.boot.autoconfigure.cache.RedisCacheConfiguration"
7 = "org.springframework.boot.autoconfigure.cache.CaffeineCacheConfiguration"
8 = "org.springframework.boot.autoconfigure.cache.SimpleCacheConfiguration"【默认】
9 = "org.springframework.boot.autoconfigure.cache.NoOpCacheConfiguration"
默认是org.springframework.boot.autoconfigure.cache.SimpleCacheConfiguration生效。
2、SimpleCacheConfiguration给容器中注册了一个CacheManager,叫ConcurrentMapCacheManager ,在ConcurrentMapCacheManager中通过,ConcurrentMap
@Bean
public ConcurrentMapCacheManager cacheManager() {
ConcurrentMapCacheManager cacheManager = new ConcurrentMapCacheManager();
List cacheNames = this.cacheProperties.getCacheNames();
if (!cacheNames.isEmpty()) {
cacheManager.setCacheNames(cacheNames);
}
return (ConcurrentMapCacheManager)this.customizerInvoker.customize(cacheManager);
}
3、ConcurrentMapCacheManager的作用就是: 可以获取和创建ConcurrentMapCache类型的缓存组件;ConcurrentMapCache的作用将数据保存在ConcurrentMap中。
1、方法运行之前,先去通过CacheManager查询Cache(缓存组件),按照cacheNames指定的名字获取;第一次获取缓存如果没有Cache,会自动创建缓存组件。
public Cache getCache(String name) {
Cache cache = (Cache)this.cacheMap.get(name);
if (cache == null && this.dynamic) {
synchronized(this.cacheMap) {
cache = (Cache)this.cacheMap.get(name);
if (cache == null) {
cache = this.createConcurrentMapCache(name);
this.cacheMap.put(name, cache);
}
}
}
return cache;
}
2、去Cache中查找缓存的内容,使用一个Key, 默认使用的key是方法的参数。
key是按照某种策略生成的: 默认使用SimpleKeyGenerator生成key.
public static Object generateKey(Object... params) {
if (params.length == 0) {
return SimpleKey.EMPTY;
} else {
if (params.length == 1) {
Object param = params[0];
if (param != null && !param.getClass().isArray()) {
return param;
}
}
return new SimpleKey(params);
}
}
simpleKeyGenerator生成key的默认策略:
* 如果没有参数: key = new SimpleKey();
* 如果有一个参数: key = 参数的值
* 如果有多个参数: key=new SimpleKey(params)
3、没有查到缓存就调用目标方法
//通过key查询缓存中有无数据,没有的话再调用目标方法
protected Object lookup(Object key) {
return this.store.get(key);
}
4、将目标方法返回的结果放到缓存中
//将目标方法的返回值保存在缓存中
public void put(Object key, @Nullable Object value) {
this.store.put(key, this.toStoreValue(value));
}
总结:@Cacheable标注的方法执行前先检查缓存中有没有这个数据,默认按照参数的值作为key去查询缓存,如果没有就运行方法并将结果放入缓存。 以后再来调用就直接使用缓存中的数据。
除了使用默认的simpleKeyGenerator,还可以自己指定:
key的生成方式:
* 第一种: 使用SPEL指定key key="#root.methodName+'['+ #id +']'" ,使用getEmp[id]作为key
* 第二种: keyGenerator
@Bean("myKeyGenerator")
public KeyGenerator keyGenerator() {
return new KeyGenerator() {
@Override
public Object generate(Object o, Method method, Object... objects) {
return method.getName() + "[" + Arrays.asList(objects).toString() + "]";
}
};
使用时在方法上标注:@Cacheable(cacheNames= {"emp"}, keyGenerator = "myKeyGenerator")