Cache- Spring Boot integration Caffeine incomplete guidance

insert image description here


Pre

Caching - Caffeine doesn't exactly point north


Glossary

  • @Cacheable : Indicates that the method supports caching. When the annotated method is called, if the corresponding key already exists in the cache, the method body will not be executed, but will be returned directly from the cache. When the method returns null, no cache operation will be performed.
  • @CachePut : Indicates that after the method is executed, its value will be updated to the cache as the latest result, and the method will be executed every time.
  • @CacheEvict : Indicates that after the method is executed, the cache clearing operation will be triggered.
  • @Caching : Used to combine the first three annotations, such as
    @Caching(cacheable = @Cacheable("CacheConstants.GET_USER"),
             evict = {
          
          @CacheEvict("CacheConstants.GET_DYNAMIC",allEntries = true)}
    public User find(Integer id) {
          
          
        return null;
    }
    
    

Annotation properties

insert image description here

  • cacheNames/value : The name of the cache component, that is, the name of the cache in the cacheManager.
  • key : The key used when caching data. By default, method parameter values ​​are used, and can also be written using SpEL expressions.
  • keyGenerator : use one of the two keys.
  • cacheManager : Specifies the cache manager to use.
  • condition : Check before the method execution starts, and cache if the condition is met
  • unless : Check after the method is executed, and do not cache if it matches unless
  • sync : Whether to use synchronous mode. If synchronous mode is used, when multiple threads load a key at the same time, other threads will be blocked.

Sync is turned on or off, and the performance in Cache and LoadingCache is inconsistent:

insert image description here

  • In Cache, sync indicates whether all threads need to wait synchronously
  • In LoadingCache, sync indicates whether to execute the annotated method when reading a non-existent/evicted key

guide steps

To integrate Caffeine cache in Spring Boot, you can follow the steps below:

Step 1: Add dependencies

pom.xmlAdd Caffeine dependencies in the file . Make sure to choose a version of Caffeine that is compatible with your Spring Boot version. Here is an example dependency:

<dependency>
    <groupId>com.github.ben-manes.caffeine</groupId>
    <artifactId>caffeine</artifactId>
    <version>2.9.0</version>
</dependency>

Step 2: Configure the cache

Add the configuration of Caffeine cache in Spring Boot's configuration file (eg application.propertiesor application.yml). Here is an example configuration:

application.properties:

spring.cache.type=caffeine
spring.cache.cache-names=myCache
spring.cache.caffeine.spec=maximumSize=100,expireAfterAccess=600s

application.yml:

spring:
  cache:
    type: caffeine
    cache-names: myCache
    caffeine:
      spec: maximumSize=100,expireAfterAccess=600s

This configures a named myCacheCaffeine cache with a maximum capacity of 100 and expires in 600 seconds after access.

Step 3: Use the cache

Where caching is required, use @Cacheableannotations to mark methods. For example, here's an example using caching:

import org.springframework.cache.annotation.Cacheable;
import org.springframework.stereotype.Service;

@Service
public class MyService {
    
    

    @Cacheable("myCache")
    public String getDataFromCache(String key) {
    
    
        // 如果缓存中存在数据,则直接返回
        // 如果缓存中不存在数据,则执行相应的业务逻辑,并将结果放入缓存
        return fetchDataFromDatabase(key);
    }

    private String fetchDataFromDatabase(String key) {
    
    
        // 执行获取数据的业务逻辑
        return "Data for key: " + key;
    }
}

In the above example, getDataFromCachethe method is @Cacheable("myCache")annotated to indicate that the result of the method will be cached in myCachea cache named

Now, when getDataFromCachethe method is called, it first checks whether the data corresponding to the given parameter exists in the cache. If it exists, the cached data will be returned directly; if not, the business logic in the method body will be executed and the result will be placed in the cache.

This is the basic step of integrating Caffeine cache in Spring Boot. We can further configure and customize according to our own needs.


Code

insert image description here

Next we use another way to achieve


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 https://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.7.13</version>
        <relativePath/> <!-- lookup parent from repository -->
    </parent>
    <groupId>com.artisan</groupId>
    <artifactId>caffeine-demo</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <name>caffeine-demo</name>
    <description>caffeine-demo</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.springframework.boot</groupId>
            <artifactId>spring-boot-starter</artifactId>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>


        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
        </dependency>
        <dependency>
            <groupId>junit</groupId>
            <artifactId>junit</artifactId>
        </dependency>


        <dependency>
            <groupId>com.github.ben-manes.caffeine</groupId>
            <artifactId>caffeine</artifactId>
            <!-- Java 8 users can continue to use version 2.x, which will be supported -->
            <version>2.9.3</version>
        </dependency>

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


    </dependencies>

    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
        </plugins>
    </build>

</project>

Config

package com.artisan.caffeinedemo.spring.config;

import com.artisan.caffeinedemo.spring.enums.CacheEnum;
import com.github.benmanes.caffeine.cache.Caffeine;
import org.springframework.cache.CacheManager;
import org.springframework.cache.annotation.EnableCaching;
import org.springframework.cache.caffeine.CaffeineCache;
import org.springframework.cache.support.SimpleCacheManager;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.TimeUnit;

/**
 * @author 小工匠
 * @version 1.0
 * @mark: show me the code , change the world
 */
@Configuration
@EnableCaching
public class CacheConfig {
    
    
    /**
     * Caffeine配置说明:
     * initialCapacity=[integer]: 初始的缓存空间大小
     * maximumSize=[long]: 缓存的最大条数
     * maximumWeight=[long]: 缓存的最大权重
     * expireAfterAccess=[duration]: 最后一次写入或访问后经过固定时间过期
     * expireAfterWrite=[duration]: 最后一次写入后经过固定时间过期
     * refreshAfterWrite=[duration]: 创建缓存或者最近一次更新缓存后经过固定的时间间隔,刷新缓存
     * weakKeys: 打开key的弱引用
     * weakValues:打开value的弱引用
     * softValues:打开value的软引用
     * recordStats:开发统计功能
     * 注意:
     * expireAfterWrite和expireAfterAccess同事存在时,以expireAfterWrite为准。
     * maximumSize和maximumWeight不可以同时使用
     * weakValues和softValues不可以同时使用
     */
    @Bean
    public CacheManager cacheManager() {
    
    
        SimpleCacheManager cacheManager = new SimpleCacheManager();
        List<CaffeineCache> list = new ArrayList<>();
        //循环添加枚举类中自定义的缓存,可以自定义
        for (CacheEnum cacheEnum : CacheEnum.values()) {
    
    
            list.add(new CaffeineCache(cacheEnum.getName(),
                    Caffeine.newBuilder()
                            .initialCapacity(50)
                            .maximumSize(1000)
                            .expireAfterAccess(cacheEnum.getExpireTime(), TimeUnit.SECONDS)
                            .build()));
        }
        cacheManager.setCaches(list);
        return cacheManager;
    }
}

Service

package com.artisan.caffeinedemo.spring.service;

import com.artisan.caffeinedemo.spring.constants.CacheConstants;
import com.artisan.caffeinedemo.spring.domains.Artisan;
import lombok.extern.slf4j.Slf4j;
import org.springframework.cache.annotation.CacheEvict;
import org.springframework.cache.annotation.CachePut;
import org.springframework.cache.annotation.Cacheable;
import org.springframework.stereotype.Service;

/**
 * @author 小工匠
 * @version 1.0
 * @mark: show me the code , change the world
 */

@Service
@Slf4j
public class ArtisanService {
    
    

    /**
     * @Cacheable注解是 通过 Spring AOP机制进行的,因此类内的调用将无法触发缓存操作,必须由外部进行调用,之前也算是踩了一遍坑,特别提醒一下
     */

    /**
     * cacheNames/value:缓存组件的名字,即cacheManager中缓存的名称。
     * key:缓存数据时使用的key。默认使用方法参数值,也可以使用SpEL表达式进行编写。
     * keyGenerator:和key二选一使用。
     * cacheManager:指定使用的缓存管理器。
     * condition:在方法执行开始前检查,在符合condition的情况下,进行缓存
     * unless:在方法执行完成后检查,在符合unless的情况下,不进行缓存
     * sync:是否使用同步模式。若使用同步模式,在多个线程同时对一个key进行load时,其他线程将被阻塞。
     */
    @Cacheable(value = CacheConstants.GET_USER, key = "'user'+#userId", sync = true)
    public Artisan getUserByUserId(Integer userId) {
    
    
        log.info("----------------触发DB查询----------------------------");
        // 模拟从DB查询数据
        Artisan artisan = Artisan.builder().id(userId).name("artisan-" + userId).address("China-" +  userId).build();
        return artisan;
    }


    @CacheEvict(value = CacheConstants.GET_USER, key = "'user'+#userId")
    public Integer cacheEvictTest(Integer userId) {
    
    
        log.info("cacheEvictTest 清除 {} 对应的缓存", userId);
        return 1;
    }

    @CachePut(value = CacheConstants.GET_USER, key = "'user'+#userId")
    public Artisan cachePut(Integer userId) {
    
    
        log.info("cachePut execute -------------------");
        Artisan artisan = Artisan.builder().id(userId).name("artisan1").address("China").build();
        return artisan;
    }

}
    

Cache Name Enum & Constant

package com.artisan.caffeinedemo.spring.enums;

import com.artisan.caffeinedemo.spring.constants.CacheConstants;
import lombok.Getter;

/**
 * @author 小工匠
 * @version 1.0
 * @mark: show me the code , change the world
 */
@Getter
public enum CacheEnum {
    
    

    MY_CACHE_1("工匠1", CacheConstants.DEFAULT_EXPIRES),
    MY_CACHE_2("工匠2", CacheConstants.EXPIRES_5_MIN),
    MY_CACHE_3(CacheConstants.GET_USER, CacheConstants.EXPIRES_10_MIN);

    private String name;
    private Integer expireTime;

    CacheEnum(String name, Integer expireTime) {
    
    
        this.name = name;
        this.expireTime = expireTime;
    }


}
    
package com.artisan.caffeinedemo.spring.constants;

/**
 * @author 小工匠
 * @version 1.0
 * @mark: show me the code , change the world
 */
public class CacheConstants {
    
    
    /**
     * 默认过期时间(配置类中使用的时间单位是秒,所以这里如 3*60 为3分钟)
     */
    public static final int DEFAULT_EXPIRES = 3 * 60;
    public static final int EXPIRES_5_MIN = 5 * 60;
    public static final int EXPIRES_10_MIN = 10 * 60;

    public static final String GET_USER = "GET:USER";
    public static final String GET_DYNAMIC = "GET:DYNAMIC";

}

Create a cache constant class, extract a layer of common constants, and reuse them. Here, you can also load these data through configuration files, such as @ConfigurationProperties and @Value

package com.artisan.caffeinedemo.spring.domains;

import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
import lombok.extern.slf4j.Slf4j;

/**
 * @author 小工匠
 * @version 1.0
 * @mark: show me the code , change the world
 */
@Slf4j
@Builder
@NoArgsConstructor
@AllArgsConstructor
@Data
public class Artisan {
    
    
    private Integer id;
    private String name;
    private String address;
}
    

test entry

package com.artisan.caffeinedemo.spring.controller;

import com.artisan.caffeinedemo.spring.domains.Artisan;
import com.artisan.caffeinedemo.spring.service.ArtisanService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.web.bind.annotation.*;

import javax.annotation.Resource;

/**
 * @author 小工匠
 * @version 1.0
 * @mark: show me the code , change the world
 */
@Slf4j
@RestController
@RequestMapping("/testCache")
public class CacheController {
    
    


    @Resource
    private ArtisanService artisanService;

    @RequestMapping("/get/{id}")
    @ResponseBody
    public String getArtisanById(@PathVariable Integer id) {
    
    
        Artisan artisan = artisanService.getUserByUserId(id);
        log.info("--------->{}", artisan.toString());
        return artisan.toString();
    }


    @RequestMapping("/evit/{id}")
    @ResponseBody
    public String cacheEvit(@PathVariable Integer id) {
    
    
        artisanService.cacheEvictTest(id);

        return "cacheEvit";
    }


    @RequestMapping("/put/{id}")
    @ResponseBody
    public String cachePut(@PathVariable Integer id) {
    
    
        Artisan artisan = artisanService.cachePut(id);

        return artisan.toString();
    }
}
    

test

According to the following interviews, the following conclusions can be drawn

http://127.0.0.1:8080/testCache/get/1  ----->  查询DB ,加入缓存

http://127.0.0.1:8080/testCache/get/1 ------>  直接从缓存中读取  


http://127.0.0.1:8080/testCache/evit/1 ------> 清除缓存的id=1的数据   


http://127.0.0.1:8080/testCache/get/1  ----->  查询DB ,加入缓存



http://127.0.0.1:8080/testCache/put/1  ----->  操作DB ,加入缓存

http://127.0.0.1:8080/testCache/put/2  ----->  操作DB ,加入缓存


http://127.0.0.1:8080/testCache/get/2 ------>  直接从缓存中读取  

insert image description here

Guess you like

Origin blog.csdn.net/yangshangwei/article/details/131667576