Spring 从 3.1 开始就引入了对 Cache 的支持。定义了 org.springframework.cache.Cache
和 org.springframework.cache.CacheManager
接口来统一不同的缓存技术
。并支持使用 JCache(JSR-107)注解简化我们的开发。
其使用方法和原理都类似于 Spring 对事务管理
的支持。Spring Cache
是作用在方法上的
,其核心思想是,当我们在调用一个缓存方法时
会把该方法参数
和返回结果
作为一个键值对存在缓存中
。
Cache 接口
包含缓存的各种 *** 作
集合,你 *** 作缓存就是通过这个接口来 *** 作的。
Cache 接口下
Spring 提供了各种 xxxCache 的实现
,比如:RedisCache、EhCache、ConcurrentMapCache
CacheManager
定义了创建、配置、获取、管理和控制
多个唯一命名的 Cache
。这些 Cache
存在于 CacheManager 的上下文
中。
@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”) |
keyGenerator | key 的生成器。 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
标注缓存注解
即可
每次调用需要缓存功能的方法
时,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
就是参数 userName
,value
就是 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,则在方法还没有执行的时候就清空缓存,缺省情况下,如果方法执行抛出异常,则不会清空缓存
allEntries
是boolean类型
,表示是否需要清除缓存中的所有元素
。默认为false
,表示不需要
。当指定了allEntries
为true
时,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值 ,当然,你也可以找到更适合自己的方式去设定。
缓存的 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等注解的使用
欢迎分享,转载请注明来源:内存溢出
评论列表(0条)