• SpringBoot整合Redis使用基于注解的缓存


    环境准备

    注解

    @EnableCaching

    image.png

    @CacheConfig

    @CacheConfig 提供了一种在类级别共享公共缓存相关设置的机制。

    | 参数 | 作用 |
    |
    | — | — | — |
    | cacheNames | 使用在类上的默认缓存名称 | |
    | keyGenerator | 用于类的默认KeyGenerator的bean名称 | |
    | cacheManager | 自定义CacheManager的bean名称,如果尚未设置,则可以用于创建默认CacheResolver | |
    | cacheResolver | 要使用的自定义CacheResolver的bean名称 | |

    @Cacheable

    @Cacheable 可以标记在一个方法上,也可以标记在类上,当标记在类上时,当前类的所有方法都支持缓存,当注解的方法被调用时,如果缓存中有值,则直接返回缓存中的数据

    参数作用example
    cacheNames / value缓存的空间名称,这两个配置只能二选一
    key / keyGenerator缓存的key,同一个空间名称value下的key唯一,可以通过SpEL 表达式编写指定这个key的值,或者通过keyGenerator生成,这两个配置只能二选一
    cacheManager自定义CacheManager的bean名称,如果尚未设置,则可以用于创建默认CacheResolver
    cacheResolver要使用的自定义CacheResolver的bean名称
    condition缓存的条件,默认为true,使用 SpEL 编写,返回true或者false,只有为true才进行缓存。为true时:如果缓存有值,则不执行方法;如果缓存没值,则执行方法并将结果保存到缓存。为false时:不执行缓存,每次都执行方法。
    unless函数返回值符合条件的不缓存、只缓存其余不符合条件的。可以使用 SpEL 编写,方法参数可以通过索引访问,例如:第二个参数可以通过#root.args[1]、#p1或#a1访问
    sync是否使用异步模式。默认是方法执行完,以同步的方式将方法返回的结果存在缓存中
       @Cacheable(cacheNames =spaceUserPre, key = "#redisSyc.name",condition = "true",unless = "#result?.result==null")
        public RedisSyc select(RedisSyc redisSyc){
            RedisSyc select = redisAnnoMapper.select(redisSyc);
            return select;
        }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    @CachePut

    @CachePut 可以标记在一个方法上,也可以标记在类上。使用 @CachePut 标注的方法在执行前不会去检查缓存中是否存在之前执行过的结果,而是每次都会执行该方法,然后将执行结果以键值对的形式存入指定的缓存中

    | 参数 | 作用 |
    |
    | — | — | — |
    | cacheNames / value | 缓存的空间名称,这两个配置只能二选一 | |
    | key / keyGenerator | 缓存的key,同一个空间名称value下的key唯一,可以通过SpEL 表达式编写指定这个key的值,或者通过keyGenerator生成,这两个配置只能二选一 | |
    | cacheManager | 自定义CacheManager的bean名称,如果尚未设置,则可以用于创建默认CacheResolver | |
    | cacheResolver | 要使用的自定义CacheResolver的bean名称 | |
    | condition | 缓存的条件,默认为true,使用 SpEL 编写,返回true或者false,只有为true才进行缓存。为true时:如果缓存有值,则不执行方法;如果缓存没值,则执行方法并将结果保存到缓存。为false时:不执行缓存,每次都执行方法。 | |
    | unless | 函数返回值符合条件的不缓存、只缓存其余不符合条件的。可以使用 SpEL 编写,方法参数可以通过索引访问,例如:第二个参数可以通过#root.args[1]、#p1或#a1访问 | |

      @CachePut(cacheNames = spaceUserPre, key = "#redisSyc.name",condition = "true",unless = "#result==null" )
        public RedisSyc add(RedisSyc redisSyc){
            redisAnnoMapper.add(redisSyc);
            log.info("添加成功:{}",new Gson().toJson(redisSyc));
            return redisSyc;
        }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    @CacheEvict

    @CacheEvict 可以标记在一个方法上,也可以标记在类上,用来清除缓存元素的。当标记在一个类上时表示其中所有的方法的执行都会触发缓存的清除操作。

    | 参数 | 作用 |
    |
    | — | — | — |
    | cacheNames / value | 缓存的空间名称,这两个配置只能二选一 | |
    | key / keyGenerator | 缓存的key,同一个空间名称value下的key唯一,可以通过SpEL 表达式编写指定这个key的值,或者通过keyGenerator生成,这两个配置只能二选一 | |
    | cacheManager | 自定义CacheManager的bean名称,如果尚未设置,则可以用于创建默认CacheResolver | |
    | cacheResolver | 要使用的自定义CacheResolver的bean名称 | |
    | condition | 缓存的条件,默认为true,使用 SpEL 编写,返回true或者false,只有为true才进行缓存。为true时:如果缓存有值,则不执行方法;如果缓存没值,则执行方法并将结果保存到缓存。为false时:不执行缓存,每次都执行方法。 | |
    | allEntries | 为true时表示清除(cacheNames或value)空间名里的所有的数据 | |
    | beforeInvocation | 为false时,缓存的清除是否再方法之前执行,默认代表缓存清除操作是在方法执行后执行,如果出现异常缓存就不会清除;为true时,代表清除缓存操作是在方法运行之前执行,无论方法是否出现异常,缓存都清除 | |

      @CacheEvict(cacheNames = spaceUserPre, key = "#redisSyc.name",condition = "true",allEntries = true,beforeInvocation = true )
        public void delete(RedisSyc redisSyc){
            redisAnnoMapper.delete(redisSyc);
        }
    
    • 1
    • 2
    • 3
    • 4
    @Caching

    @Caching 多个缓存注解(不同或相同类型)的组注解。

    | 参数 | 作用 |
    |
    | — | — | — |
    | cacheNames / value | 缓存的空间名称,这两个配置只能二选一 | |
    | key / keyGenerator | 缓存的key,同一个空间名称value下的key唯一,可以通过SpEL 表达式编写指定这个key的值,或者通过keyGenerator生成,这两个配置只能二选一 | |
    | cacheManager | 自定义CacheManager的bean名称,如果尚未设置,则可以用于创建默认CacheResolver | |
    | cacheResolver | 要使用的自定义CacheResolver的bean名称 | |
    | condition | 缓存的条件,默认为true,使用 SpEL 编写,返回true或者false,只有为true才进行缓存。为true时:如果缓存有值,则不执行方法;如果缓存没值,则执行方法并将结果保存到缓存。为false时:不执行缓存,每次都执行方法。 | |
    | allEntries | 为true时表示清除(cacheNames或value)空间名里的所有的数据 | |
    | beforeInvocation | 为false时,缓存的清除是否再方法之前执行,默认代表缓存清除操作是在方法执行后执行,如果出现异常缓存就不会清除;为true时,代表清除缓存操作是在方法运行之前执行,无论方法是否出现异常,缓存都清除 | |

    spEL 编写 key

    微信截图_20231113143318.png

    springboot集成

    <?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.17</version>
            <relativePath/> <!-- lookup parent from repository -->
        </parent>
        <groupId>com.redis</groupId>
        <artifactId>redis01</artifactId>
        <version>0.0.1-SNAPSHOT</version>
        <name>redis01</name>
        <description>redis01</description>
        <properties>
            <java.version>8</java.version>
        </properties>
        <dependencies>
            <dependency>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-starter-data-redis</artifactId>
            </dependency>
            <dependency>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-starter-web</artifactId>
            </dependency>
            <dependency>
                <groupId>org.mybatis.spring.boot</groupId>
                <artifactId>mybatis-spring-boot-starter</artifactId>
                <version>2.0.0</version>
            </dependency>
            <!--alibaba的druid数据库连接池 (德鲁伊)-->
            <dependency>
                <groupId>com.alibaba</groupId>
                <artifactId>druid-spring-boot-starter</artifactId>
                <version>1.1.10</version>
            </dependency>
            <dependency>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-devtools</artifactId>
                <scope>runtime</scope>
                <optional>true</optional>
            </dependency>
            <!--Mybatis整合springboot的起步依赖-->
            <dependency>
                <groupId>org.mybatis.spring.boot</groupId>
                <artifactId>mybatis-spring-boot-starter</artifactId>
                <version>2.0.0</version>
            </dependency>
            <!--【数据库】数据库连接池-->
            <dependency>
                <groupId>com.alibaba</groupId>
                <artifactId>druid</artifactId>
                <version>1.2.11</version>
            </dependency>
    
            <dependency>
                <groupId>com.mysql</groupId>
                <artifactId>mysql-connector-j</artifactId>
                <scope>runtime</scope>
            </dependency>
            <dependency>
                <groupId>org.projectlombok</groupId>
                <artifactId>lombok</artifactId>
                <optional>true</optional>
            </dependency>
            <dependency>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-starter-test</artifactId>
                <scope>test</scope>
            </dependency>
            <!--jedis-->
            <dependency>
                <groupId>redis.clients</groupId>
                <artifactId>jedis</artifactId>
                <version>4.3.1</version>
            </dependency>
    
        </dependencies>
    
        <build>
            <plugins>
                <plugin>
                    <groupId>org.springframework.boot</groupId>
                    <artifactId>spring-boot-maven-plugin</artifactId>
                    <configuration>
                        <excludes>
                            <exclude>
                                <groupId>org.projectlombok</groupId>
                                <artifactId>lombok</artifactId>
                            </exclude>
                        </excludes>
                    </configuration>
                </plugin>
            </plugins>
            <resources>
                <resource>
                    <directory>src/main/java</directory>
                    <includes>
                        <include>**/*.xml
                    
                
            
        
    
    
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84
    • 85
    • 86
    • 87
    • 88
    • 89
    • 90
    • 91
    • 92
    • 93
    • 94
    • 95
    • 96
    • 97
    • 98
    • 99
    • 100
    • 101
    • 102
    • 103
    • 104
    • 105
    • 106
    • 107
    • 108
    #连接数据源
    spring.datasource.druid.username=root
    spring.datasource.druid.password=xgm@2023..
    spring.datasource.druid.url=jdbc:mysql://172.16.204.51:3306/redis?serverTimezone=GMT%2B8
    spring.datasource.druid.driver-class-name=com.mysql.cj.jdbc.Driver
    spring.datasource.druid.initial-size=5
    
    ##指定缓存类型redis
    #spring.cache.type=redis
    ##一个小时,以毫秒为单位
    #spring.cache.redis.time-to-live=3600000
    ##给缓存的建都起一个前缀。  如果指定了前缀就用我们指定的,如果没有就默认使用缓存的名字作为前缀,一般不指定
    #spring.cache.redis.key-prefix=CACHE_
    ##指定是否使用前缀
    #spring.cache.redis.use-key-prefix=true
    ##是否缓存空值,防止缓存穿透
    #spring.cache.redis.cache-null-values=true
    
    
    #redis
    spring.redis.host=172.16.204.51
    spring.redis.port=6379
    spring.redis.password=123456
    spring.redis.database=1
    
    
    # mybatis配置
    mybatis:
    check-config-location: true
    #  mybatis框架配置文件,对mybatis的生命周期起作用
    config-location: "classpath:mybatis/mybatis-config.xml"
    #  配置xml路径
    mapper-locations: "classpath:mybatis/mapper/*Mapper.xml"
    #  配置model包路径
    type-aliases-package: "com.redis.redis01.bean.*"
    
    #日志
    logging.level.root=debug
    logging.level.io.lettuce.core=debug
    logging.level.org.springframework.data.redis=debug
    
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    <?xml version="1.0" encoding="UTF-8" ?>
    <!DOCTYPE configuration PUBLIC
            "-//mybatis.org//DTD Config 3.0//EN"
            "http://mybatis.org/dtd/mybatis-3-config.dtd">
    <configuration>
        <settings>
            <!-- 全局的映射器启用或禁用缓存。 -->
            <setting name="cacheEnabled" value="true"/>
            <!-- 全局启用或禁用延迟加载 -->
            <setting name="lazyLoadingEnabled" value="true"/>
            <!-- 允许或不允许多种结果集从一个单独的语句中返回 -->
            <setting name="multipleResultSetsEnabled" value="true"/>
            <!-- 使用列标签代替列名 -->
            <setting name="useColumnLabel" value="true"/>
            <!-- 允许JDBC支持生成的键 -->
            <setting name="useGeneratedKeys" value="false"/>
            <!-- 配置默认的执行器 -->
            <setting name="defaultExecutorType" value="SIMPLE"/>
            <!-- 设置超时时间 -->
            <setting name="defaultStatementTimeout" value="60"/>
            <!-- 设置驼峰标识 -->
            <setting name="mapUnderscoreToCamelCase" value="true"/>
        </settings>
        <plugins>
            <!-- 分页插件 -->
            <plugin interceptor="com.github.pagehelper.PageInterceptor" />
        </plugins>
    </configuration>
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    package com.redis.redis01.conf;
    
    import com.fasterxml.jackson.annotation.JsonAutoDetect;
    import com.fasterxml.jackson.annotation.JsonAutoDetect.Visibility;
    import com.fasterxml.jackson.annotation.PropertyAccessor;
    import com.fasterxml.jackson.databind.ObjectMapper;
    import com.fasterxml.jackson.databind.ObjectMapper.DefaultTyping;
    import com.redis.redis01.bean.CacheConstant;
    import lombok.extern.slf4j.Slf4j;
    
    import org.springframework.cache.CacheManager;
    import org.springframework.cache.annotation.CachingConfigurerSupport;
    import org.springframework.cache.annotation.EnableCaching;
    import org.springframework.context.annotation.Bean;
    import org.springframework.context.annotation.Configuration;
    import org.springframework.data.redis.cache.BatchStrategies;
    import org.springframework.data.redis.cache.RedisCacheConfiguration;
    import org.springframework.data.redis.cache.RedisCacheManager;
    import org.springframework.data.redis.cache.RedisCacheWriter;
    import org.springframework.data.redis.connection.RedisConnectionFactory;
    import org.springframework.data.redis.connection.lettuce.LettuceConnectionFactory;
    import org.springframework.data.redis.core.RedisTemplate;
    import org.springframework.data.redis.listener.ChannelTopic;
    import org.springframework.data.redis.listener.RedisMessageListenerContainer;
    import org.springframework.data.redis.listener.adapter.MessageListenerAdapter;
    import org.springframework.data.redis.serializer.*;
    
    import javax.annotation.Resource;
    import java.time.Duration;
    
    import static java.util.Collections.singletonMap;
    
    /**
     * 开启缓存支持
     * @author zyf
     * @Return:
     */
    @Slf4j
    //创建redis配置类,一定要注意打上@EnableCaching注解,否则spring自带的缓存注解功能将不会自动启用
    @EnableCaching
    @Configuration
    public class RedisConfig extends CachingConfigurerSupport {
    
        @Resource
        private LettuceConnectionFactory lettuceConnectionFactory;
    
    //	/**
    //	 * @description 自定义的缓存key的生成策略 若想使用这个key
    //	 *              只需要讲注解上keyGenerator的值设置为keyGenerator即可
    // * @return 自定义策略生成的key // */ // @Override // @Bean // public KeyGenerator keyGenerator() { // return new KeyGenerator() { // @Override // public Object generate(Object target, Method method, Object... params) { // StringBuilder sb = new StringBuilder(); // sb.append(target.getClass().getName()); // sb.append(method.getDeclaringClass().getName()); // Arrays.stream(params).map(Object::toString).forEach(sb::append); // return sb.toString(); // } // }; // } /** * RedisTemplate配置 * @param lettuceConnectionFactory * @return */ @Bean public RedisTemplate<String, Object> redisTemplate(LettuceConnectionFactory lettuceConnectionFactory) { log.debug(" --- redis config init --- "); Jackson2JsonRedisSerializer<Object> jackson2JsonRedisSerializer = jacksonSerializer(); RedisTemplate<String, Object> redisTemplate = new RedisTemplate<String, Object>(); redisTemplate.setConnectionFactory(lettuceConnectionFactory); RedisSerializer<String> stringSerializer = new StringRedisSerializer(); // key序列化 redisTemplate.setKeySerializer(stringSerializer); // value序列化 redisTemplate.setValueSerializer(jackson2JsonRedisSerializer); // Hash key序列化 redisTemplate.setHashKeySerializer(stringSerializer); // Hash value序列化 redisTemplate.setHashValueSerializer(jackson2JsonRedisSerializer); redisTemplate.afterPropertiesSet(); return redisTemplate; } /** * 缓存配置管理器 * * @param factory * @return */ @Bean public CacheManager cacheManager(LettuceConnectionFactory factory) { Jackson2JsonRedisSerializer<Object> jackson2JsonRedisSerializer = jacksonSerializer(); // 配置序列化(解决乱码的问题),并且配置缓存默认有效期 6小时 RedisCacheConfiguration config = RedisCacheConfiguration.defaultCacheConfig().entryTtl(Duration.ofHours(6)); RedisCacheConfiguration redisCacheConfiguration = config.serializeKeysWith(RedisSerializationContext.SerializationPair.fromSerializer(new StringRedisSerializer())) .serializeValuesWith(RedisSerializationContext.SerializationPair.fromSerializer(jackson2JsonRedisSerializer)); //.disableCachingNullValues(); // 以锁写入的方式创建RedisCacheWriter对象 //update-begin-author:taoyan date:20210316 for:注解CacheEvict根据key删除redis支持通配符* RedisCacheWriter writer = new MyRedisCacheWriter(factory, Duration.ofMillis(50L)); //RedisCacheWriter.lockingRedisCacheWriter(factory); // 创建默认缓存配置对象 /* 默认配置,设置缓存有效期 1小时*/ //RedisCacheConfiguration defaultCacheConfig = RedisCacheConfiguration.defaultCacheConfig().entryTtl(Duration.ofHours(1)); /* 自定义配置test:demo 的超时时间为 5分钟*/ RedisCacheManager cacheManager = RedisCacheManager.builder(writer).cacheDefaults(redisCacheConfiguration) .withInitialCacheConfigurations(singletonMap(CacheConstant.SYS_DICT_TABLE_CACHE, RedisCacheConfiguration.defaultCacheConfig().entryTtl(Duration.ofMinutes(10)).disableCachingNullValues() .serializeValuesWith(RedisSerializationContext.SerializationPair.fromSerializer(jackson2JsonRedisSerializer)))) .withInitialCacheConfigurations(singletonMap(CacheConstant.TEST_DEMO_CACHE, RedisCacheConfiguration.defaultCacheConfig().entryTtl(Duration.ofMinutes(5)).disableCachingNullValues())) .transactionAware() .build(); // RedisCacheManager cacheManager = // RedisCacheManager // .builder(RedisCacheWriter.lockingRedisCacheWriter // (factory, BatchStrategies.scan(1000))).cacheDefaults(RedisCacheConfiguration.defaultCacheConfig().entryTtl(Duration.ofMinutes(Long.valueOf(10))).disableCachingNullValues()) // .transactionAware() // .build(); //update-end-author:taoyan date:20210316 for:注解CacheEvict根据key删除redis支持通配符* return cacheManager; } private Jackson2JsonRedisSerializer jacksonSerializer() { Jackson2JsonRedisSerializer jackson2JsonRedisSerializer = new Jackson2JsonRedisSerializer(Object.class); ObjectMapper objectMapper = new ObjectMapper(); objectMapper.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY); objectMapper.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL); jackson2JsonRedisSerializer.setObjectMapper(objectMapper); return jackson2JsonRedisSerializer; } }
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84
    • 85
    • 86
    • 87
    • 88
    • 89
    • 90
    • 91
    • 92
    • 93
    • 94
    • 95
    • 96
    • 97
    • 98
    • 99
    • 100
    • 101
    • 102
    • 103
    • 104
    • 105
    • 106
    • 107
    • 108
    • 109
    • 110
    • 111
    • 112
    • 113
    • 114
    • 115
    • 116
    • 117
    • 118
    • 119
    • 120
    • 121
    • 122
    • 123
    • 124
    • 125
    • 126
    • 127
    • 128
    • 129
    • 130
    • 131
    • 132
    • 133
    • 134
    • 135
    • 136
    • 137
    • 138
    • 139
    • 140
    • 141
    • 142
    • 143
    • 144
    package com.redis.redis01.conf;
    
    
    import lombok.extern.slf4j.Slf4j;
    import org.springframework.dao.PessimisticLockingFailureException;
    import org.springframework.data.redis.cache.CacheStatistics;
    import org.springframework.data.redis.cache.CacheStatisticsCollector;
    import org.springframework.data.redis.cache.RedisCacheWriter;
    import org.springframework.data.redis.connection.RedisConnection;
    import org.springframework.data.redis.connection.RedisConnectionFactory;
    import org.springframework.data.redis.connection.RedisStringCommands.SetOption;
    import org.springframework.data.redis.core.Cursor;
    import org.springframework.data.redis.core.ScanOptions;
    import org.springframework.data.redis.core.types.Expiration;
    import org.springframework.lang.Nullable;
    import org.springframework.util.Assert;
    
    import java.nio.charset.StandardCharsets;
    import java.time.Duration;
    import java.util.Collections;
    import java.util.HashSet;
    import java.util.Optional;
    import java.util.Set;
    import java.util.concurrent.TimeUnit;
    import java.util.function.Consumer;
    import java.util.function.Function;
    
    /**
     * 该类参照 DefaultRedisCacheWriter 重写了 remove 方法实现通配符*删除,解决生产环境禁用kyes*后。@CacheEvict报错问题
     */
    @Slf4j
    public class MyRedisCacheWriter implements RedisCacheWriter {
    
        private final RedisConnectionFactory connectionFactory;
        private final Duration sleepTime;
    
        public MyRedisCacheWriter(RedisConnectionFactory connectionFactory) {
            this(connectionFactory, Duration.ZERO);
        }
    
        public MyRedisCacheWriter(RedisConnectionFactory connectionFactory, Duration sleepTime) {
            Assert.notNull(connectionFactory, "ConnectionFactory must not be null!");
            Assert.notNull(sleepTime, "SleepTime must not be null!");
            this.connectionFactory = connectionFactory;
            this.sleepTime = sleepTime;
        }
    
        public void put(String name, byte[] key, byte[] value, @Nullable Duration ttl) {
            Assert.notNull(name, "Name must not be null!");
            Assert.notNull(key, "Key must not be null!");
            Assert.notNull(value, "Value must not be null!");
            this.execute(name, (connection) -> {
                if (shouldExpireWithin(ttl)) {
                    connection.set(key, value, Expiration.from(ttl.toMillis(), TimeUnit.MILLISECONDS), SetOption.upsert());
                } else {
                    connection.set(key, value);
                }
    
                return "OK";
            });
        }
    
        public byte[] get(String name, byte[] key) {
            Assert.notNull(name, "Name must not be null!");
            Assert.notNull(key, "Key must not be null!");
            return this.execute(name, (connection) -> {
                return connection.get(key);
            });
        }
    
        public byte[] putIfAbsent(String name, byte[] key, byte[] value, @Nullable Duration ttl) {
            Assert.notNull(name, "Name must not be null!");
            Assert.notNull(key, "Key must not be null!");
            Assert.notNull(value, "Value must not be null!");
            return this.execute(name, (connection) -> {
                if (this.isLockingCacheWriter()) {
                    this.doLock(name, connection);
                }
    
                Object var7;
                try {
                    boolean put;
                    if (shouldExpireWithin(ttl)) {
                        put = connection.set(key, value, Expiration.from(ttl), SetOption.ifAbsent());
                    } else {
                        put = connection.setNX(key, value);
                    }
    
                    if (!put) {
                        byte[] var11 = connection.get(key);
                        return var11;
                    }
    
                    var7 = null;
                } finally {
                    if (this.isLockingCacheWriter()) {
                        this.doUnlock(name, connection);
                    }
    
                }
    
                return (byte[])var7;
            });
        }
    
        public void remove(String name, byte[] key) {
            Assert.notNull(name, "Name must not be null!");
            Assert.notNull(key, "Key must not be null!");
            String keyString = new String(key);
            log.debug("redis remove key:" + keyString);
            if(keyString!=null && keyString.endsWith("*")){
                execute(name, connection -> {
                    // 获取某个前缀所拥有的所有的键,某个前缀开头,后面肯定是*
                    Set<byte[]> keys = connection.keys(key);
                    int delNum = 0;
                    for (byte[] keyByte : keys) {
                        delNum += connection.del(keyByte);
                    }
                    return delNum;
                });
            }else{
                this.execute(name, (connection) -> {
                    return connection.del(new byte[][]{key});
                });
            }
        }
    
        public void clean(String name, byte[] pattern) {
            Assert.notNull(name, "Name must not be null!");
            Assert.notNull(pattern, "Pattern must not be null!");
            this.execute(name, (connection) -> {
                boolean wasLocked = false;
    
                try {
                    if (this.isLockingCacheWriter()) {
                        this.doLock(name, connection);
                        wasLocked = true;
                    }
    
                   // byte[][] keys = (byte[][])((Set)Optional.ofNullable(connection.keys(pattern)).orElse(Collections.emptySet())).toArray(new byte[0][]);
                    // 使用scan命令代替原本的keys命令搜索key
                    final Cursor<byte[]> cursor = connection.scan(ScanOptions.scanOptions().count(100).match(pattern).build());
                    Set<byte[]> byteSet = new HashSet<>();
                    while (cursor.hasNext()) {
                        byteSet.add(cursor.next());
                    }
                    byte[][] keys = byteSet.toArray(new byte[0][]);
                    if (keys.length > 0) {
                        connection.del(keys);
                    }
                } finally {
                    if (wasLocked && this.isLockingCacheWriter()) {
                        this.doUnlock(name, connection);
                    }
    
                }
    
                return "OK";
            });
        }
    
        @Override
        public void clearStatistics(String name) {
        }
    
        @Override
        public RedisCacheWriter withStatisticsCollector(CacheStatisticsCollector cacheStatisticsCollector) {
            return null;
        }
    
        void lock(String name) {
            this.execute(name, (connection) -> {
                return this.doLock(name, connection);
            });
        }
    
        void unlock(String name) {
            this.executeLockFree((connection) -> {
                this.doUnlock(name, connection);
            });
        }
    
        private Boolean doLock(String name, RedisConnection connection) {
            return connection.setNX(createCacheLockKey(name), new byte[0]);
        }
    
        private Long doUnlock(String name, RedisConnection connection) {
            return connection.del(new byte[][]{createCacheLockKey(name)});
        }
    
        boolean doCheckLock(String name, RedisConnection connection) {
            return connection.exists(createCacheLockKey(name));
        }
    
        private boolean isLockingCacheWriter() {
            return !this.sleepTime.isZero() && !this.sleepTime.isNegative();
        }
    
        private <T> T execute(String name, Function<RedisConnection, T> callback) {
            RedisConnection connection = this.connectionFactory.getConnection();
    
            try {
                this.checkAndPotentiallyWaitUntilUnlocked(name, connection);
                return callback.apply(connection);
            } finally {
                connection.close();
            }
    
        }
    
        private void executeLockFree(Consumer<RedisConnection> callback) {
            RedisConnection connection = this.connectionFactory.getConnection();
    
            try {
                callback.accept(connection);
            } finally {
                connection.close();
            }
    
        }
    
        private void checkAndPotentiallyWaitUntilUnlocked(String name, RedisConnection connection) {
            if (this.isLockingCacheWriter()) {
                try {
                    while(this.doCheckLock(name, connection)) {
                        Thread.sleep(this.sleepTime.toMillis());
                    }
    
                } catch (InterruptedException var4) {
                    Thread.currentThread().interrupt();
                    throw new PessimisticLockingFailureException(String.format("Interrupted while waiting to unlock cache %s", name), var4);
                }
            }
        }
    
        private static boolean shouldExpireWithin(@Nullable Duration ttl) {
            return ttl != null && !ttl.isZero() && !ttl.isNegative();
        }
    
        private static byte[] createCacheLockKey(String name) {
            return (name + "~lock").getBytes(StandardCharsets.UTF_8);
        }
    
        @Override
        public CacheStatistics getCacheStatistics(String cacheName) {
            return null;
        }
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84
    • 85
    • 86
    • 87
    • 88
    • 89
    • 90
    • 91
    • 92
    • 93
    • 94
    • 95
    • 96
    • 97
    • 98
    • 99
    • 100
    • 101
    • 102
    • 103
    • 104
    • 105
    • 106
    • 107
    • 108
    • 109
    • 110
    • 111
    • 112
    • 113
    • 114
    • 115
    • 116
    • 117
    • 118
    • 119
    • 120
    • 121
    • 122
    • 123
    • 124
    • 125
    • 126
    • 127
    • 128
    • 129
    • 130
    • 131
    • 132
    • 133
    • 134
    • 135
    • 136
    • 137
    • 138
    • 139
    • 140
    • 141
    • 142
    • 143
    • 144
    • 145
    • 146
    • 147
    • 148
    • 149
    • 150
    • 151
    • 152
    • 153
    • 154
    • 155
    • 156
    • 157
    • 158
    • 159
    • 160
    • 161
    • 162
    • 163
    • 164
    • 165
    • 166
    • 167
    • 168
    • 169
    • 170
    • 171
    • 172
    • 173
    • 174
    • 175
    • 176
    • 177
    • 178
    • 179
    • 180
    • 181
    • 182
    • 183
    • 184
    • 185
    • 186
    • 187
    • 188
    • 189
    • 190
    • 191
    • 192
    • 193
    • 194
    • 195
    • 196
    • 197
    • 198
    • 199
    • 200
    • 201
    • 202
    • 203
    • 204
    • 205
    • 206
    • 207
    • 208
    • 209
    • 210
    • 211
    • 212
    • 213
    • 214
    • 215
    • 216
    • 217
    • 218
    • 219
    • 220
    • 221
    • 222
    • 223
    • 224
    • 225
    • 226
    • 227
    • 228
    • 229
    • 230
    • 231
    • 232
    • 233
    • 234
    • 235
    • 236
    • 237
    • 238
    • 239
    • 240
    • 241
    • 242
    • 243
    • 244
    • 245
    • 246
    • 247
    • 248
    • 249
  • 相关阅读:
    springboot毕设项目社团活动网站b7rjo(java+VUE+Mybatis+Maven+Mysql)
    java防止同时多个相同请求并发问题
    IOS开发之页面跳转
    金融数据合规管理研究
    【C/C++】结构体内存分配问题
    OpenMMLab-AI实战营第二期-课程笔记-Class 4:深度学习预训练与MMPretrain
    Looper分析
    [Hadoop] start-dfs.sh ssh报错
    开juǎn有益系列(一)——Binary search(二分查找/折半查找算法)
    51单片机DAC数模转换
  • 原文地址:https://blog.csdn.net/weixin_38501485/article/details/134445225