当前位置: 首页 > article >正文

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;
    }
@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;
    }
@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);
    }
@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</include>
                </includes>
            </resource>
        </resources>
    </build>

</project>

#连接数据源
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


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

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


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


http://www.kler.cn/news/134370.html

相关文章:

  • AIGC ChatGPT4 读取接口文件并进行可视化分析
  • 第14届蓝桥杯青少组python试题解析:23年5月省赛
  • 持续集成交付CICD:Jenkins通过API触发流水线
  • SDL2 播放音频(MP4)
  • 【linux】进行间通信——共享内存+消息队列+信号量
  • 开源与闭源:创新与安全的平衡
  • STM32CubeMX学习笔记-CAN接口使用
  • Java SPI机制
  • 探索Scrapy中间件:自定义Selenium中间件实例解析
  • JVM-HotSpot虚拟机对象探秘
  • 原理Redis-动态字符串SDS
  • 04 后端增删改查【小白入门SpringBoot + Vue3】
  • 目标检测YOLO实战应用案例100讲-基于机器视觉的水稻病虫害监测预警(续)
  • Git命令总结-常用-后续使用频繁的再添加~
  • RSocket 与 gRPC 基准测试
  • 【开题报告】疫苗在线预约小程序的设计与实现
  • python数据可视化
  • WPF中行为与触发器的概念及用法
  • 2023年【广东省安全员C证第四批(专职安全生产管理人员)】考试题库及广东省安全员C证第四批(专职安全生产管理人员)考试试卷
  • 系统时间和JVM的Date时间不一致问题解决
  • .Net6 部署到IIS示例
  • 【电路笔记】-欧姆定律
  • 100套Axure RP大数据可视化大屏模板及通用组件库
  • Linux环境下C++ 接入OpenSSL
  • 无需添加udid,ios企业证书的自助生成方法
  • 系列九、对象的生命周期和GC
  • Linux虚拟机中网络连接的三种方式
  • MySQL 教程 1.1
  • “ /^A-Z:\\{1,2}^/:\*\?<>\|+\.(jpg|gif|png|bmp)$/i ”这个正则表达式的理解
  • 月子会所信息展示服务预约小程序的作用是什么