SpringBoot使用Caffeine缓存

本文涉及的产品
RDS MySQL Serverless 基础系列,0.5-2RCU 50GB
云数据库 RDS MySQL,集群系列 2核4GB
推荐场景:
搭建个人博客
云数据库 RDS PostgreSQL,集群系列 2核4GB
简介: springboot使用caffeine

在系统中,有些数据,访问十分频繁,往往把这些数据放入分布式缓存中,但为了减少网络传输,加快响应速度,缓存分布式缓存读压力,会把这些数据缓存到本地JVM中,大多是先取本地缓存中,再取分布式缓存中的数据,Caffeine是一个高性能Java 缓存库,使用Java8对Guava缓存重写版本,在Spring Boot 2.0中将取代Guava

本文讲解SpringBoot缓存注解的理论和整合Caffeine的基本使用

一. SpringBoot缓存注解相关知识点

1. @Cacheable:

@Cacheable可以标记在一个方法上,也可以标记在一个类上。当标记在一个方法上时表示该方法是支持缓存的,当标记在一个类上时则表示该类所有的方法都是支持缓存的。对于一个支持缓存的方法,Spring会在其被调用后将其返回值缓存起来,以保证下次利用同样的参数来执行该方法时可以直接从缓存中获取结果,而不需要再次执行该方法。Spring在缓存方法的返回值时是以键值对进行缓存的,值就是方法的返回结果,至于键的话,Spring又支持两种策略,默认策略和自定义策略,这个稍后会进行说明。需要注意的是当一个支持缓存的方法在对象内部被调用时是不会触发缓存功能的。@Cacheable可以指定三个属性,value、key和condition。

参数 解释 例子
value 缓存的名称,在 spring 配置文件中定义,必须指定至少一个 例如:@Cacheable(value=”mycache”)
key 缓存的key,可以为空,如果指定要按照SpEL表达式编写,如不指定,则按照方法所有参数组合 @Cacheable(value=”testcache”,key=”#userName”)
condition 缓存的条件,可以为空,使用 SpEL 编写,返回 true 或者 false,只有为 true 才进行缓存 @Cacheable(value=”testcache”,condition=”#userName.length()>2”)

使用案例

 // key 是指传入时的参数
 @Cacheable(value="users", key="#id")
 public Integer find(Integer id) {
     return id;
 }
// 表示第一个参数
@Cacheable(value="users", key="#p0")
 public Long find(Long id) {
     return id;
 }
// 表示User中的id值
 @Cacheable(value="users", key="#user.id")
 public User find(User user) {
      return user;
 }
 // 表示第一个参数里的id属性值
 @Cacheable(value="users", key="#p0.id")
 public User find(User user) {
     return user;
 }

除了上面的案例使用方法,还有以下几种

属性名称 描述 示例
methodName 当前方法名 #root.methodName
method 当前方法 #root.method.name
target 当前被调用的对象 #root.target
targetClass 当前被调用的对象的class #root.targetClass
args 当前方法参数组成的数组 #root.args[0]
caches 当前被调用的方法使用的Cache #root.caches[0].name
  • condition属性指定发生的条件

有的时候我们可能并不希望缓存一个方法所有的返回结果。通过condition属性可以实现这一功能。condition属性默认为空,表示将缓存所有的调用情形。其值是通过SpringEL表达式来指定的,当为true时表示进行缓存处理;当为false时表示不进行缓存处理,即每次调用该方法时该方法都会执行一次。如下示例表示只有当user的id为偶数时才会进行缓存。

    // 根据条件判断是否缓存
   @Cacheable(value="users", key="#user.id", condition="#user.id%2==0")
   public User find(User user) {
      return user;
   }

2. CacheEvict

@CacheEvict是用来标注在需要清除缓存元素的方法或类上的。当标记在一个类上时表示其中所有的方法的执行都会触发缓存的清除操作。@CacheEvict可以指定的属性有value、key、condition、allEntries和beforeInvocation。其中value、key和condition的语义与@Cacheable对应的属性类似。即value表示清除操作是发生在哪些Cache上的(对应Cache的名称);key表示需要清除的是哪个key,如未指定则会使用默认策略生成的key;condition表示清除操作发生的条件。下面我们来介绍一下新出现的两个属性allEntries和beforeInvocation。

  • allEntries属性

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

   @CacheEvict(value="user", allEntries=true)
   public void delete(Integer id) {
      System.out.println(id);
   }
  • beforeInvocation属性

清除操作默认是在对应方法成功执行之后触发的,即方法如果因为抛出异常而未能成功返回时也不会触发清除操作。使用beforeInvocation可以改变触发清除操作的时间,当我们指定该属性值为true时,Spring会在调用该方法之前清除缓存中的指定元素。

   @CacheEvict(value="user", beforeInvocation=true)
   public void delete(Integer id) {
      System.out.println(id);
   }

3. @Caching

@Caching注解可以让我们在一个方法或者类上同时指定多个Spring Cache相关的注解。其拥有三个属性:cacheable、put和evict,分别用于指定@Cacheable、@CachePut和@CacheEvict。

   @Caching(
        cacheable = @Cacheable("user"),
        evict = {
                @CacheEvict(value = "user1", key = "#id"),
                @CacheEvict(value = "user", allEntries = true)})
   public Integer find(Integer id) {
      return id;
   }

4. 自定义注解

Spring允许我们在配置可缓存的方法时使用自定义的注解,前提是自定义的注解上必须使用对应的注解进行标注。如我们有如下这么一个使用@Cacheable进行标注的自定义注解

二. Caffeine相关知识点

Caffeine常用配置说明:

  • initialCapacity=[integer]: 初始的缓存空间大小
  • maximumSize=[long]: 缓存的最大条数
  • maximumWeight=[long]: 缓存的最大权重
  • expireAfterAccess=[duration]: 最后一次写入或访问后经过固定时间过期
  • expireAfterWrite=[duration]: 最后一次写入后经过固定时间过期
  • refreshAfterWrite=[duration]: 创建缓存或者最近一次更新缓存后经过固定的时间间隔,刷新缓存

注意点:

  • expireAfterWrite和expireAfterAccess同事存在时,以expireAfterWrite为准
  • maximumSize和maximumWeight不可以同时使用

配置案例:

spring:
# 配置缓存,初始缓存容量为10,最大容量为200,过期时间(这里配置写入后过期时间为3秒)
  cache:
    type: caffeine
    caffeine:
      spec: initialCapacity=10,maximumSize=200,expireAfterWrite=3s

三. SpringBoot集成Caffeine简单demo

1. pom文件

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.1.6.RELEASE</version>
        <relativePath/> <!-- lookup parent from repository -->
    </parent>
    <groupId>com.gj</groupId>
    <artifactId>boot-cache-demo</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <name>boot-cache-demo</name>
    <description>Demo project for Spring Boot</description>

    <properties>
        <java.version>1.8</java.version>
    </properties>

    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>

        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <optional>true</optional>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-jpa</artifactId>
        </dependency>
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
        </dependency>
        <dependency>
            <groupId>cn.gjing</groupId>
            <artifactId>tools-common</artifactId>
            <version>1.0.4</version>
        </dependency>
        <dependency>
            <groupId>cn.gjing</groupId>
            <artifactId>tools-starter-swagger</artifactId>
            <version>1.0.9</version>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-cache</artifactId>
        </dependency>
        <dependency>
            <groupId>com.github.ben-manes.caffeine</groupId>
            <artifactId>caffeine</artifactId>
            <version>2.7.0</version>
        </dependency>
    </dependencies>
    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
        </plugins>
    </build>
</project>

2. 配置文件

server:
  port: 8080
spring:
  application:
    name: springboot-cache-demo
# 配置数据库信息和连接池
  datasource:
    url: jdbc:mysql://127.0.0.1:3306/cache?characterEncoding=utf8&useSSL=false&serverTimezone=UTC
    password: root
    username: root
    driver-class-name: com.mysql.cj.jdbc.Driver
    type: com.zaxxer.hikari.HikariDataSource
    hikari:
      minimum-idle: 1
      maximum-pool-size: 15
      idle-timeout: 30000
      connection-timeout: 20000
# 开启jpa自动建表
  jpa:
    database: mysql
    hibernate:
      ddl-auto: update
    database-platform: org.hibernate.dialect.MySQL5InnoDBDialect
# 配置缓存,初始缓存容量,最大容量,过期时间(这里配置写入后过期时间)
  cache:
    type: caffeine
    caffeine:
      spec: initialCapacity=10,maximumSize=200,expireAfterWrite=3s
# 配置controller路径
swagger:
  base-package: com.gj.web
  title: springboot使用caffeine缓存

3. 启动类

@SpringBootApplication
@EnableSwagger
@EnableJpaAuditing
@EnableCaching
public class BootCacheDemoApplication {
    public static void main(String[] args) {
        SpringApplication.run(BootCacheDemoApplication.class, args);
    }
}

4. 定义一个实体

/**
 * @author Gjing
 **/
@Entity
@Table(name = "custom")
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
@EntityListeners(AuditingEntityListener.class)
public class Custom {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Integer id;

    @Column(name = "custom_name", columnDefinition = "varchar(20) not null comment '用户名'")
    private String customName;

    @Column(name = "custom_number", columnDefinition = "int not null comment '用户编号'")
    private Integer customNumber;

    @Column(name = "create_time", columnDefinition = "datetime")
    @CreatedDate
    private Date createTime;

    @Column(name = "update_time", columnDefinition = "datetime")
    @LastModifiedDate
    private Date updateTime;
}

5. 定义持久层接口

/**
 * @author Gjing
 **/
@Repository
public interface CustomRepository extends JpaRepository<Custom,Integer> {
    /**
     * 通过用户名查询
     * @param customName 用户名
     * @return 用户
     */
    Custom findByCustomName(String customName);
}

6. 定义service

/**
 * @author Gjing
 **/
@Service
@Slf4j
public class CustomService {
    @Resource
    private CustomRepository customRepository;


    /**
     * 获取一个用户
     *
     * @param customId 用户id
     * @return custom
     */
    @Cacheable(value = "user", key = "#customId")
    public Custom getCustom(Integer customId) {
        log.warn("通过数据库去查询,用户id为:{}", customId);
        return customRepository.findById(customId)
                .orElseThrow(() -> new UserNotFoundException("Users don't exist"));
    }

    @CacheEvict(value = "user", key = "#customId")
    public void deleteCustom(Integer customId) {
        Custom custom = customRepository.findById(customId)
                .orElseThrow(() -> new UserNotFoundException("Users don't exist"));
        customRepository.delete(custom);
    }

    public Boolean insertCustom(String customName) {
        Custom custom = customRepository.findByCustomName(customName);
        if (custom == null) {
            customRepository.save(Custom.builder()
                    .customName(customName)
                    .customNumber(Integer.valueOf(RandomUtil.generateNumber(6)))
                    .build());
            return true;
        }
        return false;
    }
}

7. 定义异常

/**
 * @author Gjing
 **/
public class UserNotFoundException extends RuntimeException{
    public UserNotFoundException(String message) {
        super(message);
    }
}

/**
 * @author Gjing
 **/
@RestControllerAdvice
class DemoExceptionHandler {
    @ExceptionHandler(UserNotFoundException.class)
    public ResponseEntity userNot(UserNotFoundException e) {
        return ResponseEntity.badRequest().body(ErrorResult.error(e.getMessage()));
    }
}

8. 定义接口

/**
 * @author Gjing
 **/
@RestController
public class CustomController {
    @Resource
    private CustomService customService;

    @PostMapping("/custom")
    @ApiOperation(value = "添加用户", httpMethod = "POST")
    @ApiImplicitParam(name = "customName", value = "用户名", required = true, dataType = "String", paramType = "Query")
    public ResponseEntity insertCustom(String customName) {
        Boolean insertCustom = customService.insertCustom(customName);
        if (insertCustom) {
            return ResponseEntity.ok("New successful");
        }
        return ResponseEntity.ok("Add failed, user already exists");
    }

    @GetMapping("/custom/{custom-id}")
    @ApiOperation(value = "查询指定用户", httpMethod = "GET")
    public ResponseEntity getCustom(@PathVariable("custom-id") Integer customId) {
        return ResponseEntity.ok(customService.getCustom(customId));
    }

    @DeleteMapping("/custom")
    @ApiOperation(value = "删除指定用户", httpMethod = "DELETE")
    @ApiImplicitParam(name = "customId", value = "用户id", required = true, dataType = "int", paramType = "Query")
    public ResponseEntity deleteCustom(Integer customId) {
        customService.deleteCustom(customId);
        return ResponseEntity.ok("Delete successful");
    }
}

启动后访问http://localhost:8080/swagger-ui.html即可测试,第一次获取数据会从数据库中查询,接下来会直接读取缓存直到缓存失效

Demo源码地址:点击前往

相关实践学习
如何在云端创建MySQL数据库
开始实验后,系统会自动创建一台自建MySQL的 源数据库 ECS 实例和一台 目标数据库 RDS。
全面了解阿里云能为你做什么
阿里云在全球各地部署高效节能的绿色数据中心,利用清洁计算为万物互联的新世界提供源源不断的能源动力,目前开服的区域包括中国(华北、华东、华南、香港)、新加坡、美国(美东、美西)、欧洲、中东、澳大利亚、日本。目前阿里云的产品涵盖弹性计算、数据库、存储与CDN、分析与搜索、云通信、网络、管理与监控、应用服务、互联网中间件、移动服务、视频服务等。通过本课程,来了解阿里云能够为你的业务带来哪些帮助 &nbsp; &nbsp; 相关的阿里云产品:云服务器ECS 云服务器 ECS(Elastic Compute Service)是一种弹性可伸缩的计算服务,助您降低 IT 成本,提升运维效率,使您更专注于核心业务创新。产品详情: https://www.aliyun.com/product/ecs
目录
相关文章
|
19天前
|
缓存 NoSQL Java
什么是缓存?如何在 Spring Boot 中使用缓存框架
什么是缓存?如何在 Spring Boot 中使用缓存框架
27 0
|
15天前
|
缓存 NoSQL Java
Spring Boot中的分布式缓存方案
Spring Boot提供了简便的方式来集成和使用分布式缓存。通过Redis和Memcached等缓存方案,可以显著提升应用的性能和扩展性。合理配置和优化缓存策略,可以有效避免常见的缓存问题,保证系统的稳定性和高效运行。
32 3
|
4月前
|
存储 缓存 Java
Java本地高性能缓存实践问题之如何定义Caffeine的缓存
Java本地高性能缓存实践问题之如何定义Caffeine的缓存
|
3月前
|
存储 缓存 Java
在Spring Boot中使用缓存的技术解析
通过利用Spring Boot中的缓存支持,开发者可以轻松地实现高效和可扩展的缓存策略,进而提升应用的性能和用户体验。Spring Boot的声明式缓存抽象和对多种缓存技术的支持,使得集成和使用缓存变得前所未有的简单。无论是在开发新应用还是优化现有应用,合理地使用缓存都是提高性能的有效手段。
47 1
|
3月前
|
存储 消息中间件 缓存
本地缓存Caffeine系列(三)
本地缓存Caffeine系列(三)
|
3月前
|
存储 缓存 NoSQL
本地缓存Caffeine系列(一)
本地缓存Caffeine系列(一)
消息中间件 缓存 监控
143 0
|
3月前
|
缓存 NoSQL 算法
本地缓存Caffeine系列(四)
本地缓存Caffeine系列(四)
|
3月前
|
缓存 算法
本地缓存Caffeine系列(五)
本地缓存Caffeine系列(五)
|
3月前
|
缓存 NoSQL 中间件
本地缓存Caffeine系列(二)
本地缓存Caffeine系列(二)