SpringCache_概述、Cacheable、更新缓存、删除缓存、从0搭建缓存项目

发布时间:2023年12月17日

①. Spring Cache概述

在这里插入图片描述在这里插入图片描述在这里插入图片描述在这里插入图片描述在这里插入图片描述

  • ②.Spring 从 3.1开始定义了org.springframework.cache.Cache和 org.springframework.cache.Cache Manager接口来统一不同的缓存技术,并支持使用 JCache(JSR-107)注解简化我们开发

  • ③. JSR-107定义了5个核心接口来实现缓存操作,分别是CachingProvider, CacheManager, Cache, Entry和Expiry
    在这里插入图片描述

  • ④. 每次调用需要缓存功能的方法时,Spring会检查检查指定参数的指定的目标方法是否已经被调用过;如果有就直接从缓存中获取方法调用后的结果,如果没有就调用方法并缓存结果后返回给用户。下次调用直接从缓存中获取

  • ⑤. SpringCache常用注解详解

注解解释
@Cacheable触发将数据保存到缓存的操作
@CacheEvict触发将数据从缓存删除的操作
@CachePut不影响方法执行更新缓存 双写模式
@Caching组合以上多个操作
@CacheConfig在类级别共享缓存的相同配置

②. 触发缓存入口 - @Cacheable

  • ①. @Cacheable 注解提供了一些参数,用于配置缓存的行为。下面是一些常用的参数:
@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Inherited
@Documented
public @interface Cacheable {
    @AliasFor("cacheNames")
    String[] value() default {};

    @AliasFor("value")
    String[] cacheNames() default {};

    String key() default "";

    String keyGenerator() default "";

    String cacheManager() default "";

    String cacheResolver() default "";

    String condition() default "";

    String unless() default "";

    boolean sync() default false;
}
  • ②. value/cacheNames属性
  1. 这两个属性代表的意义相同,根据@AliasFor注解就能看出来了。
  2. 这两个属性都是用来指定缓存组件的名称,即将方法的返回结果放在哪个缓存中,属性定义为数组,可以指定多个缓存
    @AliasFor("cacheNames")
    String[] value() default {};

    @AliasFor("value")
    String[] cacheNames() default {};
	@Cacheable(cacheNames ={ CacheEnum.RedisCacheNameExpGroup.AN_MINUTE,CacheEnum.RedisCacheNameExpGroup.FIVE_MINUTES})
	 
	//这两种配置等价
	@Cacheable(value = "user") //@Cacheable(cacheNames = "user")
	User getUser(Integer id);
    @Cacheable(cacheNames = CacheEnum.RedisCacheNameExpGroup.AN_MINUTE, cacheManager =
            CacheEnum.CacheManager.REDIS_MANAGER,
            key = CacheEnum.RedisCacheKeys.PATROL_BOARD + "+#params.areaCode" + "+#params.staffCode")
  • ③. key属性:可以通过key属性来指定缓存数据所使用的的key,默认使用的是方法调用传过来的参数作为key

在这里插入图片描述在这里插入图片描述

@Cacheable(value = "user",key = "#root.method.name")
User getUser(Integer id);
  • ④. cacheManager属性:用来指定缓存管理器。针对不同的缓存技术,需要实现不同的 cacheManager,Spring 也为我们定义了如下的一些cacheManger实现

在这里插入图片描述

  • ⑤. unless属性,意为"除非"的意思。即只有unless指定的条件为true时,方法的返回值才不会被缓存。可以在获取到结果后进行判断
@Cacheable(value = "user",unless = "#result==null || #result.size()==0")//当方法返回值为 null 时,就不缓存
User getUser(Integer id);

@Cacheable(value = "user",unless = "#a0 == 1")//如果第一个参数的值是1,结果不缓存
User getUser(Integer id);
  • ⑥. condition:条件判断属性,用来指定符合指定的条件下才可以缓存。也可以通过SpEL 表达式进行设置。这个配置规则和上面表格中的配置规则是相同的
@Cacheable(value = "user",condition = "#id>0")//传入的 id 参数值>0才进行缓存
User getUser(Integer id);
  • ⑦. sync该属性用来指定是否使用异步模式,该属性默认值为false,默认为同步模式。异步模式指定sync = true即可,异步模式下unless属性不可用

③. 更新缓存 - CachePut

  • ①. @CachePut:不影响方法执行更新缓存(双写模式) 需要有返回值

  • ②. @CachePut标注的方法在执行前不会去检查缓存中是否存在之前执行过的结果,而是每次都会执行该方法,并将执行结果以键值对的形式存入指定的缓存中

  • ③. @Cacheput注解一般使用在保存,更新方法中

	@CachePut(value="emp",key = "#result.id")
	public Employee updateEmployee(Employee employee){
		System.out.println("updateEmp:"+employee);
		employeeMapper.updateEmployee(employee);
		return employee;
	}

④. 删除缓存 - CacheEvict

  • @CacheEvict:触发将数据从缓存删除的操作
    @CacheEvict(cacheNames = CacheEnum.RedisCacheNameExpGroup.ONE_DAY, cacheManager = CacheEnum.CacheManager.REDIS_MANAGER,
            key =CacheEnum.RedisCacheKeys.DISTRICT_DETAIL + "+#params.areaCode" + "+#params.timeRange")
    public void districtDetail(DiagnosisParams params) {
        log.info("refresh districtDetail!,areaCode:{},timeRange:{}",params.getAreaCode(),params.getTimeRange());
    }

⑤. 组合操作- Caching

  • ①. 组合以上多个操作: @Caching不常用
  1. @Caching 注解可以在一个方法或者类上同时指定多个Spring Cache相关的注解。
  2. 其拥有三个属性:cacheable、put和evict,分别用于指定@Cacheable、@CachePut和
  • ②. @CacheEvict。对于一个数据变动,更新多个缓存的场景,可以通过@Caching来实现:
@Caching(cacheable = @Cacheable(cacheNames = "caching", key = "#age"), evict = @CacheEvict(cacheNames = "t4", key = "#age"))
public String caching(int age) {
    return "caching: " + age + "-->" + UUID.randomUUID().toString();
}

⑥. 共享缓存配置 - CacheConfig

  • ①. @CacheConfig不常用:@CacheConfig是Spring提供的一个注解,用于统一配置缓存的一些属性,例如缓存名称、缓存管理器等

  • ②. 使用@CacheConfig注解需要注意以下几点:

  1. @CacheConfig可以放在类上,也可以放在方法上。如果放在类上,则该类中所有的缓存方法都会继承该注解的属性。
  2. @CacheConfig的属性可以被缓存方法上的@Cacheable、@CachePut、@CacheEvict等注解覆盖。
  3. @CacheConfig的属性可以通过Spring的EL表达式进行动态配置。
// 在上面的示例中,@CacheConfig注解指定了缓存名称为"myCache",缓存管理器为"myCacheManager"。这些属性会被该类中所有的缓存方法继承
// 但是,如果某个缓存方法上使用了@Cacheable、@CachePut、@CacheEvict等注解,并且指定了相同的属性,则该注解的属性会覆盖@CacheConfig注解的属性
@CacheConfig(cacheNames = "myCache", cacheManager = "myCacheManager")
@Service
public class MyService {

    @Cacheable(key = "#id")
    public MyObject findById(Long id) {
        // ...
    }

    @CachePut(key = "#myObject.id")
    public MyObject save(MyObject myObject) {
        // ...
    }

    @CacheEvict(key = "#id")
    public void deleteById(Long id) {
        // ...
    }
}

⑦. 从0搭建缓存项目

  • ①. 导入依赖
<?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">
    <parent>
        <artifactId>study2022_xz</artifactId>
        <groupId>com.xiaozhi</groupId>
        <version>0.0.1-SNAPSHOT</version>
    </parent>
    <modelVersion>4.0.0</modelVersion>

    <artifactId>springboot-swagger</artifactId>

    <properties>
        <maven.compiler.source>8</maven.compiler.source>
        <maven.compiler.target>8</maven.compiler.target>
        <knife4j.version>2.0.9</knife4j.version>
        <lombok.version>1.18.12</lombok.version>
        <caffeine.version>2.7.0</caffeine.version>
    </properties>

    <dependencies>
        <dependency>
            <groupId>com.github.xiaoymin</groupId>
            <artifactId>knife4j-spring-boot-starter</artifactId>
            <version>${knife4j.version}</version>
        </dependency>
        <!--web场景-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>

        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>fastjson</artifactId>
            <version>1.2.80</version>
        </dependency>

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

        <!--引入caffeine依赖-->
        <dependency>
            <groupId>com.github.ben-manes.caffeine</groupId>
            <artifactId>caffeine</artifactId>
            <version>${caffeine.version}</version>
        </dependency>

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

        <!--引入guava依赖-->
        <dependency>
            <groupId>com.google.guava</groupId>
            <artifactId>guava</artifactId>
            <version>30.1-jre</version>
        </dependency>

        <!--引入redis依赖-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-redis</artifactId>
        </dependency>
        <dependency>
            <groupId>org.apache.commons</groupId>
            <artifactId>commons-pool2</artifactId>
            <scope>compile</scope>
        </dependency>
    </dependencies>
</project>
  • ②. 配置类 - 常量分组概念
public interface CacheEnum {

    /**
     * cacheTemplate
     */
    interface CacheTemplates {
        /**
         * 业务用redis
         */
        String REDIS_CACHE_TEMPLATE = "redisTemplate";
    }

    /**
     * cacheManager
     */
    interface CacheManager {

        /**
         * caffeine Cache Manager
         */
        String CAFFEINE_MANAGER = "caffeineManager";

        /**
         * redis Cache Manager
         */
        String REDIS_MANAGER = "redisCacheManager";
    }

    /**
     * redis keys
     */
    interface RedisCacheKeys{
        String SSM_USER_KEY = "SSM:USER_KEY:";
    }


    /**
     * redis exp group
     */
    interface RedisCacheNameExpGroup {
        String AN_MINUTE = "ssm:anMinute";
        String FIVE_MINUTES = "ssm:fiveMinute";
        String AN_HOUR = "ssm:anHour";
        String TWO_HOUR = "ssm:twoHour";
        String FIVE_HOURS = "ssm:fiveHour";
        String ONE_DAY = "ssm:oneDay";
        String TWO_DAY ="ssm:twoDay";
    }


    /**
     * caffeine cache names conf
     */
    @AllArgsConstructor
    @Getter
    enum CaffeineCacheNamesConf {
        SSM_TEST(CaffeineCacheNames.SSM_TEST,100,10000,1, TimeUnit.DAYS),
        ;


        private String cacheName;

        private int initialCapacity;

        private int maximumSize;

        private int expsNum;

        private TimeUnit timeUnit;
    }

    /**
     * Caffeine Cache Names
     */
    interface CaffeineCacheNames{
        String SSM_TEST = "SSM_TEST";
    }
}
/**
 * caffeine缓存配置,根据{@link CacheEnum.CaffeineCacheNamesConf}配置,
 * 动态设置参数以及自动加载策略
 */
@Configuration
public class CaffeineCacheConfig {

    @Bean(CacheEnum.CacheManager.CAFFEINE_MANAGER)
    public CacheManager cacheManagerWithCaffeine() {
        SimpleCacheManager caffeineCacheManager = new SimpleCacheManager();
        caffeineCacheManager.setCaches(buildCaffeineCache());
        return caffeineCacheManager;
    }

    //动态设置缓存参数
    private List<CaffeineCache> buildCaffeineCache() {
        ArrayList<CaffeineCache> caches = Lists.newArrayList();
        CacheEnum.CaffeineCacheNamesConf[] cacheNames = CacheEnum.CaffeineCacheNamesConf.values();
        for (CacheEnum.CaffeineCacheNamesConf nameCof : cacheNames) {
            caches.add(new CaffeineCache(nameCof.getCacheName(),
                    Caffeine.newBuilder().recordStats()
                            .initialCapacity(nameCof.getInitialCapacity())
                            .maximumSize(nameCof.getMaximumSize())
                            .expireAfterWrite(nameCof.getExpsNum(), nameCof.getTimeUnit()).build()));
        }
        return caches;
    }
}
@EnableCaching
@Configuration
@Slf4j
public class RedisConfig extends JCacheConfigurerSupport {
    @Value("${spring.redis.database}")
    private int dbIndex;
    @Value("${spring.redis.host}")
    private String host;
    @Value("${spring.redis.port}")
    private int port;
    @Value("${spring.redis.password}")
    private String password;
    @Value("${spring.redis.timeout}")
    private int timeout;
    @Value("${spring.redis.lettuce.pool.max-active}")
    private int redisPoolMaxActive;
    @Value("${spring.redis.lettuce.pool.max-wait}")
    private int redisPoolMaxWait;
    @Value("${spring.redis.lettuce.pool.max-idle}")
    private int redisPoolMaxIdle;
    @Value("${spring.redis.lettuce.pool.min-idle}")
    private int redisPoolMinIdle;

    public RedisSerializer<Object> redisSerializer() {
        ObjectMapper objectMapper = new ObjectMapper();
        objectMapper.configure(JsonParser.Feature.ALLOW_COMMENTS, true);
        objectMapper.configure(SerializationFeature.INDENT_OUTPUT, true);
        objectMapper.configure(DeserializationFeature.ACCEPT_SINGLE_VALUE_AS_ARRAY, true);
        objectMapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
        objectMapper.configure(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS, false);
        objectMapper.configure(SerializationFeature.FAIL_ON_EMPTY_BEANS, false);
        objectMapper.configure(DeserializationFeature.FAIL_ON_INVALID_SUBTYPE, false);
        objectMapper.activateDefaultTyping(LaissezFaireSubTypeValidator.instance, ObjectMapper.DefaultTyping.NON_FINAL, JsonTypeInfo.As.PROPERTY);
        return new GenericJackson2JsonRedisSerializer(objectMapper);
    }


    public LettuceConnectionFactory lettuceConnectionFactory() {
        RedisStandaloneConfiguration configuration = new RedisStandaloneConfiguration();
        configuration.setHostName(host);
        configuration.setPort(port);
        configuration.setDatabase(dbIndex);
        if (!ObjectUtils.isEmpty(password)) {
            RedisPassword redisPassword = RedisPassword.of(password);
            configuration.setPassword(redisPassword);
        }
        GenericObjectPoolConfig genericObjectPoolConfig = new GenericObjectPoolConfig();
        genericObjectPoolConfig.setMaxTotal(redisPoolMaxActive);
        genericObjectPoolConfig.setMinIdle(redisPoolMinIdle);
        genericObjectPoolConfig.setMaxIdle(redisPoolMaxIdle);
        genericObjectPoolConfig.setMaxWaitMillis(redisPoolMaxWait);
        LettucePoolingClientConfiguration.LettucePoolingClientConfigurationBuilder builder = LettucePoolingClientConfiguration.builder();
        builder.poolConfig(genericObjectPoolConfig);
        builder.commandTimeout(Duration.ofSeconds(timeout));
        LettuceConnectionFactory connectionFactory = new LettuceConnectionFactory(configuration, builder.build());
        connectionFactory.afterPropertiesSet();
        return connectionFactory;
    }

    @Bean(CacheEnum.CacheTemplates.REDIS_CACHE_TEMPLATE)
    public RedisTemplate<String, Object> redisTemplate() {
        RedisTemplate<String, Object> redisTemplate = new RedisTemplate();
        redisTemplate.setKeySerializer(new StringRedisSerializer());
        redisTemplate.setHashKeySerializer(new StringRedisSerializer());
        redisTemplate.setHashValueSerializer(redisSerializer());
        redisTemplate.setValueSerializer(redisSerializer());
        redisTemplate.setConnectionFactory(lettuceConnectionFactory());
        redisTemplate.afterPropertiesSet();
        return redisTemplate;
    }

    @Bean(CacheEnum.CacheManager.REDIS_MANAGER)
    @Primary
    public CacheManager redisCacheManager() {
        RedisCacheConfiguration defConfig = RedisCacheConfiguration.defaultCacheConfig();
        ImmutableSet.Builder<String> cacheNames = ImmutableSet.builder();
        ImmutableMap.Builder<String, RedisCacheConfiguration> cacheConfig = ImmutableMap.builder();
        HashMap<String, Duration> exps = new HashMap<>();
        exps.put(CacheEnum.RedisCacheNameExpGroup.AN_MINUTE, Duration.ofMinutes(1));
        exps.put(CacheEnum.RedisCacheNameExpGroup.FIVE_MINUTES, Duration.ofMinutes(5));
        exps.put(CacheEnum.RedisCacheNameExpGroup.AN_HOUR, Duration.ofHours(1));
        exps.put(CacheEnum.RedisCacheNameExpGroup.TWO_HOUR, Duration.ofHours(2));
        exps.put(CacheEnum.RedisCacheNameExpGroup.FIVE_HOURS, Duration.ofHours(5));
        exps.put(CacheEnum.RedisCacheNameExpGroup.ONE_DAY, Duration.ofDays(1));
        exps.put(CacheEnum.RedisCacheNameExpGroup.TWO_DAY, Duration.ofDays(2));
        for (String cacheName : exps.keySet()) {
            defConfig = defConfig.entryTtl(exps.get(cacheName));
            cacheConfig.put(cacheName, defConfig);
            cacheNames.add(cacheName);
        }
        return RedisCacheManager.builder(lettuceConnectionFactory())
                .initialCacheNames(cacheNames.build())
                .withInitialCacheConfigurations(cacheConfig.build())
                .build();
    }

    @Override
    public CacheErrorHandler errorHandler() {
        return new CacheErrorHandler() {
            @Override
            public void handleCacheGetError(RuntimeException exception, Cache cache, Object key) {
                log.error("handleCacheGetError!!!->{}", exception.getMessage());
            }

            @Override
            public void handleCachePutError(RuntimeException exception, Cache cache, Object key, Object value) {
                log.error("handleCachePutError!!!->{}", exception.getMessage());
            }

            @Override
            public void handleCacheEvictError(RuntimeException exception, Cache cache, Object key) {
                log.error("handleCacheEvictError!!!->{}", exception.getMessage());
            }

            @Override
            public void handleCacheClearError(RuntimeException exception, Cache cache) {
                log.error("handleCacheClearError!!!->{}", exception.getMessage());
            }
        };
    }
}
  • ③. 测试
    @GetMapping("/testUser")
    @Cacheable(cacheNames = {CacheEnum.RedisCacheNameExpGroup.AN_MINUTE, 
    		CacheEnum.RedisCacheNameExpGroup.FIVE_MINUTES}, // 缓存组概念
            cacheManager = CacheEnum.CacheManager.REDIS_MANAGER, // 使用哪个manager管理
            condition = "#a0>2", // 满足条件
            key = "#root.method.name", // key = 当前方法名词
            unless = "#result==null || #result.size()==0") // 值为空无效
    //key = "'" + CacheEnum.RedisCacheKeys.SSM_USER_KEY + "'" + "+#userId", unless = "#result==null || #result.size()==0")
    public List<Integer> testUser(int userId) {
        return Lists.newArrayList(userId);
    }
文章来源:https://blog.csdn.net/TZ845195485/article/details/133655448
本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。