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

文章目录

  • ①. Spring Cache概述
  • ②. 触发缓存入口 - @Cacheable
  • ③. 更新缓存 - CachePut
  • ④. 删除缓存 - CacheEvict
  • ⑤. 组合操作- Caching
  • ⑥. 共享缓存配置 - CacheConfig
  • ⑦. 从0搭建缓存项目

①. Spring Cache概述

  • ①. 如何找到Spring Cache的官方文档

SpringCache_概述、Cacheable、更新缓存、删除缓存、从0搭建缓存项目_第1张图片SpringCache_概述、Cacheable、更新缓存、删除缓存、从0搭建缓存项目_第2张图片SpringCache_概述、Cacheable、更新缓存、删除缓存、从0搭建缓存项目_第3张图片SpringCache_概述、Cacheable、更新缓存、删除缓存、从0搭建缓存项目_第4张图片SpringCache_概述、Cacheable、更新缓存、删除缓存、从0搭建缓存项目_第5张图片

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

  • ③. JSR-107定义了5个核心接口来实现缓存操作,分别是CachingProvider, CacheManager, Cache, Entry和Expiry
    SpringCache_概述、Cacheable、更新缓存、删除缓存、从0搭建缓存项目_第6张图片

  • ④. 每次调用需要缓存功能的方法时,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

SpringCache_概述、Cacheable、更新缓存、删除缓存、从0搭建缓存项目_第7张图片SpringCache_概述、Cacheable、更新缓存、删除缓存、从0搭建缓存项目_第8张图片

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

SpringCache_概述、Cacheable、更新缓存、删除缓存、从0搭建缓存项目_第9张图片

  • ⑤. 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搭建缓存项目

  • ①. 导入依赖

<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_xzartifactId>
        <groupId>com.xiaozhigroupId>
        <version>0.0.1-SNAPSHOTversion>
    parent>
    <modelVersion>4.0.0modelVersion>

    <artifactId>springboot-swaggerartifactId>

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

    <dependencies>
        <dependency>
            <groupId>com.github.xiaoymingroupId>
            <artifactId>knife4j-spring-boot-starterartifactId>
            <version>${knife4j.version}version>
        dependency>
        
        <dependency>
            <groupId>org.springframework.bootgroupId>
            <artifactId>spring-boot-starter-webartifactId>
        dependency>

        <dependency>
            <groupId>com.alibabagroupId>
            <artifactId>fastjsonartifactId>
            <version>1.2.80version>
        dependency>

        <dependency>
            <groupId>org.projectlombokgroupId>
            <artifactId>lombokartifactId>
        dependency>

        
        <dependency>
            <groupId>com.github.ben-manes.caffeinegroupId>
            <artifactId>caffeineartifactId>
            <version>${caffeine.version}version>
        dependency>

        <dependency>
            <groupId>org.springframework.bootgroupId>
            <artifactId>spring-boot-starter-cacheartifactId>
        dependency>

        
        <dependency>
            <groupId>com.google.guavagroupId>
            <artifactId>guavaartifactId>
            <version>30.1-jreversion>
        dependency>

        
        <dependency>
            <groupId>org.springframework.bootgroupId>
            <artifactId>spring-boot-starter-data-redisartifactId>
        dependency>
        <dependency>
            <groupId>org.apache.commonsgroupId>
            <artifactId>commons-pool2artifactId>
            <scope>compilescope>
        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);
    }

你可能感兴趣的:(REDIS,-,高性能缓存,缓存,java,数据库)