Caffeine缓存

==作者:YB-Chi==

[toc]

Caffeine介绍

Caffeine是基于Java 1.8的高性能本地缓存库,由Guava改进而来,而且在Spring5开始的默认缓存实现就将Caffeine代替原来的Google Guava,官方说明指出,其缓存命中率已经接近最优值。实际上Caffeine这样的本地缓存和ConcurrentMap很像,即支持并发,并且支持O(1)时间复杂度的数据存取。二者的主要区别在于:

ConcurrentMap将存储所有存入的数据,直到你显式将其移除;
Caffeine将通过给定的配置,自动移除“不常用”的数据,以保持内存的合理占用。
因此,一种更好的理解方式是:Cache是一种带有存储和移除策略的Map。

引入依赖

1
2
3
4
5
<dependency>
<groupId>com.github.ben-manes.caffeine</groupId>
<artifactId>caffeine</artifactId>
<version>3.1.1</version>
</dependency>

集成springboot

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

Caffeine配置说明

参数 类型 描述
initialCapacity integer 初始的缓存空间大小
maximumSize long 缓存的最大条数
maximumWeight long 缓存的最大权重
expireAfterAccess duration 最后一次写入或访问后,指定经过多长的时间过期
expireAfterWrite duration 最后一次写入后,指定经过多长的时间缓存过期
refreshAfterWrite duration 创建缓存或者最近一次更新缓存后,经过指定的时间间隔后刷新缓存
weakKeys boolean 打开 key 的弱引用
weakValues boolean 打开 value 的弱引用
softValues boolean 打开 value 的软引用
recordStats - 开发统计功能

注意:

  • weakValuessoftValues 不可以同时使用。
  • maximumSizemaximumWeight 不可以同时使用。
  • expireAfterWriteexpireAfterAccess 同时存在时,以 expireAfterWrite 为准。

注解说明

  • @EnableCaching:开启基于注解的缓存-
  • @Cacheable:表示该方法支持缓存。当调用被注解的方法时,如果对应的键已经存在缓存,则不再执行方法体,而从缓存中直接返回。当方法返回null时,将不进行缓存操作。
  • @CachePut:表示执行该方法后,其值将作为最新结果更新到缓存中,每次都会执行该方法。
  • @CacheEvict:表示执行该方法后,将触发缓存清除操作。
  • @Caching:用于组合前三个注解
@EnableCaching
1
2
3
4
5
6
7
8
9
10
11
12
13
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cache.annotation.EnableCaching;

@SpringBootApplication
@EnableCaching
public class SpringbootCacheApplication {

public static void main(String[] args) {
SpringApplication.run(SpringbootCacheApplication.class, args);
}

}
@Cacheable
  • cacheNames/value:指定缓存组件的名字,数组形式
  • key:缓存数据使用的key,确定缓存可以用唯一key进行指定;eg:编写SpEL; #id,参数id的值 ,,#a0(第一个参数), #p0(和a0的一样的意义) ,#root.args[0]
  • keyGenerator:key的生成器;可以自己指定key的生成器的组件id(注意: key/keyGenerator:二选一使用;不能同时使用)
  • cacheManager:指定缓存管理器;或者cacheResolver指定获取解析器
  • condition:指定符合条件的情况下才缓存;使用SpEl表达式,eg:condition = “#a0>1”:第一个参数的值>1的时候才进行缓存
  • unless:否定缓存;当unless指定的条件为true,方法的返回值就不会被缓存;eg:unless = “#a0!=2”:如果第一个参数的值不是2,结果不缓存;
  • sync:是否使用异步模式
1
2
3
4
5
6
7
8
9
10
11
12
@Cacheable(
value = {"emp"},
/*keyGenerator = "myKeyGenerator",*/
key = "#id",
condition = "#a0>=1",
unless = "#a0!=2"
)
public Employee getEmp(Integer id) {
Employee employee = this.employeeMapper.getEmpById(id);
LOG.info("查询{}号员工数据",id);
return employee;
}

这里也可以使用自定义的keyGenerator,使用属性keyGenerator = “myKeyGenerator

定义一个@Bean类,将KeyGenerator添加到Spring容器

1
2
3
4
5
6
7
8
9
10
11
12
13
@Configuration
public class CacheConfig {

@Bean(value = {"myKeyGenerator"})
public KeyGenerator keyGenerator(){
return new KeyGenerator() {
@Override
public Object generate(Object target, Method method, Object... params) {
return method.getName()+"["+ Arrays.asList(params).toString()+"]";
}
};
}
}
@CachePut

@CachePut注解也是一个用来缓存的注解,不过缓存和@Cacheable有明显的区别是既调用方法,又更新缓存数据,也就是执行方法操作之后再来同步更新缓存,所以这个主键常用于更新操作,也可以用于查询,主键属性和@Cacheable有很多类似的,详情参看@CachePut源码

1
2
3
4
5
6
7
8
9
10
/**
* @CachePut:既调用方法,又更新缓存数据;同步更新缓存
* 修改了数据,同时更新缓存
*/
@CachePut(value = {"emp"}, key = "#result.id")
public Employee updateEmp(Employee employee){
employeeMapper.updateEmp(employee);
LOG.info("更新{}号员工数据",employee.getId());
return employee;
}
@CacheEvict
  • key:指定要清除的数据
  • allEntries = true:指定清除这个缓存中所有的数据
  • beforeInvocation = false:默认代表缓存清除操作是在方法执行之后执行
  • beforeInvocation = true:代表清除缓存操作是在方法运行之前执行
1
2
3
4
5
@CacheEvict(value = {"emp"}, beforeInvocation = true,key="#id")
public void deleteEmp(Integer id){
employeeMapper.deleteEmpById(id);
//int i = 10/0;
}
@Caching

@Caching 用于定义复杂的缓存规则,可以集成@Cacheable和 @CachePut

1
2
3
4
5
6
7
8
9
10
11
12
13
// @Caching 定义复杂的缓存规则
@Caching(
cacheable = {
@Cacheable(/*value={"emp"},*/key = "#lastName")
},
put = {
@CachePut(/*value={"emp"},*/key = "#result.id"),
@CachePut(/*value={"emp"},*/key = "#result.email")
}
)
public Employee getEmpByLastName(String lastName){
return employeeMapper.getEmpByLastName(lastName);
}

附录拓展:SpEL表达式用法

Cache SpEL available metadata

名称 位置 描述 示例
methodName root对象 当前被调用的方法名 #root.methodname
method root对象 当前被调用的方法 #root.method.name
target root对象 当前被调用的目标对象实例 #root.target
targetClass root对象 当前被调用的目标对象的类 #root.targetClass
args root对象 当前被调用的方法的参数列表 #root.args[0]
caches root对象 当前方法调用使用的缓存列表 #root.caches[0].name
argument Name 执行上下文(avaluation context) 当前被调用的方法的参数,如findArtisan(Artisan artisan),可以通过#artsian.id获得参数 #artsian.id
result 执行上下文(evaluation context) 方法执行后的返回值(仅当方法执行后的判断有效,如 unless cacheEvict的beforeInvocation=false) #result

实战

@Cacheable,查询用户功能,走了缓存就不走方法

1
2
3
4
5
6
@Cacheable(value = "user", key = "#user.id")
public User getUserById(User user) {
User userByUserId = userMapper.findUserByUserId(user.getId());
System.out.println("走了数据库:" + user.getId());
return userByUserId;
}

@CachePut,注册用户功能,走方法也更新缓存

1
2
3
4
5
6
7
8
9
10
11
12
13
@CachePut(value = "user", key = "#user.id")
public User register(User user) {
user.setCreateTime(new Date());
user.setStatus("0");
//查询是否有相同用户名的用户
List<User> userList = userMapper.findByUsername(user.getUserName());
if (userList.size() > 0) return null;
//将密码进行加密操作
String encodePassword = passwordEncoder.encode(user.getPassword());
user.setPassword(encodePassword);
userMapper.register(user);
return user;
}

@CacheEvict,标记用户删除状态,删除指定缓存,默认缓存清除操作是在方法执行之后执行

1
2
3
4
5
@CacheEvict(value = "user", key = "#user.id")
public void markUserDelByUserId(User user) {
System.out.println("删除id:" + user.getId());
userMapper.markUserDelByUserId(user.getId());
}
文章作者: CYBSKY
文章链接: https://cybsky.top/2022/09/07/cyb-mds/java/code/Caffeine/
版权声明: 本博客所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议。转载请注明来自 CYBSKY