SpringBoot 缓存 @Cacheable、@CachePut、@CacheEvict

SpringBoot 缓存 @Cacheable、@CachePut、@CacheEvict,第1张

缓存介绍

Spring 从 3.1 开始就引入了对 Cache 的支持。定义了 org.springframework.cache.Cacheorg.springframework.cache.CacheManager 接口来统一不同的缓存技术。并支持使用 JCache(JSR-107)注解简化我们的开发。

其使用方法和原理都类似于 Spring 对事务管理的支持。Spring Cache 是作用在方法上的,其核心思想是,当我们在调用一个缓存方法时会把该方法参数返回结果作为一个键值对存在缓存中

Cache 和 CacheManager 接口说明

Cache 接口包含缓存的各种 *** 作集合,你 *** 作缓存就是通过这个接口来 *** 作的。

Cache 接口下 Spring 提供了各种 xxxCache 的实现,比如:RedisCache、EhCache、ConcurrentMapCache

CacheManager 定义了创建、配置、获取、管理和控制多个唯一命名的 Cache。这些 Cache 存在于 CacheManager 的上下文中。

@Cacheable、@CachePut、@CacheEvict 总结对比

@cacheable(“something");这个相当于save() *** 作,@cachePut相当于Update() *** 作,只要他标示的方法被调用,那么都会缓存起来,而@cacheable则是先看下有没已经缓存了,然后再选择是否执行方法@CacheEvict相当于 Delete() *** 作。用来清除缓存用的

@Cacheable 主要的参数
value缓存的名称,在 spring 配置文件中定义,必须指定至少一个@Cacheable(value=”mycache”) 或者 @Cacheable(value={”cache1”,”cache2”}
key缓存的 key,可以为空,如果指定要按照 SpEL 表达式编写,如果不指定,则缺省按照方法的所有参数进行组合@Cacheable(value=”testcache”,key=”#userName”)
condition缓存的条件,可以为空,使用 SpEL 编写,返回 true 或者 false,只有为 true 才进行缓存@Cacheable(value=”testcache”,condition=”#userName.length()>2”)
keyGeneratorkey 的生成器。 key 和 keyGenerator 二选一使用@Cacheable(value=”testcache”,keyGenerator =”myGenerator”)
cacheManager指定 缓存管理器。从 哪个缓存管理器里面获取缓存
unless否定缓存。当 unless 指定的条件为 true ,方法的返回值就 不会被缓存@Cacheable(value=”testcache”,unless =”#userName.length()>2”)
sync是否使用 异步模式@Cacheable(value=”testcache”,sync = true)


使用步骤

1 开启基于注解的缓存,使用 @EnableCaching 标识在 SpringBoot 的主启动类上

2 标注缓存注解即可

@Cacheable

每次调用需要缓存功能的方法时,Spring 会检查指定参数指定目标方法是否已经被调用过,如果有就直接从缓存中获取方法调用后的结果,如果没有就调用方法缓存结果返回给用户下次调用直接从缓存中获取

// @since 3.1  可以标注在方法上、类上  下同
@Target({ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Inherited
@Documented
public @interface Cacheable {
    // 缓存名称  可以写多个,key的真正组成,以cacheName为前缀,多个就会有多个key产生
    @AliasFor("cacheNames")
    String[] value() default {};
    @AliasFor("value")
    String[] cacheNames() default {};

    // 支持写SpEL,切可以使用#root,#参数名”或者“#p参数index”
    //详情去 https://blog.csdn.net/dalong_bamboo/article/details/103844076
    String key() default "";
    // Mutually exclusive:它和key属性互相排斥。请只使用一个,直接写bean的名字就可以
    String keyGenerator() default "";

   //用于选择使用哪个cacheManager
    String cacheManager() default "";
    //用户定义如何处理缓存,实现 org.springframework.cache.interceptor.CacheResolver接口
    String cacheResolver() default "";

    // 表示在哪种情况下才缓存结果,可使用SpEL,可以使用#root。  只有true时,才会作用在这个方法上
    String condition() default "";
    // 表示在哪种情况下不缓存结果,可以写SpEL #root,并且可以使用#result拿到方法返回值    经典值#result == null
    String unless() default "";
    
    // true:表示强制同步执行。(若多个线程试图为**同一个键**加载值,以同步的方式来进行目标方法的调用)
    // 同步的好处是:后一个线程会读取到前一个缓存的缓存数据,不用再查库了~~~ 
    // 默认是false,不开启同步one by one的
    // @since 4.3  注意是sync而不是Async
    // 它的解析依赖于Spring4.3提供的Cache.get(Object key, Callable valueLoader);方法
    boolean sync() default false;
}

属性

cacheNames/value:用来指定 缓存组件 的名字,不能为空

key :缓存数据时使用的 key,可以用它来指定。默认使用 方法的参数类型及参数值 。(这个 key 你可以使用 spEL 表达式来编写)

keyGenerator :key 的生成器。 key 和 keyGenerator 二选一使用

cacheManager :可以用来指定 缓存管理器。从 哪个缓存管理器里面获取缓存 

condition :可以用来指定 符合条件的情况下才缓存,默认为 空,既表示全部都加入缓存,支持SpEL

unless :否定缓存。当 unless 指定的条件为 true ,方法的返回值就 不会被缓存 。当然你也可以获取到结果进行判断。(通过 #result 获取方法结果)

sync :是否使用 异步模式 

cacheNames 指定缓存组件的名字,将方法的返回结果放在哪个缓存中,可以是数组的方式,支持指定多个缓存
key 缓存数据时使用的 key默认使用的是方法参数的值。可以使用 spEL 表达式去编写

keyGenerator key 的生成器,可以自己指定 key 的生成器,通过这个生成器来生成 key。

这样放入缓存中的 key 的生成规则就按照自定义的 keyGenerator 来生成。不过需要注意的是:

@Cacheable 的属性key 和 keyGenerator 使用的时候,一般二选一


condition 符合条件的情况下才缓存。方法返回的数据要不要缓存,可以做一个动态判断。

unless 否定缓存。当 unless 指定的条件为 true 方法的返回值 就不会被缓存

sync 是否使用异步模式。默认是方法执行完,以同步的方式将方法返回的结果存在缓存中

@Cacheable(value=”accountCache”),这个注释的意思是,当调用这个方法的时候,会从一个名叫 accountCache 的缓存中查询,如果没有,则执行实际的方法(即查询数据库),并将执行的结果存入缓存中,否则返回缓存中的对象。这里的缓存中的key就是参数 userNamevalue 就是 Account 对象。“accountCache”缓存是在 spring*.xml 中定义的名称。

@Cacheable(value="accountCache")// 使用了一个缓存名叫 accountCache 
public Account getAccountByName(String userName) {
     // 方法内部实现不考虑缓存逻辑,直接实现业务
     System.out.println("real query account."+userName); 
     return getFromDB(userName); 
} 
@CachePut

主要针对方法配置,能够根据方法的请求参数对其结果进行缓存,和 @Cacheable 不同的是,它每次都会触发真实方法的调用

@Target({ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Inherited
@Documented
public @interface Cacheable {

	@AliasFor("cacheNames")
	String[] value() default {};
	@AliasFor("value")
	String[] cacheNames() default {};

	// 注意:它和上面区别是。此处key它还能使用#result
	String key() default "";
	String keyGenerator() default "";

	String cacheManager() default "";
	String cacheResolver() default "";

	String condition() default "";
	String unless() default "";
}


@CachePut 注释,这个注释可以确保方法被执行,同时方法的返回值也被记录到缓存中,实现缓存与数据库的同步更新

@CachePut(value="accountCache",key="#account.getName()")// 更新accountCache 缓存
public Account updateAccount(Account account) { 
   return updateDB(account); 
} 
@CacheEvict

针对方法配置,能够根据一定的条件对缓存进行清空

@Target({ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Inherited
@Documented
public @interface Cacheable {

	@AliasFor("cacheNames")
	String[] value() default {};
	@AliasFor("value")
	String[] cacheNames() default {};

	// 它也能使用#result
	String key() default "";
	String keyGenerator() default "";

	String cacheManager() default "";
	String cacheResolver() default "";
	String condition() default "";

	// 是否把上面cacheNames指定的所有的缓存都清除掉,默认false
	boolean allEntries() default false;
	// 是否让清理缓存动作在目标方法之前执行,默认是false(在目标方法之后执行)
	// 注意:若在之后执行的话,目标方法一旦抛出异常了,那缓存就清理不掉了~~~~
	boolean beforeInvocation() default false;

}

value:缓存位置名称,不能为空,同上
key:缓存的key,默认为空,同上
condition:触发条件,只有满足条件的情况才会清除缓存,默认为空,支持SpEL
allEntries:true表示清除value中的全部缓存,默认为false
beforeInvocation:是否在方法执行前就清空,缺省为 false,如果指定为 true,则在方法还没有执行的时候就清空缓存,缺省情况下,如果方法执行抛出异常,则不会清空缓存

allEntriesboolean类型,表示是否需要清除缓存中的所有元素默认为false,表示不需要。当指定了allEntriestrue时,Spring Cache将忽略指定的key。有的时候我们需要Cache一下清除所有的元素,这比一个一个清除元素更有效率。

@CacheEvict(value="users", allEntries=true)
public void delete(Integer id) {
   System.out.println("delete user by id: " + id);
}
//清除掉指定key的缓存  
@CacheEvict(value="andCache",key="#user.userId + 'findById'")  
public void modifyUserRole(SystemUser user) {  
         System.out.println("hello andCache delete"+user.getUserId());  
}  
  
//清除掉全部缓存  
@CacheEvict(value="andCache",allEntries=true)  
public final void setReservedUsers(String[] reservedUsers) {  
    System.out.println("hello andCache deleteall");  
}

一般来说,我们的更新 *** 作只需要刷新缓存中某一个值,所以定义缓存的key值的方式就很重要,最好是能够唯一,因为这样可以准确的清除掉特定的缓存,而不会影响到其它缓存值

比如我这里针对用户的 *** 作,使用(userId+方法名称)的方式设定key值 ,当然,你也可以找到更适合自己的方式去设定。

spEL 编写 key

缓存的 key 支持使用 spEL 表达式去编写,下面总结一下使用 spEL 去编写 key 可以用的一些元数据:

@CacheEvict(value = "user", key = "#user.id", condition = "#root.target.canCache() and #root.caches[0].get(#user.id).get().username ne #user.username", beforeInvocation = true)  
public void conditionUpdate(User user) 

//@Cacheable将在执行方法之前( #result还拿不到返回值)判断condition,如果返回true,则查缓存; 
@Cacheable(value = "user", key = "#id", condition = "#id lt 10")
public User conditionFindById(final Long id)  

//@CachePut将在执行完方法后(#result就能拿到返回值了)判断condition,如果返回true,则放入缓存; 
@CachePut(value = "user", key = "#id", condition = "#result.username ne 'zhang'")  
public User conditionSave(final User user)   

//@CachePut将在执行完方法后(#result就能拿到返回值了)判断unless,如果返回false,则放入缓存;(即跟condition相反)
@CachePut(value = "user", key = "#user.id", unless = "#result.username eq 'zhang'")
public User conditionSave2(final User user)  

//@CacheEvict, beforeInvocation=false表示在方法执行之后调用(#result能拿到返回值了);且判断condition,如果返回true,则移除缓存;
@CacheEvict(value = "user", key = "#user.id", beforeInvocation = false, condition = "#result.username ne 'zhang'")  
public User conditionDelete(final User user)   
@Caching 、@CacheConfig 和 自定义注解封装 @Caching

可能组合多个Cache注解使用;比如用户新增成功后,我们要添加id–>user;username—>user;email—>user的缓存;此时就需要@Caching组合多个注解标签了。

@Caching(put = {
@CachePut(value = "user", key = "#user.id"),
@CachePut(value = "user", key = "#user.username"),
@CachePut(value = "user", key = "#user.email")
})
public User save(User user) {
@CacheConfig

所有的@Cacheable()里面都有一个name=“xxx”的属性,这显然如果方法多了,写起来也是挺累的,如果可以一次性声明完 那就省事了,
所以,有了@CacheConfig这个配置, @CacheConfig is a class-level 注解在类上 annotation that allows to share the cache names, 不过不用担心,如果你在你的方法写别的名字,那么依然以方法的名字为准

@CacheConfig("books")
public class BookRepositoryImpl implements BookRepository {
 
    @Cacheable
    public Book findBook(ISBN isbn) {...}
}
自定义注解

@ Cacheable ( name = "remote" , key = "'USER_NAME_'+#args[0]" ,conditional=“xxx”,allEntries=true,beforeInvocation=true ) ,像这样的配置就很长

@Cacheable(name = "book", key="#isbn",conditional=“xxx”,allEntries=true,beforeInvocation=true) 
public Book findBook(ISBN isbn, boolean checkWarehouse, boolean includeUsed)

这样的配置很长,而且有可能声明在很多个方法的,所以我们很想精简点,容易配置些。所以 自定义一个包含 @Cacheable 的注解

@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.METHOD})
@Cacheable(cacheNames="books", key="#isbn")
public @interface findBookByIsbn {
}
@Caching(put = {
@CachePut(value = "user", key = "#user.id"),
@CachePut(value = "user", key = "#user.username"),
@CachePut(value = "user", key = "#user.email")
})
@Target({ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Inherited
public @interface UserSaveCache {
}

整个代码显得比较干净

@findBookByIsbnervice
//@UserSaveCache 
public Book findBook(ISBN isbn, boolean checkWarehouse, boolean includeUsed)
自定义 CacheConfiguration
@Configuration
public class CacheConfiguration {

    @Bean
    public RedisCacheManagerBuilderCustomizer redisCacheManagerBuilderCustomizer() {
        return (builder) -> builder
                .withCacheConfiguration("cache_key",
                        RedisCacheConfiguration.defaultCacheConfig()
                                .entryTtl(Duration.ofMinutes(3))
                                .disableCachingNullValues()
                                .computePrefixWith(name -> name + ":")  //变双冒号为单冒号
                                .serializeValuesWith(SerializationPair.fromSerializer(new Jackson2JsonRedisSerializer<>(List.class))));
    }
}

RedisConfig

@CacheConfig
@Configuration
@EnableCaching
public class RedisConfig extends CachingConfigurerSupport{

    @Bean
    public KeyGenerator keyGenerator() {
        return new KeyGenerator() {
            //为给定的方法及其参数生成一个键
            //格式为:com.frog.mvcdemo.controller.FrogTestController-show-[params]
            @Override
            public Object generate(Object target, Method method, Object... params) {
                StringBuffer sb = new StringBuffer();
                sb.append(target.getClass().getName());//类名
                sb.append("-");
                sb.append(method.getName());//方法名
                sb.append("-");
                for (Object param: params ) {
                    sb.append(param.toString());//参数
                }
                return sb.toString();
            }
        };
    }

    @Bean
    public CacheManager cacheManager(RedisTemplate redisTemplate) {
        RedisCacheManager rcm = new RedisCacheManager(redisTemplate);
        //设置默认的过期时间(以秒为单位)
        rcm.setDefaultExpiration(600);
        //rcm.setExpires();设置缓存区域(按key)的过期时间(以秒为单位)
        return rcm;
    }

    @Bean
    public RedisTemplate<String, String> redisTemplate(RedisConnectionFactory factory) {
        StringRedisTemplate template = new StringRedisTemplate(factory);
        Jackson2JsonRedisSerializer jackson2JsonRedisSerializer = new Jackson2JsonRedisSerializer(Object.class);
        ObjectMapper om = new ObjectMapper();
        om.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
        om.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL);
        jackson2JsonRedisSerializer.setObjectMapper(om);
        template.setValueSerializer(jackson2JsonRedisSerializer);
        template.afterPropertiesSet();
        return template;
    }
}
结合 redis

spring boot 整合 redis,使用@Cacheable,@CacheEvict,@CachePut,jedisPool *** 作redis数据库
SpringBoot使用Redis做缓存,@Cacheable、@CachePut、@CacheEvict等注解的使用

欢迎分享,转载请注明来源:内存溢出

原文地址: http://outofmemory.cn/langs/723237.html

(0)
打赏 微信扫一扫 微信扫一扫 支付宝扫一扫 支付宝扫一扫
上一篇 2022-04-26
下一篇 2022-04-26

发表评论

登录后才能评论

评论列表(0条)

保存