SpringBoot use Caffeine cache

In the system, some of the data, access is very frequent, often put these data into a distributed cache, but in order to reduce network traffic, faster response speed, cache distributed cache read pressure, these data will be cached locally on the JVM, mostly is to take the local cache, then take the data in the distributed cache, the Caffeine is a high performance cache Java library using Guava Java8 overridden version of the cache, substituted in the Guava Spring Boot 2.0

This article explains the basic use of the cache SpringBoot annotation theory and the integration of Caffeine

A. SpringBoot cache annotations related knowledge

1. @Cacheable:

@Cacheable may be labeled in a way, it may also be marked on a class. It indicates that the method is to support the cache, when a mark indicates the class class methods are all supported in the cache when a tag method. For a way to support caching, Spring will return value cached after it is called, to ensure you can get the next time the method is performed using the same parameters directly from the cache As a result, without the need to execute the method again. Spring in the return value caching method is based on the return key caching, value is the result of the method, as key words, Spring and supports two strategies, the default policy and custom policy, this will be explained later. Note that when one supports caching method is called inside the object will not trigger a cache function. @Cacheable can specify three attributes, value, key and condition.

parameter Explanation example
value Cache names defined in the spring configuration file, you must specify at least one For example: @Cacheable (value = "mycache")
key Cache key, can be empty, if you want to specify SpEL prepared in accordance with the expression, if not specified, the combination of all parameters in accordance with the method @Cacheable(value=”testcache”,key=”#userName”)
condition Buffer conditions, can be empty, the SpEL write, returns true or false, it is true only for caching @Cacheable(value=”testcache”,condition=”#userName.length()>2”)

Use case :

 // 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;
 }

In addition to the above case using the method, as well as the following :

Property Name description Examples
methodName The current method name #root.methodName
method The current method #root.method.name
target The current object has been called #root.target
targetClass The current class is called an object #root.targetClass
args The current method parameter array consisting of #root.args[0]
caches The current method is called Cache used #root.caches[0].name
  • condition attribute specified condition occurs

Sometimes we may not want to cache a method to return all of the results. This functionality can be achieved through the condition property. condition property defaults to blank, all calls will be cached situations. The value is specified by SpringEL expression that when the cache processing is true; represents not to cache, i.e., the method will be executed each time the method is called once when to false. Following example represents only when an even number of id user will be cached.

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

2. CacheEvict

@CacheEvict is used to mark on the need to clear the cache element method or the like. Which represents a method of performing all of the triggers cleanup when the cache tag in a class. @CacheEvict can specify attributes value, key, condition, allEntries and beforeInvocation. Wherein @Cacheable similar semantics corresponding attribute value, key, and the condition. That value represents the cleanup operation is (name of the corresponding Cache) which takes place on the Cache; key which expressed the need to clear the key, if not specified then the default key strategies generated; condition clears operating conditions occur. Here we introduce two properties allEntries and beforeInvocation emerging.

  • allEntries property

allEntries type boolean indicating whether all the elements need to clear the cache. The default is false, meaning not needed. When you specify allEntries is true, Spring Cache ignores the specified key. Sometimes we need to look at Cache to clear all of the elements, than a clear element of a more efficient.

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

Clear operation default after successful execution of the corresponding method is triggered, it will not trigger the clearing operation that is because if a method throws an exception and failed to return. Use beforeInvocation to change the trigger cleanup time, when we specify the property value is true, Spring will clear the specified elements in the cache before calling the method.

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

3. @Caching

@Caching annotation allows us to specify multiple annotations Spring Cache related in one way or class. It has three attributes: cacheable, put and evict, are used to specify @ Cacheable, @ CachePut and @CacheEvict.

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

4. custom annotation

Spring allows us to use custom annotations by the cache, a method is provided on a custom annotations must be labeled using the corresponding annotations. If we have such a following use @Cacheable customized annotation labels

Two. Caffeine-related knowledge

Caffeine common configuration instructions:

  • initialCapacity = [integer]: the initial buffer space
  • maximumSize = [long]: The maximum number of cache
  • maximumWeight = [long]: maximum weight cache weight
  • expireAfterAccess = [duration]: after the last write access after a fixed time has expired or
  • expireAfterWrite = [duration]: elapsed after writing the last fixed time expired
  • refreshAfterWrite = [duration]: After creating the cache or the most recent update the cache after a fixed time interval, refresh the cache

important point:

  • When expireAfterWrite and expireAfterAccess colleagues present to prevail expireAfterWrite
  • maximumSize and can not be used simultaneously maximumWeight

Configuration Examples:

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

sa. SpringBoot integrated Caffeine simple demo

1. pom file

<?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. Profiles

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. Start class

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

4. Define an entity

/**
 * @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;
}

The definition of the interface persistence layer

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

6. Define 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. defined exceptions

/**
 * @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. The definition of the interface

/**
 * @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");
    }
}

After starting the visit http: // localhost: 8080 / swagger -ui.html to test, for the first time will get data from the database query, the next will directly read cache until the cache invalidation

Demo Source Address: Click Go

Guess you like

Origin yq.aliyun.com/articles/706596