Spring 缓存

本文最后更新于:2024年9月8日 晚上

Spring 缓存

  • 从3.1开始,Spring引入了对Cache的支持。

配置

pom.xml

1
2
3
4
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>

@EnableCaching

  • 在启动类上加注解 @EnableCaching
1
2
3
4
5
6
7
@SpringBootApplication
@EnableCaching
public class SellApplication {
public static void main(String[] args) {
SpringApplication.run(SellApplication.class, args);
}
}

使用

@Cacheable

  • 在支持Spring Cache的环境下,对于使用@Cacheable标注的方法,Spring在每次执行前都会检查Cache中是否存在相同key的缓存元素,如果存在就不再执行该方法,而是直接从缓存中获取结果进行返回,否则才会执行并将返回结果存入指定的缓存中。
  • @Cacheable可以标记在一个方法上,也可以标记在一个类上,当标记在一个方法上时表示该方法是支持缓存的,当标记在一个类上时则表示该类所有的方法都是支持缓存的。

value

  • value属性是必须指定的,其表示当前方法的返回值是会被缓存在哪个Cache上的,对应Cache的名称,其可以是一个Cache也可以是多个Cache,当需要指定多个Cache时其是一个数组。
1
2
3
4
5
6
7
8
@Cacheable("cache1")//Cache是发生在cache1上的。
public User find(Integer id) {
return null;
}
@Cacheable({"cache1", "cache2"})//Cache是发生在cache1和cache2上的。
public User find(Integer id) {
return null;
}

key

  • key属性是用来指定Spring缓存方法的返回结果时对应的key的,该属性支持SpringEL表达式,当我们没有指定该属性时,Spring将使用默认策略生成key
  • 自定义策略是指我们可以通过Spring的EL表达式来指定我们的key,EL表达式可以使用方法参数及它们对应的属性,使用方法参数时可以直接使用"#参数名”或者"#p参数index”
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
@Cacheable(value="users", key="#id")
public User find(Integer id) {
return null;
}
@Cacheable(value="users", key="#p0")
public User find(Integer id) {
return null;
}
@Cacheable(value="users", key="#user.id")
public User find(User user) {
returnnull;
}
@Cacheable(value="users", key="#p0.id")
public User find(User user) {
return null;
}
  • 除了上述使用方法参数作为key之外,Spring还为我们提供了一个root对象可以用来生成key
属性名称 描述 示例
methodName 当前方法名 #root.methodName
method 当前方法 #root.method.name
target 当前被调用的对象 #root.target
targetClass 当前被调用的对象的class #root.targetClass
args 当前方法参数组成的数组 #root.args[0]
caches 当前被调用的方法使用的Cache #root.caches[0].name
  • 如果要使用root对象的属性作为key时也可以将"#root”省略,因为Spring默认使用的就是root对象的属性。
1
2
3
4
@Cacheable(value={"users", "xxx"}, key="caches[1].name")
public User find(User user) {
return null;
}

condition

  • 有的时候可能并不希望缓存一个方法所有的返回结果,通过condition属性可以实现这一功能。
  • condition属性默认为空,表示将缓存所有的调用情形,其值是通过SpringEL表达式来指定的,当为true时表示进行缓存处理,当为false时表示不进行缓存处理,即每次调用该方法时该方法都会执行一次。
1
2
3
4
5
@Cacheable(value={"users"}, key="#user.id", condition="#user.id%2==0")
public User find(User user) {
System.out.println("find user by user " + user);
return user;
}
  • 只有当user的id为偶数时才会进行缓存。

Sync

  • Cache缓存的时候,如果多次同时调用,当没有命中的时候,会直接调用方法计算,这会导致重复计算,以及缓存没有生效,这时就需要采用同步的方式,在多线程环境下,将会加锁,同一时刻只会有一个线程在计算待缓存的value,其他线程阻塞等待。
  • 可以通过sync="true”属性来指定(默认为false)
1
2
3
4
@Cacheable({"users", sync="true")
public User find(User user) {
return null;
}
  • 注意:这个特性取决于底层的实现(在Cache Aop读取流程中并没有加锁处理)

@CachePut

  • @CachePut也可以声明一个方法支持缓存功能,与@Cacheable不同的是使用@CachePut标注的方法在执行前不会去检查缓存中是否存在之前执行过的结果,而是每次都会执行该方法,并将执行结果以键值对的形式存入指定的缓存中。
  • @CachePut也可以标注在类上和方法上,使用@CachePut时可以指定的属性与@Cacheable是一样的。
1
2
3
4
@CachePut("users")// 每次都会执行方法,并将结果存入指定的缓存中。
public User find(Integer id) {
return null;
}

@CacheEvict

  • @CacheEvict是用来标注在需要清除缓存元素的方法或类上的,当标记在一个类上时表示其中所有的方法的执行都会触发缓存的清除操作。

allEntries

  • allEntries是boolean类型,表示是否需要清除缓存中的所有元素,默认为false,表示不需要,当指定了allEntries为true时,Spring Cache将忽略指定的key,有的时候我们需要Cache清除所有的元素,这比一个一个清除元素更有效率。
1
2
3
4
@CacheEvict(value="users", allEntries=true)
public void delete(Integer id) {
System.out.println("delete user by id: " + id);
}

beforeInvocation

  • 是否应该在调用方法之前删除缓存,将此属性设置为 true,无论方法结果如何都会删除缓存,默认为 false,这意味着缓存删除操作将在成功调用方法后发生(即仅当调用未引发异常时)
1
2
3
4
@CacheEvict(value="users", beforeInvocation=true)
public void delete(Integer id) {
System.out.println("delete user by id: " + id);
}

@Caching

  • @Caching注解可以让我们在一个方法或者类上同时指定多个Spring Cache相关的注解。
  • 其拥有三个属性:cacheable,put和evict,分别用于指定@Cacheable,@CachePut和@CacheEvict
1
2
3
4
@Caching(cacheable = @Cacheable("users"), evict = { @CacheEvict("cache2"), @CacheEvict(value = "cache3", allEntries = true) })
public User find(Integer id) {
return null;
}

使用自定义注解

  • Spring允许在配置可缓存的方法时使用自定义的注解,前提是自定义的注解上必须使用对应的注解进行标注。
  • 例如一个使用@Cacheable进行标注的自定义注解。
1
2
3
4
5
6
@Target({ElementType.*TYPE*, ElementType.*METHOD*})
@Retention(RetentionPolicy.*RUNTIME*)
@Cacheable(value="users")
public @interface MyCacheable {

}
  • 那么在需要缓存的方法上使用@MyCacheable进行标注也可以达到同样的效果。
1
2
3
4
5
6
7
8
@MyCacheable
public User findById(Integer id) {
System.out.println("find user by id: " + id);
User user = new User();
user.setId(id);
user.setName("Name" + id);
return user;
}

键的生成策略

  • 键的生成策略有两种,一种是默认策略,一种是自定义策略。

默认策略

  • 默认的key生成策略是通过KeyGenerator生成的,其默认策略如下:
    1. 如果方法没有参数,则使用0作为key
    2. 如果只有一个参数的话则使用该参数作为key
    3. 如果参数多余一个的话则使用所有参数的hashCode作为key

自定义策略

  • 如果希望使用自定义的key生成策略,只需继承KeyGenerator,并声明为一个bean
1
2
3
4
5
6
7
@Component("customKeyGenerate")
public static class SelfKeyGenerate implements KeyGenerator {
@Override
public Object generate(Object target, Method method, Object... params) {
return target.getClass().getSimpleName() + "#" + method.getName() + "(" + JSON.toJSONString(params) + ")";
}
}
  • 然后在使用的地方,利用注解中的keyGenerator来指定key生成策略。
1
2
3
4
@Cacheable(value = "test", keyGenerator = "customKeyGenerate")
public String selfKey(int id) {
return "selfKey:" + id + " --> " + UUID.randomUUID().toString();
}

本博客所有文章除特别声明外,均采用 CC BY-SA 4.0 协议 ,转载请注明出处!