SpringBoot2.0-缓存注解的使用及缓存原理

在SpringBoot中,当我们需要使用缓存时:

第一步:在pom.xml文件中导入缓存依赖即可:


     org.springframework.boot
     spring-boot-starter-cache

第二步:在SpringBoot启动类中标注 @EnableCaching开启缓存

@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 {};
}

缓存注解使用实战:

1、empMapper

@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);
}

2、EmpService

@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);
    }

3、EmpController

@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。

SpringBoot2.0-缓存注解的使用及缓存原理_第1张图片

于是它为我们导入了各种各样的缓存配置类:

 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 cacheMap = new ConcurrentHashMap(16);以键值对存储缓存组件。key就是自己指定的CacheName,值就是ConcurrentMapCache。

    @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 store中。

@Cacheable运行流程:

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")

 

你可能感兴趣的:(SringBoot)