当前位置:   article > 正文

Springboot项目技术集成_springboot 集成

springboot 集成

一、建新模块

得到程序能正常的跑起来

当前使用的连接池

希望些、安全一些,从连接池、语句的改变这几点开始修改,下面就是操作

二、集成Druid

C3P0 :是一个开放源代码的 JDBC 连接池,它在 lib 目录中与 Hibernate 一起发布,包括了实现 jdbc3 和 jdbc2扩展规范说明的 Connection Statement 池的 DataSources 对象。
Proxool :是一个 Java SQL Driver 驱动程序,提供了对选择的其它类型的驱动程序的连接池封装。可以 非常简单的移植到现存的代码中,完全可配置,快速、成熟、健壮。可以透明地为现存的JDBC 驱动程序 增加连接池功能。
Jakarta DBCP DBCP 是一个依赖 Jakartacommons-pool 对象池机制的数据库连接池。 DBCP 可以直接的 在应用程序中使用。
BoneCP :是一个快速、开源的数据库连接池。帮用户管理数据连接,让应用程序能更快速地访问数据 库。比C3P0/DBCP 连接池速度快 25 倍。
Druid Druid 不仅是一个数据库连接池,还包含一个 ProxyDriver (代理程序)、一系列内置的 JDBC 组 件库、一个SQL
Parser 。支持所有 JDBC 兼容的数据库,包括 Oracle MySql Derby Postgresql SQL Server H2

1、导入pom依赖

<dependency>
<groupId> com.alibaba </groupId>
<artifactId> druid-spring-boot-starter </artifactId>
<version> 1.2.8 </version>
</dependency>

2、修改application.yml

server:
  port: 8080
spring:
  datasource:
    username: root
    password: 123456
    driver-class-name: com.mysql.cj.jdbc.Driver
    url: jdbc:mysql://localhost:3306/SpringBoot?useSSL=false&serverTimezone=Asia/Shanghai&useUnicode=true&characterEncoding=utf-8&allowPublicKeyRetrieval=true
    type: com.alibaba.druid.pool.DruidDataSource
    druid:
      initial-size: 5                                       # 初始化大小
      min-idle: 10                                          # 最小连接数
      max-active: 20                                        # 最大连接数
      max-wait: 60000                                       # 获取连接时的最大等待时间
      min-evictable-idle-time-millis: 300000                # 一个连接在池中最小生存的时间,单位是毫秒
      time-between-eviction-runs-millis: 60000              # 多久才进行一次检测需要关闭的空闲连接,单位是毫秒
      filters: stat                                         # 配置扩展插件:stat-监控统计,log4j-日志,wall-防火墙(防止SQL注入),去掉后,监控界面的sql无法统计   ,wall
      validation-query: SELECT 1                            # 检测连接是否有效的 SQL语句,为空时以下三个配置均无效
      test-on-borrow: true                                  # 申请连接时执行validationQuery检测连接是否有效,默认true,开启后会降低性能
      test-on-return: true                                  # 归还连接时执行validationQuery检测连接是否有效,默认false,开启后会降低性能
      test-while-idle: true                                 # 申请连接时如果空闲时间大于timeBetweenEvictionRunsMillis,执行validationQuery检测连接是否有效,默认false,建议开启,不影响性能
      stat-view-servlet:
        enabled: true                                       # 是否开启 StatViewServlet
        allow: 127.0.0.1                                    # 访问监控页面 白名单,默认127.0.0.1
        deny: 192.168.56.1                                  # 访问监控页面 黑名单
        login-username: admin                               # 访问监控页面 登陆账号
        login-password: 123                                 # 访问监控页面 登陆密码
      filter:
        stat:
          enabled: true                                     # 是否开启 FilterStat,默认true
          log-slow-sql: true                                # 是否开启 慢SQL 记录,默认false
          slow-sql-millis: 5000                             # 慢 SQL 的标准,默认 3000,单位:毫秒
          merge-sql: false                                  # 合并多个连接池的监控数据,默认false
  redis:
    database: 0         #数据库索引
    host: 127.0.0.1     #主机位置
    port: 6379          #端口
    password: 123         #密码
    jedis:
      pool:
        max-active: 8   #最大连接数
        max-wait: -1    #最大阻塞等待时间(负数表示没限制)
        max-idle: 8     #最大空闲
        min-idle: 0     #最小空闲
    timeout: 10000      #连接超时时间
logging:
  level:
    com.mwy.code.mapper: debug

3、增加controller软件包,编写StudentController类

package com.mwy.code.controller;
import com.mwy.code.service.StudentService;
import com.mwy.code.util.PageBean;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import javax.servlet.http.HttpServletRequest;
@RestController
@RequestMapping("/stu")
public class StudentController {
    @Autowired
    StudentService service;

    @GetMapping("/index")
    public Object index(HttpServletRequest request){
        PageBean pageBean=new PageBean();
        pageBean.setRequest(request);
        return service.findPager(pageBean);
    }
}

 启动测试得到结果

4、 Druid :连接池提供了监控界面

可以看到程序到底执行了那些语句,登录账号密码在yml中写着

 如果某一天发现应用程序非常的卡,那么就去查看监控界面,到底是哪一段语句执行最长,知道后就能根据语句进行改进,对数据进行整理

5、分页插件yml

pagehelper : # https://pagehelper.github.io/docs/howtouse
# 是否启用分页合理化。如果启用 , page<1 , 会自动查询第一页的数据
# page>maxPages , 自动查询最后一页数据
# 不启用的 , 以上两种情况都会返回空数据
reasonable : true
# 分页插件会从查询方法的参数值中 , 自动根据上面 params 配置的字段中取值 , 查找到合适的值时就会
自动分页
supportMethodsArguments : true
# 默认值为 false, 当该参数设置为 true , 如果 pageSize=0 或者 RowBounds.limit = 0
会查询出全部的结果
page-size-zero : true
# 指定数据库 , 不指定的话会默认自动检测数据库类型
helper-dialect : mysql

三、集成Redis

提高运行速度,将以下语句(查询数据库的语句)去除,因此将它放到缓存中去,所以集成(Redis)缓存

 1、yml配置

redis:
  database: 0         #数据库索引
  host: 127.0.0.1     #主机位置
  port: 6379          #端口
  password: 123         #密码
  jedis:
    pool:
      max-active: 8   #最大连接数
      max-wait: -1    #最大阻塞等待时间(负数表示没限制)
      max-idle: 8     #最大空闲
      min-idle: 0     #最小空闲
  timeout: 10000      #连接超时时间

2、增加config软件包

@Configuration:变成配置类,被spring管理

①、CrossConfiguration类

  1. package com.mwy.code.conf;
  2. import org.springframework.context.annotation.Configuration;
  3. import org.springframework.web.servlet.config.annotation.CorsRegistry;
  4. import org.springframework.web.servlet.config.annotation.WebMvcConfigurerAdapter;
  5. @Configuration
  6. public class CrossConfiguration extends WebMvcConfigurerAdapter {
  7. @Override
  8. public void addCorsMappings(CorsRegistry registry) {
  9. registry
  10. /*可以跨域的路径*/
  11. .addMapping("/**")
  12. /*可以跨域的ip*/
  13. .allowedOrigins("*")
  14. /*可以跨域的方法*/
  15. .allowedMethods("*")
  16. /*设置预检请求多就失效*/
  17. .maxAge(6000)
  18. /*允许携带的头部*/
  19. .allowedHeaders("*");
  20. }
  21. }

②、RedisConfiguration类

增加pom依赖

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
@EnableCaching:开启缓存
  1. package com.mwy.code.conf;
  2. import org.springframework.cache.CacheManager;
  3. import org.springframework.cache.annotation.CachingConfigurerSupport;
  4. import org.springframework.cache.annotation.EnableCaching;
  5. import org.springframework.cache.interceptor.KeyGenerator;
  6. import org.springframework.context.annotation.Bean;
  7. import org.springframework.context.annotation.Configuration;
  8. import org.springframework.context.annotation.Primary;
  9. import org.springframework.data.redis.cache.RedisCacheConfiguration;
  10. import org.springframework.data.redis.cache.RedisCacheManager;
  11. import org.springframework.data.redis.connection.RedisConnectionFactory;
  12. import org.springframework.data.redis.core.RedisTemplate;
  13. import org.springframework.data.redis.serializer.GenericJackson2JsonRedisSerializer;
  14. import org.springframework.data.redis.serializer.RedisSerializationContext;
  15. import org.springframework.data.redis.serializer.RedisSerializer;
  16. import org.springframework.data.redis.serializer.StringRedisSerializer;
  17. import org.springframework.util.ClassUtils;
  18. import java.lang.reflect.Array;
  19. import java.lang.reflect.Method;
  20. import java.time.Duration;
  21. @Configuration
  22. @EnableCaching
  23. public class RedisConfiguration extends CachingConfigurerSupport {
  24. // @Bean 相当于 @component 成为spring的bean列项(组件容器的意思)
  25. // @Primar 相同类的情况下加它会被优先注入
  26. @Bean
  27. @Primary
  28. CacheManager cacheManager(RedisConnectionFactory factory) {
  29. RedisCacheConfiguration cacheConfiguration = RedisCacheConfiguration.defaultCacheConfig()
  30. .computePrefixWith(cacheName -> cacheName + ":-cache-:")
  31. /*设置缓存过期时间*/
  32. .entryTtl(Duration.ofHours(1))
  33. /*禁用缓存空值,不缓存null校验*/
  34. .disableCachingNullValues()
  35. /*设置CacheManager的值序列化方式为json序列化,可使用加入@Class属性*/
  36. .serializeValuesWith(RedisSerializationContext.SerializationPair.fromSerializer(
  37. new GenericJackson2JsonRedisSerializer()
  38. ));
  39. /*使用RedisCacheConfiguration创建RedisCacheManager*/
  40. RedisCacheManager manager = RedisCacheManager.builder(factory)
  41. .cacheDefaults(cacheConfiguration)
  42. .build();
  43. return manager;
  44. }
  45. @Bean
  46. @Primary
  47. public RedisTemplate redisTemplate(RedisConnectionFactory factory) {
  48. RedisTemplate<String, Object> redisTemplate = new RedisTemplate<String, Object>();
  49. redisTemplate.setConnectionFactory(factory);
  50. RedisSerializer stringSerializer = new StringRedisSerializer();
  51. /* key序列化 */
  52. redisTemplate.setKeySerializer(stringSerializer);
  53. /* value序列化 */
  54. redisTemplate.setValueSerializer(new GenericJackson2JsonRedisSerializer());
  55. /* Hash key序列化 */
  56. redisTemplate.setHashKeySerializer(stringSerializer);
  57. /* Hash value序列化 */
  58. redisTemplate.setHashValueSerializer(new GenericJackson2JsonRedisSerializer());
  59. redisTemplate.afterPropertiesSet();
  60. return redisTemplate;
  61. }
  62. @Bean
  63. @Primary
  64. @Override
  65. public KeyGenerator keyGenerator() {
  66. return (Object target, Method method, Object... params) -> {
  67. final int NO_PARAM_KEY = 0;
  68. final int NULL_PARAM_KEY = 53;
  69. StringBuilder key = new StringBuilder();
  70. /* Class.Method: */
  71. key.append(target.getClass().getSimpleName())
  72. .append(".")
  73. .append(method.getName())
  74. .append(":");
  75. if (params.length == 0) {
  76. return key.append(NO_PARAM_KEY).toString();
  77. }
  78. int count = 0;
  79. for (Object param : params) {
  80. /* 参数之间用,进行分隔 */
  81. if (0 != count) {
  82. key.append(',');
  83. }
  84. if (param == null) {
  85. key.append(NULL_PARAM_KEY);
  86. } else if (ClassUtils.isPrimitiveArray(param.getClass())) {
  87. int length = Array.getLength(param);
  88. for (int i = 0; i < length; i++) {
  89. key.append(Array.get(param, i));
  90. key.append(',');
  91. }
  92. } else if (ClassUtils.isPrimitiveOrWrapper(param.getClass()) || param instanceof String) {
  93. key.append(param);
  94. } else {
  95. /*JavaBean一定要重写hashCode和equals*/
  96. key.append(param.hashCode());
  97. }
  98. count++;
  99. }
  100. return key.toString();
  101. };
  102. }
  103. }

四、注解的使用

@Configuration: 用于定义配置类,可替换 xml 配置文件,被注解的类内部包含有一个或多个被 @Bean 注解的方法
@Bean: 用于定义加载类型
@CacheConfig:  一个类级别的注解,允许共享缓存的 cacheNames KeyGenerator CacheManager 和 CacheResolver. Cacheable 用来声明方法是可缓存的。将结果存储到缓存中以便后续使用相同参数调用时不需执行实际的方 法。直接从缓存中取值
@CachePut:  标注的方法在执行前不会去检查缓存中是否存在之前执行过的结果,而是每次都会执行该方法, 并将执行结果以键值对的形式存入指定的缓存中。
@CacheEvict : 的作用 主要针对方法配置,能够根据一定的条件对缓存进行清空

1、增加注解

StudentServiceImpl:

类中使用@cacheable注解开启缓存

package com.mwy.code.service;
import com.mwy.code.mapper.StudentMapper;
import com.mwy.code.pojo.Student;
import com.mwy.code.util.PageBean;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cache.annotation.Cacheable;
import org.springframework.stereotype.Service;
import java.util.List;
@Service
public class StudentServiceImpl implements StudentService {
    @Autowired
    private StudentMapper mapper;

    @Override
//    @Cacheable:会根据里面的内容生成一个组件 ,设置缓存名称为mm,cacheManager把缓存给缓存管理员,
    @Cacheable(cacheNames = "mm",cacheManager = "cacheManager")
    public List<Student> findPager(PageBean pageBean) {
        return mapper.selectAll();
    }
}

  这是第二次测试的结果,已经没有SQL语句了,在缓存中存在了

 查看缓存

2、查看缓存中是学生的

①、修改一下名字

在每一个结果前面加一个student关键字

cacheNames是缓存的名字用来做分类的,查看缓存时方便,可以在类上进行注解,表示所有的方法的缓存名字都为指定名字

package com.mwy.code.service;
import com.mwy.code.mapper.StudentMapper;
import com.mwy.code.pojo.Student;
import com.mwy.code.util.PageBean;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cache.annotation.CacheConfig;
import org.springframework.cache.annotation.Cacheable;
import org.springframework.stereotype.Service;
import java.util.List;

@Service
@CacheConfig(cacheNames = "student")
public class StudentServiceImpl implements StudentService {
    @Autowired
    private StudentMapper mapper;

    @Override
//    @Cacheable:会根据里面的内容生成一个组件 ,设置缓存名称为mm,cacheManager把缓存给缓存管理员,
    @Cacheable(cacheManager = "cacheManager")
    public List<Student> findPager(PageBean pageBean) {
        return mapper.selectAll();
    }
}

②、测试

3、cacheManager:是缓存管理器

第一次运行会出现sql语句,然后数据会进入redis缓存

①、修改cacheManager 

②、修改StudentServiceImpl

③、测试结果

4、keyGenerator方法用来生成缓存的主键 

①、RedisConfiguration中增加:

@Bean
@Primary
public KeyGenerator m1() {
    return (Object target, Method method, Object... params) -> {
        return LocalDate.now()+"";
    };
}

②、StudentServiceImpl:

key用来标识方法的结果,由键去找值,找不到进数据库,要去缓存用户信息,我们一般把用户的id作为键

spel表达式使用 取值

(1)cachePut用来更新缓存,用来做修改

(2)cacheEvict用来删除缓存,通过键key

 五、Template的使用

1、Template的使用

2、增加帮助类

  1. package com.mwy.code.util;
  2. import com.fasterxml.jackson.databind.ObjectMapper;
  3. import lombok.SneakyThrows;
  4. import lombok.extern.slf4j.Slf4j;
  5. import org.springframework.beans.BeansException;
  6. import org.springframework.context.ApplicationContext;
  7. import org.springframework.context.ApplicationContextAware;
  8. import org.springframework.data.redis.RedisSystemException;
  9. import org.springframework.data.redis.connection.DataType;
  10. import org.springframework.data.redis.connection.RedisConnection;
  11. import org.springframework.data.redis.connection.RedisStringCommands;
  12. import org.springframework.data.redis.connection.ReturnType;
  13. import org.springframework.data.redis.connection.jedis.JedisConnection;
  14. import org.springframework.data.redis.connection.lettuce.LettuceConnection;
  15. import org.springframework.data.redis.core.Cursor;
  16. import org.springframework.data.redis.core.RedisOperations;
  17. import org.springframework.data.redis.core.ScanOptions;
  18. import org.springframework.data.redis.core.StringRedisTemplate;
  19. import org.springframework.data.redis.core.ZSetOperations.TypedTuple;
  20. import org.springframework.data.redis.core.types.Expiration;
  21. import org.springframework.stereotype.Component;
  22. import java.nio.charset.StandardCharsets;
  23. import java.time.Instant;
  24. import java.util.*;
  25. import java.util.Map.Entry;
  26. import java.util.concurrent.TimeUnit;
  27. /**
  28. * Redis工具类
  29. * <p>
  30. * 声明: 此工具只简单包装了redisTemplate的大部分常用的api,没有包装redisTemplate所有的api
  31. * 如果对此工具类中的功能不太满意,或对StringRedisTemplate提供的api不太满意,
  32. * 那么可自行实现相应的{@link StringRedisTemplate}类中的对应execute方法,以达
  33. * 到自己想要的效果; 至于如何实现,则可参考源码或{@link LockOps}中的方法
  34. * <p>
  35. * 注: 此工具类依赖spring-boot-starter-data-redis类库
  36. * 注: 更多javadoc细节,可详见{@link RedisOperations}
  37. * <p>
  38. * 统一说明一: 方法中的key、 value都不能为null
  39. * 统一说明二: 不能跨数据类型进行操作,否者会操作失败/操作报错
  40. * 如: 向一个String类型的做Hash操作,会失败/报错......等等
  41. */
  42. @Slf4j
  43. @Component
  44. @SuppressWarnings("unused")
  45. public class RedisUtil implements ApplicationContextAware {
  46. /**
  47. * 使用StringRedisTemplate(,其是RedisTemplate的定制化升级)
  48. */
  49. private static StringRedisTemplate redisTemplate;
  50. private static final ObjectMapper mapper = new ObjectMapper();
  51. @Override
  52. public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
  53. RedisUtil.redisTemplate = applicationContext.getBean(StringRedisTemplate.class);
  54. }
  55. /**
  56. * key相关操作
  57. */
  58. public static class KeyOps {
  59. /**
  60. * 根据key,删除redis中的对应key-value
  61. * <p>
  62. * 注: 若删除失败,则返回false
  63. * <p>
  64. * 若redis中,不存在该key,那么返回的也是false
  65. * 所以,不能因为返回了false,就认为redis中一定还存
  66. * 在该key对应的key-value
  67. *
  68. * @param key 要删除的key
  69. * @return 删除是否成功
  70. */
  71. public static Boolean delete(String key) {
  72. log.info("delete(...) => key -> {}", key);
  73. // 返回值只可能为true/false,不可能为null
  74. Boolean result = redisTemplate.delete(key);
  75. log.info("delete(...) => result -> {}", result);
  76. if (result == null) {
  77. throw new RedisOpsResultIsNullException();
  78. }
  79. return result;
  80. }
  81. /**
  82. * 根据keys,批量删除key-value
  83. * <p>
  84. * 注: 若redis中,不存在对应的key,那么计数不会加1,即:
  85. * redis中存在的key-value里,有名为a1、a2key,
  86. * 删除时,传的集合是a1、a2、a3,那么返回结果为2
  87. *
  88. * @param keys 要删除的key集合
  89. * @return 删除了的key-value个数
  90. */
  91. public static long delete(Collection<String> keys) {
  92. log.info("delete(...) => keys -> {}", keys);
  93. Long count = redisTemplate.delete(keys);
  94. log.info("delete(...) => count -> {}", count);
  95. if (count == null) {
  96. throw new RedisOpsResultIsNullException();
  97. }
  98. return count;
  99. }
  100. /**
  101. *key对应的value值进行序列化,并返回序列化后的value
  102. * <p>
  103. * 注: 若不存在对应的key,则返回null
  104. * 注: dump时,并不会删除redis中的对应key-value
  105. * 注: dump功能与restore相反
  106. *
  107. * @param key 要序列化的valuekey
  108. * @return 序列化后的value
  109. */
  110. public static byte[] dump(String key) {
  111. log.info("dump(...) =>key -> {}", key);
  112. byte[] result = redisTemplate.dump(key);
  113. log.info("dump(...) => result -> {}", result);
  114. return result;
  115. }
  116. /**
  117. * 将给定的value值,反序列化到redis中,形成新的key-value
  118. *
  119. * @param key value对应的key
  120. * @param value 要反序列的value
  121. * 注: 这个值可以由{@link this#dump(String)}获得
  122. * @param timeToLive 反序列化后的key-value的存活时长
  123. * @param unit timeToLive的单位
  124. * @throws RedisSystemException 如果redis中已存在同样的key时,抛出此异常
  125. */
  126. public static void restore(String key, byte[] value, long timeToLive, TimeUnit unit) {
  127. restore(key, value, timeToLive, unit, false);
  128. }
  129. /**
  130. * 将给定的value值,反序列化到redis中,形成新的key-value
  131. *
  132. * @param key value对应的key
  133. * @param value 要反序列的value
  134. * 注: 这个值可以由{@link this#dump(String)}获得
  135. * @param timeout 反序列化后的key-value的存活时长
  136. * @param unit timeout的单位
  137. * @param replace 若redis中已经存在了相同的key,是否替代原来的key-value
  138. * @throws RedisSystemException 如果redis中已存在同样的key,且replacefalse时,抛出此异常
  139. */
  140. public static void restore(String key, byte[] value, long timeout, TimeUnit unit, boolean replace) {
  141. log.info("restore(...) => key -> {},value -> {},timeout -> {},unit -> {},replace -> {}",
  142. key, value, timeout, unit, replace);
  143. redisTemplate.restore(key, value, timeout, unit, replace);
  144. }
  145. /**
  146. * redis中是否存在,指定key的key-value
  147. *
  148. * @param key 指定的key
  149. * @return 是否存在对应的key-value
  150. */
  151. public static boolean hasKey(String key) {
  152. log.info("hasKey(...) => key -> {}", key);
  153. Boolean result = redisTemplate.hasKey(key);
  154. log.info("hasKey(...) => result -> {}", result);
  155. if (result == null) {
  156. throw new RedisOpsResultIsNullException();
  157. }
  158. return result;
  159. }
  160. /**
  161. * 给指定的key对应的key-value设置: 多久过时
  162. * <p>
  163. * 注:过时后,redis会自动删除对应的key-value
  164. * 注:若key不存在,那么也会返回false
  165. *
  166. * @param key 指定的key
  167. * @param timeout 过时时间
  168. * @param unit timeout的单位
  169. * @return 操作是否成功
  170. */
  171. public static boolean expire(String key, long timeout, TimeUnit unit) {
  172. log.info("expire(...) => key -> {},timeout -> {},unit -> {}", key, timeout, unit);
  173. Boolean result = redisTemplate.expire(key, timeout, unit);
  174. log.info("expire(...) => result is -> {}", result);
  175. if (result == null) {
  176. throw new RedisOpsResultIsNullException();
  177. }
  178. return result;
  179. }
  180. /**
  181. * 给指定的key对应的key-value设置: 什么时候过时
  182. * <p>
  183. * 注:过时后,redis会自动删除对应的key-value
  184. * 注:若key不存在,那么也会返回false
  185. *
  186. * @param key 指定的key
  187. * @param date 啥时候过时
  188. * @return 操作是否成功
  189. */
  190. public static boolean expireAt(String key, Date date) {
  191. log.info("expireAt(...) => key -> {},date -> {}", key, date);
  192. Boolean result = redisTemplate.expireAt(key, date);
  193. log.info("expireAt(...) => result is -> {}", result);
  194. if (result == null) {
  195. throw new RedisOpsResultIsNullException();
  196. }
  197. return result;
  198. }
  199. /**
  200. * 找到所有匹配pattern的key,并返回该key的结合.
  201. * <p>
  202. * 提示:若redis中键值对较多,此方法耗时相对较长,慎用!慎用!慎用!
  203. *
  204. * @param pattern 匹配模板
  205. * 注: 常用的通配符有:
  206. * ? 有且只有一个;
  207. * * >=0个;
  208. * @return 匹配pattern的key的集合 可能为null
  209. */
  210. public static Set<String> keys(String pattern) {
  211. log.info("keys(...) => pattern -> {}", pattern);
  212. Set<String> keys = redisTemplate.keys(pattern);
  213. log.info("keys(...) => keys -> {}", keys);
  214. return keys;
  215. }
  216. /**
  217. * 将当前数据库中的key对应的key-value,移动到对应位置的数据库中
  218. * <p>
  219. * 注:单机版的redis,默认将存储分为16个db,index015
  220. * 注:同一个db下,key唯一; 但是在不同db中,key可以相同
  221. * 注:若目标db下,已存在相同的key,那么move会失败,返回false
  222. *
  223. * @param key 定位要移动的key-value的key
  224. * @param dbIndex 要移动到哪个db
  225. * @return 移动是否成功
  226. * 注: 若目标db下,已存在相同的key,那么move会失败,返回false
  227. */
  228. public static boolean move(String key, int dbIndex) {
  229. log.info("move(...) => key -> {},dbIndex -> {}", key, dbIndex);
  230. Boolean result = redisTemplate.move(key, dbIndex);
  231. log.info("move(...) =>result -> {}", result);
  232. if (result == null) {
  233. throw new RedisOpsResultIsNullException();
  234. }
  235. return result;
  236. }
  237. /**
  238. * 移除key对应的key-value的过期时间,使该key-value一直存在
  239. * <p>
  240. * 注: 若key对应的key-value,本身就是一直存在(无过期时间的),那么persist方法会返回false;
  241. * 若没有key对应的key-value存在,本那么persist方法会返回false;
  242. *
  243. * @param key 定位key-value的key
  244. * @return 操作是否成功
  245. */
  246. public static boolean persist(String key) {
  247. log.info("persist(...) => key -> {}", key);
  248. Boolean result = redisTemplate.persist(key);
  249. log.info("persist(...) => result -> {}", result);
  250. if (result == null) {
  251. throw new RedisOpsResultIsNullException();
  252. }
  253. return result;
  254. }
  255. /**
  256. * 获取key对应的key-value的过期时间
  257. * <p>
  258. * 注: 若key-value永不过期,那么返回的为-1
  259. * 注: 若不存在key对应的key-value,那么返回的为-2
  260. * 注:若存在零碎时间不足1 SECONDS,则(大体上)四舍五入到SECONDS级别
  261. *
  262. * @param key 定位key-value的key
  263. * @return 过期时间(单位s)
  264. */
  265. public static long getExpire(String key) {
  266. return getExpire(key, TimeUnit.SECONDS);
  267. }
  268. /**
  269. * 获取key对应的key-value的过期时间
  270. * <p>
  271. * 注: 若key-value永不过期,那么返回的为-1
  272. * 注: 若不存在key对应的key-value,那么返回的为-2
  273. * 注:若存在零碎时间不足1 unit,则(大体上)四舍五入到unit
  274. *
  275. * @param key 定位key-value的key
  276. * @return 过期时间(单位unit)
  277. */
  278. public static long getExpire(String key, TimeUnit unit) {
  279. log.info("getExpire(...) =>key -> {},unit is -> {}", key, unit);
  280. Long result = redisTemplate.getExpire(key, unit);
  281. log.info("getExpire(...) => result -> {}", result);
  282. if (result == null) {
  283. throw new RedisOpsResultIsNullException();
  284. }
  285. return result;
  286. }
  287. /**
  288. * 从redis的所有key中,随机获取一个key
  289. * <p>
  290. * 注: 若redis中不存在任何key-value,那么这里返回null
  291. *
  292. * @return 随机获取到的一个key
  293. */
  294. public static String randomKey() {
  295. String result = redisTemplate.randomKey();
  296. log.info("randomKey(...) => result is -> {}", result);
  297. return result;
  298. }
  299. /**
  300. * 重命名对应的oldKey为新的newKey
  301. * <p>
  302. * 注: 若oldKey不存在,则会抛出异常.
  303. * 注: 若redis中已存在与newKey一样的key,
  304. * 那么原key-value会被丢弃,
  305. * 只留下新的key,以及原来的value
  306. * 示例说明: 假设redis中已有 (keyAlpha,valueAlpha) 和 (keyBeta,valueBeat),
  307. * 在使用rename(keyAlpha,keyBeta)替换后,redis中只会剩下(keyBeta,valueAlpha)
  308. *
  309. * @param oldKey 旧的key
  310. * @param newKey 新的key
  311. * @throws RedisSystemException 若oldKey不存在时,抛出此异常
  312. */
  313. public static void rename(String oldKey, String newKey) {
  314. log.info("rename(...) => oldKey -> {},newKey -> {}", oldKey, newKey);
  315. redisTemplate.rename(oldKey, newKey);
  316. }
  317. /**
  318. * 当redis中不存在newKey时,重命名对应的oldKey为新的newKey
  319. * 否者不进行重命名操作
  320. * <p>
  321. * 注: 若oldKey不存在,则会抛出异常.
  322. *
  323. * @param oldKey 旧的key
  324. * @param newKey 新的key
  325. * @throws RedisSystemException 若oldKey不存在时,抛出此异常
  326. */
  327. public static boolean renameIfAbsent(String oldKey, String newKey) {
  328. log.info("renameIfAbsent(...) => oldKey -> {},newKey -> {}", oldKey, newKey);
  329. Boolean result = redisTemplate.renameIfAbsent(oldKey, newKey);
  330. log.info("renameIfAbsent(...) => result -> {}", result);
  331. if (result == null) {
  332. throw new RedisOpsResultIsNullException();
  333. }
  334. return result;
  335. }
  336. /**
  337. * 获取key对应的value的数据类型
  338. * <p>
  339. * 注: 若redis中不存在该key对应的key-value,那么这里返回NONE
  340. *
  341. * @param key 用于定位的key
  342. * @return key对应的value的数据类型
  343. */
  344. public static DataType type(String key) {
  345. log.info("type(...) => key -> {}", key);
  346. DataType result = redisTemplate.type(key);
  347. log.info("type(...) => result -> {}", result);
  348. return result;
  349. }
  350. }
  351. /**
  352. * string相关操作
  353. */
  354. public static class StringOps {
  355. /**
  356. * 设置key-value
  357. * <p>
  358. * 注: 若已存在相同的key,那么原来的key-value会被丢弃
  359. *
  360. * @param key key
  361. * @param value key对应的value
  362. */
  363. public static void set(String key, String value) {
  364. log.info("set(...) => key -> {},value -> {}", key, value);
  365. redisTemplate.opsForValue().set(key, value);
  366. }
  367. /**
  368. * 处理redis中key对应的value值,将第offset位的值,设置为10
  369. * <p>
  370. * 说明: 在redis中,存储的字符串都是以二级制的进行存在的; 如存储的key-value里,值为abc,实际上,
  371. * 在redis里面存储的是011000010110001001100011,前8为对应a,中间8为对应b,后面8位对应c
  372. * 示例:这里如果setBit(key,6,true)的话,就是将索引位置6的那个数,设置值为1,值就变成
  373. *011000110110001001100011
  374. * 追注:offset即index,从0开始
  375. * <p>
  376. * 注: 参数valuetrue,则设置为1;参数valuefalse,则设置为0
  377. * <p>
  378. * 注: 若redis中不存在对应的key,那么会自动创建新的
  379. * 注: offset可以超过value在二进制下的索引长度
  380. *
  381. * @param key 定位valuekey
  382. * @param offset 要改变的bit的索引
  383. * @param value 改为10,true - 改为1,false - 改为0
  384. * @return set是否成功
  385. */
  386. public static boolean setBit(String key, long offset, boolean value) {
  387. log.info("setBit(...) => key -> {},offset -> {},value -> {}", key, offset, value);
  388. Boolean result = redisTemplate.opsForValue().setBit(key, offset, value);
  389. log.info("setBit(...) => result -> {}", result);
  390. if (result == null) {
  391. throw new RedisOpsResultIsNullException();
  392. }
  393. return result;
  394. }
  395. /**
  396. * 设置key-value
  397. * <p>
  398. * 注: 若已存在相同的key,那么原来的key-value会被丢弃
  399. *
  400. * @param key key
  401. * @param value key对应的value
  402. * @param timeout 过时时长
  403. * @param unit timeout的单位
  404. */
  405. public static void setEx(String key, String value, long timeout, TimeUnit unit) {
  406. log.info("setEx(...) => key -> {},value -> {},timeout -> {},unit -> {}",
  407. key, value, timeout, unit);
  408. redisTemplate.opsForValue().set(key, value, timeout, unit);
  409. }
  410. /**
  411. * 若不存在key时,向redis中添加key-value,返回成功/失败
  412. * 若存在,则不作任何操作,返回false
  413. *
  414. * @param key key
  415. * @param value key对应的value
  416. * @return set是否成功
  417. */
  418. public static boolean setIfAbsent(String key, String value) {
  419. log.info("setIfAbsent(...) => key -> {},value -> {}", key, value);
  420. Boolean result = redisTemplate.opsForValue().setIfAbsent(key, value);
  421. log.info("setIfAbsent(...) => result -> {}", result);
  422. if (result == null) {
  423. throw new RedisOpsResultIsNullException();
  424. }
  425. return result;
  426. }
  427. /**
  428. * 若不存在key时,向redis中添加一个(具有超时时长的)key-value,返回成功/失败
  429. * 若存在,则不作任何操作,返回false
  430. *
  431. * @param key key
  432. * @param value key对应的value
  433. * @param timeout 超时时长
  434. * @param unit timeout的单位
  435. * @return set是否成功
  436. */
  437. public static boolean setIfAbsent(String key, String value, long timeout, TimeUnit unit) {
  438. log.info("setIfAbsent(...) => key -> {},value -> {},key -> {},value -> {}", key, value, timeout, unit);
  439. Boolean result = redisTemplate.opsForValue().setIfAbsent(key, value, timeout, unit);
  440. log.info("setIfAbsent(...) => result -> {}", result);
  441. if (result == null) {
  442. throw new RedisOpsResultIsNullException();
  443. }
  444. return result;
  445. }
  446. /**
  447. * 从(redis中key对应的)value的offset位置起(包含该位置),用replaceValue替换对应长度的值
  448. * <p>
  449. * 举例说明:
  450. * 1.假设redis中存在key-value ("ds","0123456789"); 调
  451. * 用setRange("ds","abcdefghijk",3)后,redis中该value值就变为了[012abcdefghijk]
  452. * <p>
  453. * 2.假设redis中存在key-value ("jd","0123456789");调
  454. * 用setRange("jd","xyz",3)后,redis中该value值就变为了[012xyz6789]
  455. * <p>
  456. * 3.假设redis中存在key-value ("ey","0123456789");调
  457. * 用setRange("ey","qwer",15)后,redis中该value值就变为了[0123456789 qwer]
  458. * 注:case3比较特殊,offset超过了原value的长度了,中间就会有一些空格来填充,但是如果在程序
  459. * 中直接输出的话,中间那部分空格可能会出现乱码
  460. *
  461. * @param key 定位key-value的key
  462. * @param replaceValue 要替换的值
  463. * @param offset 起始位置
  464. */
  465. public static void setRange(String key, String replaceValue, long offset) {
  466. log.info("setRange(...) => key -> {},replaceValue -> {},offset -> {}", key, replaceValue, offset);
  467. redisTemplate.opsForValue().set(key, replaceValue, offset);
  468. }
  469. /**
  470. * 获取到key对应的value的长度
  471. * <p>
  472. * 注: 长度等于{@link String#length}
  473. * 注: 若redis中不存在对应的key-value,则返回值为0.
  474. *
  475. * @param key 定位valuekey
  476. * @return value的长度
  477. */
  478. public static long size(String key) {
  479. log.info("size(...) => key -> {}", key);
  480. Long result = redisTemplate.opsForValue().size(key);
  481. log.info("size(...) => result -> {}", result);
  482. if (result == null) {
  483. throw new RedisOpsResultIsNullException();
  484. }
  485. return result;
  486. }
  487. /**
  488. * 批量设置 key-value
  489. * <p>
  490. * 注: 若存在相同的key,则原来的key-value会被丢弃
  491. *
  492. * @param maps key-value 集
  493. */
  494. public static void multiSet(Map<String, String> maps) {
  495. log.info("multiSet(...) => maps -> {}", maps);
  496. redisTemplate.opsForValue().multiSet(maps);
  497. }
  498. /**
  499. * 当redis中,不存在任何一个keys时,才批量设置 key-value,并返回成功/失败.
  500. * 否者,不进行任何操作,并返回false
  501. * <p>
  502. * 即: 假设调用此方法时传入的参数map是这样的: {k1=v1,k2=v2,k3=v3}
  503. * 那么redis中,k1、k2、k3都不存在时,才会批量设置key-value;
  504. * 否则不会设置任何key-value
  505. * <p>
  506. * 注: 若存在相同的key,则原来的key-value会被丢弃
  507. * <p>
  508. * 注:
  509. *
  510. * @param maps key-value 集
  511. * @return 操作是否成功
  512. */
  513. public static boolean multiSetIfAbsent(Map<String, String> maps) {
  514. log.info("multiSetIfAbsent(...) => maps -> {}", maps);
  515. Boolean result = redisTemplate.opsForValue().multiSetIfAbsent(maps);
  516. log.info("multiSetIfAbsent(...) => result -> {}", result);
  517. if (result == null) {
  518. throw new RedisOpsResultIsNullException();
  519. }
  520. return result;
  521. }
  522. /**
  523. */减 整数
  524. * <p>
  525. * 注: 负数则为减
  526. * 注: 若key对应的value值不支持增/减操作(即: value不是数字),那么会
  527. * 抛出org.springframework.data.redis.RedisSystemException
  528. *
  529. * @param key 用于定位valuekey
  530. * @param increment 增加多少
  531. * @return 增加后的总值
  532. * @throws RedisSystemException key对应的value值不支持增/减操作时
  533. */
  534. public static long incrBy(String key, long increment) {
  535. log.info("incrBy(...) => key -> {},increment -> {}", key, increment);
  536. Long result = redisTemplate.opsForValue().increment(key, increment);
  537. log.info("incrBy(...) => result -> {}", result);
  538. if (result == null) {
  539. throw new RedisOpsResultIsNullException();
  540. }
  541. return result;
  542. }
  543. /**
  544. */减 浮点数
  545. * <p>
  546. * 注: 慎用浮点数,会有精度问题
  547. * 如: 先 RedisUtil.StringOps.set("ds","123");
  548. * 然后再RedisUtil.StringOps.incrByFloat("ds",100.6);
  549. * 就会看到精度问题
  550. * 注: 负数则为减
  551. * 注: 若key对应的value值不支持增/减操作(即: value不是数字),那么会
  552. * 抛出org.springframework.data.redis.RedisSystemException
  553. *
  554. * @param key 用于定位valuekey
  555. * @param increment 增加多少
  556. * @return 增加后的总值
  557. * @throws RedisSystemException key对应的value值不支持增/减操作时
  558. */
  559. public static double incrByFloat(String key, double increment) {
  560. log.info("incrByFloat(...) => key -> {},increment -> {}", key, increment);
  561. Double result = redisTemplate.opsForValue().increment(key, increment);
  562. log.info("incrByFloat(...) => result -> {}", result);
  563. if (result == null) {
  564. throw new RedisOpsResultIsNullException();
  565. }
  566. return result;
  567. }
  568. /**
  569. * 追加值到末尾
  570. * <p>
  571. * 注: 当redis中原本不存在key时,那么(从效果上来看)此方法就等价于{@link this#set(String, String)}
  572. *
  573. * @param key 定位valuekey
  574. * @param value 要追加的value
  575. * @return 追加后, 整个value的长度
  576. */
  577. public static int append(String key, String value) {
  578. log.info("append(...) => key -> {},value -> {}", key, value);
  579. Integer result = redisTemplate.opsForValue().append(key, value);
  580. log.info("append(...) => result -> {}", result);
  581. if (result == null) {
  582. throw new RedisOpsResultIsNullException();
  583. }
  584. return result;
  585. }
  586. /**
  587. * 根据key,获取到对应的value
  588. *
  589. * @param key key-value对应的key
  590. * @returnkey对应的值
  591. * 注: 若key不存在,则返回null
  592. */
  593. public static String get(String key) {
  594. log.info("get(...) => key -> {}", key);
  595. String result = redisTemplate.opsForValue().get(key);
  596. log.info("get(...) => result -> {} ", result);
  597. return result;
  598. }
  599. /**
  600. * 对(key对应的)value进行截取,截取范围为[start,end]
  601. * <p>
  602. * 注: 若[start,end]的范围不在value的范围中,那么返回的是空字符串 ""
  603. * 注: 若value只有一部分在[start,end]的范围中,那么返回的是value对应部分的内容(即:不足的地方,并不会以空来填充)
  604. *
  605. * @param key 定位valuekey
  606. * @param start 起始位置 (从0开始)
  607. * @param end 结尾位置 (从0开始)
  608. * @return 截取后的字符串
  609. */
  610. public static String getRange(String key, long start, long end) {
  611. log.info("getRange(...) => kry -> {}", key);
  612. String result = redisTemplate.opsForValue().get(key, start, end);
  613. log.info("getRange(...) => result -> {} ", result);
  614. return result;
  615. }
  616. /**
  617. * 给指定key设置新的value,并返回旧的value
  618. * <p>
  619. * 注: 若redis中不存在key,那么此操作仍然可以成功,不过返回的旧值是null
  620. *
  621. * @param key 定位valuekey
  622. * @param newValue 要为该key设置的新的value
  623. * @return 旧的value
  624. */
  625. public static String getAndSet(String key, String newValue) {
  626. log.info("getAndSet(...) => key -> {},value -> {}", key, newValue);
  627. String oldValue = redisTemplate.opsForValue().getAndSet(key, newValue);
  628. log.info("getAndSet(...) => oldValue -> {}", oldValue);
  629. return oldValue;
  630. }
  631. /**
  632. * 获取(key对应的)value在二进制下,offset位置的bit
  633. * <p>
  634. * 注: 当offset的值在(二进制下的value的)索引范围外时,返回的也是false
  635. * <p>
  636. * 示例:
  637. * RedisUtil.StringOps.set("akey","a");
  638. * 字符串a,转换为二进制为01100001
  639. * 那么getBit("akey",6)获取到的结果为false
  640. *
  641. * @param key 定位valuekey
  642. * @param offset 定位bit的索引
  643. * @return offset位置对应的bit的值(true - 1, false - 0)
  644. */
  645. public static boolean getBit(String key, long offset) {
  646. log.info("getBit(...) => key -> {},offset -> {}", key, offset);
  647. Boolean result = redisTemplate.opsForValue().getBit(key, offset);
  648. log.info("getBit(...) => result -> {}", result);
  649. if (result == null) {
  650. throw new RedisOpsResultIsNullException();
  651. }
  652. return result;
  653. }
  654. /**
  655. * 批量获取value
  656. * <p>
  657. * 注: 若redis中,对应的key不存在,那么该key对应的返回的value值为null
  658. *
  659. * @param keys key
  660. * @return value值集合
  661. */
  662. public static List<String> multiGet(Collection<String> keys) {
  663. log.info("multiGet(...) => keys -> {}", keys);
  664. List<String> result = redisTemplate.opsForValue().multiGet(keys);
  665. log.info("multiGet(...) => result -> {}", result);
  666. return result;
  667. }
  668. }
  669. /**
  670. * hash相关操作
  671. * <p>
  672. * 提示: 简单的,可以将redis中hash的数据结构看作是 Map<String,Map<HK,HV>>
  673. */
  674. public static class HashOps {
  675. /**
  676. *key对应的hash中,增加一个键值对entryKey-entryValue
  677. * <p>
  678. * 注: 同一个hash里面,若已存在相同的entryKey,那么此操作将丢弃原来的entryKey-entryValue,
  679. * 而使用新的entryKey-entryValue
  680. *
  681. * @param key 定位hash的key
  682. * @param entryKey 要向hash中增加的键值对里的 键
  683. * @param entryValue 要向hash中增加的键值对里的 值
  684. */
  685. public static void hPut(String key, String entryKey, String entryValue) {
  686. log.info("hPut(...) => key -> {},entryKey -> {},entryValue -> {}", key, entryKey, entryValue);
  687. redisTemplate.opsForHash().put(key, entryKey, entryValue);
  688. }
  689. /**
  690. *key对应的hash中,增加maps(即: 批量增加entry集)
  691. * <p>
  692. * 注: 同一个hash里面,若已存在相同的entryKey,那么此操作将丢弃原来的entryKey-entryValue,
  693. * 而使用新的entryKey-entryValue
  694. *
  695. * @param key 定位hash的key
  696. * @param maps 要向hash中增加的键值对集
  697. */
  698. public static void hPutAll(String key, Map<String, String> maps) {
  699. log.info("hPutAll(...) => key -> {},maps -> {}", key, maps);
  700. redisTemplate.opsForHash().putAll(key, maps);
  701. }
  702. /**
  703. *key对应的hash中,不存在entryKey时,才(向key对应的hash中,)增加entryKey-entryValue
  704. * 否则,不进行任何操作
  705. *
  706. * @param key 定位hash的key
  707. * @param entryKey 要向hash中增加的键值对里的 键
  708. * @param entryValue 要向hash中增加的键值对里的 值
  709. * @return 操作是否成功
  710. */
  711. public static boolean hPutIfAbsent(String key, String entryKey, String entryValue) {
  712. log.info("hPutIfAbsent(...) => key -> {},entryKey -> {},entryValue -> {}",
  713. key, entryKey, entryValue);
  714. Boolean result = redisTemplate.opsForHash().putIfAbsent(key, entryKey, entryValue);
  715. log.info("hPutIfAbsent(...) => result -> {}", result);
  716. if (result != null) {
  717. throw new RedisOpsResultIsNullException();
  718. }
  719. return result;
  720. }
  721. /**
  722. * 获取到key对应的hash里面的对应字段的值
  723. * <p>
  724. * 注: 若redis中不存在对应的key,则返回null
  725. *key对应的hash中不存在对应的entryKey,也会返回null
  726. *
  727. * @param key 定位hash的key
  728. * @param entryKey 定位hash里面的entryValue的entryKey
  729. * @return key对应的hash里的entryKey对应的entryValue值
  730. */
  731. public static Object hGet(String key, String entryKey) {
  732. log.info("hGet(...) => key -> {},entryKey -> {}", key, entryKey);
  733. Object entryValue = redisTemplate.opsForHash().get(key, entryKey);
  734. log.info("hGet(...) => entryValue -> {}", entryValue);
  735. return entryValue;
  736. }
  737. /**
  738. * 获取到key对应的hash(即: 获取到key对应的Map<HK,HV>)
  739. * <p>
  740. * 注: 若redis中不存在对应的key,则返回一个没有任何entry的空的Map(,而不是返回null)
  741. *
  742. * @param key 定位hash的key
  743. * @return key对应的hash
  744. */
  745. public static Map<Object, Object> hGetAll(String key) {
  746. log.info("hGetAll(...) => key -> {}", key);
  747. Map<Object, Object> result = redisTemplate.opsForHash().entries(key);
  748. log.info("hGetAll(...) => result -> {}", result);
  749. return result;
  750. }
  751. /**
  752. * 批量获取(key对应的)hash中的entryKey的entryValue
  753. * <p>
  754. * 注: 若hash中对应的entryKey不存在,那么返回的对应的entryValue值为null
  755. * 注: redis中key不存在,那么返回的List中,每个元素都为null
  756. * 追注: 这个List本身不为null,size也不为0,只是每个list中的每个元素为null而已
  757. *
  758. * @param key 定位hash的key
  759. * @param entryKeys 需要获取的hash中的字段集
  760. * @return hash中对应entryKeys的对应entryValue集
  761. */
  762. public static List<Object> hMultiGet(String key, Collection<Object> entryKeys) {
  763. log.info("hMultiGet(...) => key -> {},entryKeys -> {}", key, entryKeys);
  764. List<Object> entryValues = redisTemplate.opsForHash().multiGet(key, entryKeys);
  765. log.info("hMultiGet(...) => entryValues -> {}", entryValues);
  766. return entryValues;
  767. }
  768. /**
  769. * (批量)删除(key对应的)hash中的对应entryKey-entryValue
  770. * <p>
  771. * 注: 1、若redis中不存在对应的key,则返回0;
  772. * 2、若要删除的entryKey,在key对应的hash中不存在,在count不会+1,如:
  773. * RedisUtil.HashOps.hPut("ds","name","邓沙利文");
  774. * RedisUtil.HashOps.hPut("ds","birthday","1994-02-05");
  775. * RedisUtil.HashOps.hPut("ds","hobby","女");
  776. * 则调用RedisUtil.HashOps.hDelete("ds","name","birthday","hobby","non-exist-entryKey")
  777. * 的返回结果为3
  778. * 注: 若(key对应的)hash中的所有entry都被删除了,那么该key也会被删除
  779. *
  780. * @param key 定位hash的key
  781. * @param entryKeys 定位要删除的entryKey-entryValue的entryKey
  782. * @return 删除了对应hash中多少个entry
  783. */
  784. public static long hDelete(String key, Object... entryKeys) {
  785. log.info("hDelete(...) => key -> {},entryKeys -> {}", key, entryKeys);
  786. Long count = redisTemplate.opsForHash().delete(key, entryKeys);
  787. log.info("hDelete(...) => count -> {}", count);
  788. if (count == null) {
  789. throw new RedisOpsResultIsNullException();
  790. }
  791. return count;
  792. }
  793. /**
  794. * 查看(key对应的)hash中,是否存在entryKey对应的entry
  795. * <p>
  796. * 注: 若redis中不存在key,则返回false
  797. * 注: 若key对应的hash中不存在对应的entryKey,也会返回false
  798. *
  799. * @param key 定位hash的key
  800. * @param entryKey 定位hash中entry的entryKey
  801. * @return hash中是否存在entryKey对应的entry.
  802. */
  803. public static boolean hExists(String key, String entryKey) {
  804. log.info("hDelete(...) => key -> {},entryKeys -> {}", key, entryKey);
  805. Boolean exist = redisTemplate.opsForHash().hasKey(key, entryKey);
  806. log.info("hDelete(...) => exist -> {}", exist);
  807. return exist;
  808. }
  809. /**
  810. */减(hash中的某个entryValue值) 整数
  811. * <p>
  812. * 注: 负数则为减
  813. * 注: 若key不存在,那么会自动创建对应的hash,并创建对应的entryKey、entryValue,entryValue的初始值为increment
  814. * 注: 若entryKey不存在,那么会自动创建对应的entryValue,entryValue的初始值为increment
  815. * 注: 若key对应的value值不支持增/减操作(即: value不是数字),那么会
  816. * 抛出org.springframework.data.redis.RedisSystemException
  817. *
  818. * @param key 用于定位hash的key
  819. * @param entryKey 用于定位entryValue的entryKey
  820. * @param increment 增加多少
  821. * @return 增加后的总值
  822. * @throws RedisSystemException key对应的value值不支持增/减操作时
  823. */
  824. public static long hIncrBy(String key, Object entryKey, long increment) {
  825. log.info("hIncrBy(...) => key -> {},entryKey -> {},increment -> {}",
  826. key, entryKey, increment);
  827. Long result = redisTemplate.opsForHash().increment(key, entryKey, increment);
  828. log.info("hIncrBy(...) => result -> {}", result);
  829. if (result == null) {
  830. throw new RedisOpsResultIsNullException();
  831. }
  832. return result;
  833. }
  834. /**
  835. */减(hash中的某个entryValue值) 浮点数
  836. * <p>
  837. * 注: 负数则为减
  838. * 注: 若key不存在,那么会自动创建对应的hash,并创建对应的entryKey、entryValue,entryValue的初始值为increment
  839. * 注: 若entryKey不存在,那么会自动创建对应的entryValue,entryValue的初始值为increment
  840. * 注: 若key对应的value值不支持增/减操作(即: value不是数字),那么会
  841. * 抛出org.springframework.data.redis.RedisSystemException
  842. * 注: 因为是浮点数,所以可能会和{@link StringOps#incrByFloat(String, double)}一样,出现精度问题
  843. * 追注: 本人简单测试了几组数据,暂未出现精度问题
  844. *
  845. * @param key 用于定位hash的key
  846. * @param entryKey 用于定位entryValue的entryKey
  847. * @param increment 增加多少
  848. * @return 增加后的总值
  849. * @throws RedisSystemException key对应的value值不支持增/减操作时
  850. */
  851. public static double hIncrByFloat(String key, Object entryKey, double increment) {
  852. log.info("hIncrByFloat(...) => key -> {},entryKey -> {},increment -> {}",
  853. key, entryKey, increment);
  854. Double result = redisTemplate.opsForHash().increment(key, entryKey, increment);
  855. log.info("hIncrByFloat(...) => result -> {}", result);
  856. if (result == null) {
  857. throw new RedisOpsResultIsNullException();
  858. }
  859. return result;
  860. }
  861. /**
  862. * 获取(key对应的)hash中的所有entryKey
  863. * <p>
  864. * 注: 若key不存在,则返回的是一个空的Set(,而不是返回null)
  865. *
  866. * @param key 定位hash的key
  867. * @return hash中的所有entryKey
  868. */
  869. public static Set<Object> hKeys(String key) {
  870. log.info("hKeys(...) => key -> {}", key);
  871. Set<Object> entryKeys = redisTemplate.opsForHash().keys(key);
  872. log.info("hKeys(...) => entryKeys -> {}", entryKeys);
  873. return entryKeys;
  874. }
  875. /**
  876. * 获取(key对应的)hash中的所有entryValue
  877. * <p>
  878. * 注: 若key不存在,则返回的是一个空的List(,而不是返回null)
  879. *
  880. * @param key 定位hash的key
  881. * @return hash中的所有entryValue
  882. */
  883. public static List<Object> hValues(String key) {
  884. log.info("hValues(...) => key -> {}", key);
  885. List<Object> entryValues = redisTemplate.opsForHash().values(key);
  886. log.info("hValues(...) => entryValues -> {}", entryValues);
  887. return entryValues;
  888. }
  889. /**
  890. * 获取(key对应的)hash中的所有entry的数量
  891. * <p>
  892. * 注: 若redis中不存在对应的key,则返回值为0
  893. *
  894. * @param key 定位hash的key
  895. * @return (key对应的)hash中, entry的个数
  896. */
  897. public static long hSize(String key) {
  898. log.info("hSize(...) => key -> {}", key);
  899. Long count = redisTemplate.opsForHash().size(key);
  900. log.info("hSize(...) => count -> {}", count);
  901. if (count == null) {
  902. throw new RedisOpsResultIsNullException();
  903. }
  904. return count;
  905. }
  906. /**
  907. * 根据options匹配到(key对应的)hash中的对应的entryKey,并返回对应的entry集
  908. * <p>
  909. * <p>
  910. * 注: ScanOptions实例的创建方式举例:
  911. * 1、ScanOptions.NONE
  912. * 2、ScanOptions.scanOptions().match("n??e").build()
  913. *
  914. * @param key 定位hash的key
  915. * @param options 匹配entryKey的条件
  916. * 注: ScanOptions.NONE表示全部匹配
  917. * 注: ScanOptions.scanOptions().match(pattern).build()表示按照pattern匹配,
  918. * 其中pattern中可以使用通配符 * ? 等,
  919. * * 表示>=0个字符
  920. * ? 表示有且只有一个字符
  921. * 此处的匹配规则与{@link KeyOps#keys(String)}处的一样
  922. * @return 匹配到的(key对应的)hash中的entry
  923. */
  924. @SneakyThrows
  925. public static Cursor<Entry<Object, Object>> hScan(String key, ScanOptions options) {
  926. log.info("hScan(...) => key -> {},options -> {}", key, mapper.writeValueAsString(options));
  927. Cursor<Entry<Object, Object>> cursor = redisTemplate.opsForHash().scan(key, options);
  928. log.info("hScan(...) => cursor -> {}", mapper.writeValueAsString(cursor));
  929. return cursor;
  930. }
  931. }
  932. /**
  933. * list相关操作
  934. * <p>
  935. * 提示: 列表中的元素,可以重复
  936. * <p>
  937. * 提示: list是有序的
  938. * <p>
  939. * 提示: redis中的list中的索引,可分为两类,这两类都可以用来定位list中元素:
  940. * 类别一: 从left到right,是从0开始依次增大: 0, 1, 2, 3...
  941. * 类别二: 从right到left,是从-1开始依次减小: -1,-2,-3,-4...
  942. */
  943. public static class ListOps {
  944. /**
  945. * 从左端推入元素进列表
  946. * <p>
  947. * 注: 若redis中不存在对应的key,那么会自动创建
  948. *
  949. * @param key 定位list的key
  950. * @param item 要推入list的元素
  951. * @return 推入后, (key对应的)list的size
  952. */
  953. public static long lLeftPush(String key, String item) {
  954. log.info("lLeftPush(...) => key -> {},item -> {}", key, item);
  955. Long size = redisTemplate.opsForList().leftPush(key, item);
  956. log.info("lLeftPush(...) => size -> {}", size);
  957. if (size == null) {
  958. throw new RedisOpsResultIsNullException();
  959. }
  960. return size;
  961. }
  962. /**
  963. * 从左端批量推入元素进列表
  964. * <p>
  965. * 注: 若redis中不存在对应的key,那么会自动创建
  966. * 注: 这一批item中,先push左侧的,后push右侧的
  967. *
  968. * @param key 定位list的key
  969. * @param items 要批量推入list的元素集
  970. * @return 推入后, (key对应的)list的size
  971. */
  972. public static long lLeftPushAll(String key, String... items) {
  973. log.info("lLeftPushAll(...) => key -> {},items -> {}", key, items);
  974. Long size = redisTemplate.opsForList().leftPushAll(key, items);
  975. log.info("lLeftPushAll(...) => size -> {}", size);
  976. if (size == null) {
  977. throw new RedisOpsResultIsNullException();
  978. }
  979. return size;
  980. }
  981. /**
  982. * 从左端批量推入元素进列表
  983. * <p>
  984. * 注: 若redis中不存在对应的key,那么会自动创建
  985. * 注: 这一批item中,那个item先从Collection取出来,就先push哪个
  986. *
  987. * @param key 定位list的key
  988. * @param items 要批量推入list的元素集
  989. * @return 推入后, (key对应的)list的size
  990. */
  991. public static long lLeftPushAll(String key, Collection<String> items) {
  992. log.info("lLeftPushAll(...) => key -> {},items -> {}", key, items);
  993. Long size = redisTemplate.opsForList().leftPushAll(key, items);
  994. log.info("lLeftPushAll(...) => size -> {}", size);
  995. if (size == null) {
  996. throw new RedisOpsResultIsNullException();
  997. }
  998. return size;
  999. }
  1000. /**
  1001. * 如果redis中存在key,则从左端批量推入元素进列表;
  1002. * 否则,不进行任何操作
  1003. *
  1004. * @param key 定位list的key
  1005. * @param item 要推入list的项
  1006. * @return 推入后, (key对应的)list的size
  1007. */
  1008. public static long lLeftPushIfPresent(String key, String item) {
  1009. log.info("lLeftPushIfPresent(...) => key -> {},item -> {}", key, item);
  1010. Long size = redisTemplate.opsForList().leftPushIfPresent(key, item);
  1011. log.info("lLeftPushIfPresent(...) => size -> {}", size);
  1012. if (size == null) {
  1013. throw new RedisOpsResultIsNullException();
  1014. }
  1015. return size;
  1016. }
  1017. /**
  1018. *key对应的list中存在pivot项,那么将item放入第一个pivot项前(即:放在第一个pivot项左边);
  1019. *key对应的list中不存在pivot项,那么不做任何操作,直接返回-1
  1020. * <p>
  1021. * 注: 若redis中不存在对应的key,那么会自动创建
  1022. *
  1023. * @param key 定位list的key
  1024. * @param item 要推入list的元素
  1025. * @return 推入后, (key对应的)list的size
  1026. */
  1027. public static long lLeftPush(String key, String pivot, String item) {
  1028. log.info("lLeftPush(...) => key -> {},pivot -> {},item -> {}", key, pivot, item);
  1029. Long size = redisTemplate.opsForList().leftPush(key, pivot, item);
  1030. log.info("lLeftPush(...) => size -> {}", size);
  1031. if (size == null) {
  1032. throw new RedisOpsResultIsNullException();
  1033. }
  1034. return size;
  1035. }
  1036. /**
  1037. * 与{@link ListOps#lLeftPush(String, String)}类比即可,不过是从list右侧推入元素
  1038. */
  1039. public static long lRightPush(String key, String item) {
  1040. log.info("lRightPush(...) => key -> {},item -> {}", key, item);
  1041. Long size = redisTemplate.opsForList().rightPush(key, item);
  1042. log.info("lRightPush(...) => size -> {}", size);
  1043. if (size == null) {
  1044. throw new RedisOpsResultIsNullException();
  1045. }
  1046. return size;
  1047. }
  1048. /**
  1049. * 与{@link ListOps#lLeftPushAll(String, String...)}类比即可,不过是从list右侧推入元素
  1050. */
  1051. public static long lRightPushAll(String key, String... items) {
  1052. log.info("lRightPushAll(...) => key -> {},items -> {}", key, items);
  1053. Long size = redisTemplate.opsForList().rightPushAll(key, items);
  1054. log.info("lRightPushAll(...) => size -> {}", size);
  1055. if (size == null) {
  1056. throw new RedisOpsResultIsNullException();
  1057. }
  1058. return size;
  1059. }
  1060. /**
  1061. * 与{@link ListOps#lLeftPushAll(String, Collection<String>)}类比即可,不过是从list右侧推入元素
  1062. */
  1063. public static long lRightPushAll(String key, Collection<String> items) {
  1064. log.info("lRightPushAll(...) => key -> {},items -> {}", key, items);
  1065. Long size = redisTemplate.opsForList().rightPushAll(key, items);
  1066. log.info("lRightPushAll(...) => size -> {}", size);
  1067. if (size == null) {
  1068. throw new RedisOpsResultIsNullException();
  1069. }
  1070. return size;
  1071. }
  1072. /**
  1073. * 与{@link ListOps#lLeftPushIfPresent(String, String)}类比即可,不过是从list右侧推入元素
  1074. */
  1075. public static long lRightPushIfPresent(String key, String item) {
  1076. log.info("lRightPushIfPresent(...) => key -> {},item -> {}", key, item);
  1077. Long size = redisTemplate.opsForList().rightPushIfPresent(key, item);
  1078. log.info("lRightPushIfPresent(...) => size -> {}", size);
  1079. if (size == null) {
  1080. throw new RedisOpsResultIsNullException();
  1081. }
  1082. return size;
  1083. }
  1084. /**
  1085. * 与{@link ListOps#lLeftPush(String, String, String)}类比即可,不过是从list右侧推入元素
  1086. */
  1087. public static long lRightPush(String key, String pivot, String item) {
  1088. log.info("lLeftPush(...) => key -> {},pivot -> {},item -> {}", key, pivot, item);
  1089. Long size = redisTemplate.opsForList().rightPush(key, pivot, item);
  1090. log.info("lLeftPush(...) => size -> {}", size);
  1091. if (size == null) {
  1092. throw new RedisOpsResultIsNullException();
  1093. }
  1094. return size;
  1095. }
  1096. /**
  1097. * 【非阻塞队列】 从左侧移出(key对应的)list中的第一个元素,并将该元素返回
  1098. * <p>
  1099. * 注: 此方法是非阻塞的,即: 若(key对应的)list中的所有元素都被pop移出了,此时,再进行pop的话,会立即返回null
  1100. * 注: 此方法是非阻塞的,即: 若redis中不存在对应的key,那么会立即返回null
  1101. * 注: 若将(key对应的)list中的所有元素都pop完了,那么该key会被删除
  1102. *
  1103. * @param key 定位list的key
  1104. * @return 移出的那个元素
  1105. */
  1106. public static String lLeftPop(String key) {
  1107. log.info("lLeftPop(...) => key -> {}", key);
  1108. String item = redisTemplate.opsForList().leftPop(key);
  1109. log.info("lLeftPop(...) => item -> {}", item);
  1110. return item;
  1111. }
  1112. /**
  1113. * 【阻塞队列】 从左侧移出(key对应的)list中的第一个元素,并将该元素返回
  1114. * <p>
  1115. * 注: 此方法是阻塞的,即: 若(key对应的)list中的所有元素都被pop移出了,此时,再进行pop的话,
  1116. * 会阻塞timeout这么久,然后返回null
  1117. * 注: 此方法是阻塞的,即: 若redis中不存在对应的key,那么会阻塞timeout这么久,然后返回null
  1118. * 注: 若将(key对应的)list中的所有元素都pop完了,那么该key会被删除
  1119. * <p>
  1120. * 提示: 若阻塞过程中,目标key-list出现了,且里面有item了,那么会立马停止阻塞,进行元素移出并返回
  1121. *
  1122. * @param key 定位list的key
  1123. * @param timeout 超时时间
  1124. * @param unit timeout的单位
  1125. * @return 移出的那个元素
  1126. */
  1127. public static String lLeftPop(String key, long timeout, TimeUnit unit) {
  1128. log.info("lLeftPop(...) => key -> {},timeout -> {},unit -> {}", key, timeout, unit);
  1129. String item = redisTemplate.opsForList().leftPop(key, timeout, unit);
  1130. log.info("lLeftPop(...) => item -> {}", item);
  1131. return item;
  1132. }
  1133. /**
  1134. * 与{@link ListOps#lLeftPop(String)}类比即可,不过是从list右侧移出元素
  1135. */
  1136. public static String lRightPop(String key) {
  1137. log.info("lRightPop(...) => key -> {}", key);
  1138. String item = redisTemplate.opsForList().rightPop(key);
  1139. log.info("lRightPop(...) => item -> {}", item);
  1140. return item;
  1141. }
  1142. /**
  1143. * 与{@link ListOps#lLeftPop(String, long, TimeUnit)}类比即可,不过是从list右侧移出元素
  1144. */
  1145. public static String lRightPop(String key, long timeout, TimeUnit unit) {
  1146. log.info("lRightPop(...) => key -> {},timeout -> {},unit -> {}", key, timeout, unit);
  1147. String item = redisTemplate.opsForList().rightPop(key, timeout, unit);
  1148. log.info("lRightPop(...) => item -> {}", item);
  1149. return item;
  1150. }
  1151. /**
  1152. * 【非阻塞队列】 从sourceKey对应的sourceList右侧移出一个item,并将这个item推
  1153. * 入(destinationKey对应的)destinationList的左侧
  1154. * <p>
  1155. * 注: 若sourceKey对应的list中没有item了,则立马认为(从sourceKey对应的list中pop出来的)item为null,
  1156. * null并不会往destinationKey对应的list中push
  1157. * 追注: 此时,此方法的返回值是null
  1158. * <p>
  1159. * 注: 若将(sourceKey对应的)list中的所有元素都pop完了,那么该sourceKey会被删除
  1160. *
  1161. * @param sourceKey 定位sourceList的key
  1162. * @param destinationKey 定位destinationList的key
  1163. * @return 移动的这个元素
  1164. */
  1165. public static String lRightPopAndLeftPush(String sourceKey, String destinationKey) {
  1166. log.info("lRightPopAndLeftPush(...) => sourceKey -> {},destinationKey -> {}",
  1167. sourceKey, destinationKey);
  1168. String item = redisTemplate.opsForList().rightPopAndLeftPush(sourceKey, destinationKey);
  1169. log.info("lRightPopAndLeftPush(...) => item -> {}", item);
  1170. return item;
  1171. }
  1172. /**
  1173. * 【阻塞队列】 从sourceKey对应的sourceList右侧移出一个item,并将这个item推
  1174. * 入(destinationKey对应的)destinationList的左侧
  1175. * <p>
  1176. * 注: 若sourceKey对应的list中没有item了,则阻塞等待,直到能从sourceList中移出一个非null的item(或等待时长超时);
  1177. * case1: 等到了一个非null的item,那么继续下面的push操作,并返回这个item
  1178. * case2: 超时了,还没等到非null的item,那么pop出的结果就未null,此时并不会往destinationList进行push
  1179. * 此时,此方法的返回值是null
  1180. * <p>
  1181. * 注: 若将(sourceKey对应的)list中的所有元素都pop完了,那么该sourceKey会被删除
  1182. *
  1183. * @param sourceKey 定位sourceList的key
  1184. * @param destinationKey 定位destinationList的key
  1185. * @param timeout 超时时间
  1186. * @param unit timeout的单位
  1187. * @return 移动的这个元素
  1188. */
  1189. public static String lRightPopAndLeftPush(String sourceKey, String destinationKey, long timeout,
  1190. TimeUnit unit) {
  1191. log.info("lRightPopAndLeftPush(...) => sourceKey -> {},destinationKey -> {},timeout -> {},"
  1192. + " unit -> {}", sourceKey, destinationKey, timeout, unit);
  1193. String item = redisTemplate.opsForList().rightPopAndLeftPush(sourceKey, destinationKey, timeout, unit);
  1194. log.info("lRightPopAndLeftPush(...) => item -> {}", item);
  1195. return item;
  1196. }
  1197. /**
  1198. * 设置(key对应的)list中对应索引位置index处的元素为item
  1199. * <p>
  1200. * 注: 若key不存在,则会抛出org.springframework.data.redis.RedisSystemException
  1201. * 注: 若索引越界,也会抛出org.springframework.data.redis.RedisSystemException
  1202. *
  1203. * @param key 定位list的key
  1204. * @param index 定位list中的元素的索引
  1205. * @param item 要替换成的值
  1206. */
  1207. public static void lSet(String key, long index, String item) {
  1208. log.info("lSet(...) => key -> {},index -> {},item -> {}", key, index, item);
  1209. redisTemplate.opsForList().set(key, index, item);
  1210. }
  1211. /**
  1212. * 通过索引index,获取(key对应的)list中的元素
  1213. * <p>
  1214. * 注: 若key不存在 或 index超出(key对应的)list的索引范围,那么返回null
  1215. *
  1216. * @param key 定位list的key
  1217. * @param index 定位list中的item的索引
  1218. * @return list中索引index对应的item
  1219. */
  1220. public static String lIndex(String key, long index) {
  1221. log.info("lIndex(...) => key -> {},index -> {}", key, index);
  1222. String item = redisTemplate.opsForList().index(key, index);
  1223. log.info("lIndex(...) => item -> {}", item);
  1224. return item;
  1225. }
  1226. /**
  1227. * 获取(key对应的)list中索引在[start,end]之间的item集
  1228. * <p>
  1229. * 注: 含start、含end
  1230. * 注: 当key不存在时,获取到的是空的集合
  1231. * 注: 当获取的范围比list的范围还要大时,获取到的是这两个范围的交集
  1232. * <p>
  1233. * 提示: 可通过RedisUtil.ListOps.lRange(key,0,-1)来获取到该key对应的整个list
  1234. *
  1235. * @param key 定位list的key
  1236. * @param start 起始元素的index
  1237. * @param end 结尾元素的index
  1238. * @return 对应的元素集合
  1239. */
  1240. public static List<String> lRange(String key, long start, long end) {
  1241. log.info("lRange(...) => key -> {},start -> {},end -> {}", key, start, end);
  1242. List<String> result = redisTemplate.opsForList().range(key, start, end);
  1243. log.info("lRange(...) => result -> {}", result);
  1244. return result;
  1245. }
  1246. /**
  1247. * 获取(key对应的)list
  1248. *
  1249. * @param key 定位list的key
  1250. * @return (key对应的)list
  1251. * @see ListOps#lRange(String, long, long)
  1252. */
  1253. public static List<String> lWholeList(String key) {
  1254. log.info("lWholeList(...) => key -> {}", key);
  1255. List<String> result = redisTemplate.opsForList().range(key, 0, -1);
  1256. log.info("lWholeList(...) => result -> {}", result);
  1257. return result;
  1258. }
  1259. /**
  1260. * 获取(key对应的)list的size
  1261. * <p>
  1262. * 注: 当key不存在时,获取到的size0.
  1263. *
  1264. * @param key 定位list的key
  1265. * @return list的size
  1266. */
  1267. public static long lSize(String key) {
  1268. log.info("lSize(...) => key -> {}", key);
  1269. Long size = redisTemplate.opsForList().size(key);
  1270. log.info("lSize(...) => size -> {}", size);
  1271. if (size == null) {
  1272. throw new RedisOpsResultIsNullException();
  1273. }
  1274. return size;
  1275. }
  1276. /**
  1277. * 删除(key对应的)list中,前expectCount个值等于item的项
  1278. * <p>
  1279. * 注: 若expectCount == 0,则表示删除list中所有的值等于item的项.
  1280. * 注: 若expectCount > 0, 则表示删除从左往右进行
  1281. * 注: 若expectCount < 0, 则表示删除从右往左进行
  1282. * <p>
  1283. * 注: 若list中,值等于item的项的个数少于expectCount时,那么会删除list中所有的值等于item的项
  1284. * 注: 当key不存在时,返回0
  1285. * 注: 若lRemove后,将(key对应的)list中没有任何元素了,那么该key会被删除
  1286. *
  1287. * @param key 定位list的key
  1288. * @param expectCount 要删除的item的个数
  1289. * @param item 要删除的item
  1290. * @return 实际删除了的item的个数
  1291. */
  1292. public static long lRemove(String key, long expectCount, String item) {
  1293. log.info("lRemove(...) => key -> {},expectCount -> {},item -> {}", key, expectCount, item);
  1294. Long actualCount = redisTemplate.opsForList().remove(key, expectCount, item);
  1295. log.info("lRemove(...) => actualCount -> {}", actualCount);
  1296. if (actualCount == null) {
  1297. throw new RedisOpsResultIsNullException();
  1298. }
  1299. return actualCount;
  1300. }
  1301. /**
  1302. * 裁剪(即: 对list中的元素取交集)
  1303. * <p>
  1304. * 举例说明: list中的元素索引范围是[0,8],而这个方法传入的[start,end]为 [3,10],
  1305. * 那么裁剪就是对[0,8]和[3,10]进行取交集,得到[3,8],那么裁剪后
  1306. * 的list中,只剩下(原来裁剪前)索引在[3,8]之间的元素了
  1307. * <p>
  1308. * 注: 若裁剪后的(key对应的)list就是空的,那么该key会被删除
  1309. *
  1310. * @param key 定位list的key
  1311. * @param start 要删除的item集的起始项的索引
  1312. * @param end 要删除的item集的结尾项的索引
  1313. */
  1314. public static void lTrim(String key, long start, long end) {
  1315. log.info("lTrim(...) => key -> {},start -> {},end -> {}", key, start, end);
  1316. redisTemplate.opsForList().trim(key, start, end);
  1317. }
  1318. }
  1319. /**
  1320. * set相关操作
  1321. * <p>
  1322. * 提示: set中的元素,不可以重复
  1323. * 提示: set是无序的
  1324. * 提示: redis中String的数据结构可参考resources/data-structure/Set(集合)的数据结构(示例一).png
  1325. * redis中String的数据结构可参考resources/data-structure/Set(集合)的数据结构(示例二).png
  1326. */
  1327. public static class SetOps {
  1328. /**
  1329. * 向(key对应的)set中添加items
  1330. * <p>
  1331. * 注: 若key不存在,则会自动创建
  1332. * 注: set中的元素会去重
  1333. *
  1334. * @param key 定位setkey
  1335. * @param items 要向(key对应的)set中添加的items
  1336. * @return 此次添加操作, 添加到set中的元素的个数
  1337. */
  1338. public static long sAdd(String key, String... items) {
  1339. log.info("sAdd(...) => key -> {},items -> {}", key, items);
  1340. Long count = redisTemplate.opsForSet().add(key, items);
  1341. log.info("sAdd(...) => count -> {}", count);
  1342. if (count == null) {
  1343. throw new RedisOpsResultIsNullException();
  1344. }
  1345. return count;
  1346. }
  1347. /**
  1348. * 从(key对应的)set中删除items
  1349. * <p>
  1350. * 注: 若key不存在,则返回0
  1351. * 注: 若已经将(key对应的)set中的项删除完了,那么对应的key也会被删除
  1352. *
  1353. * @param key 定位setkey
  1354. * @param items 要移除的items
  1355. * @return 实际删除了的个数
  1356. */
  1357. public static long sRemove(String key, Object... items) {
  1358. log.info("sRemove(...) => key -> {},items -> {}", key, items);
  1359. Long count = redisTemplate.opsForSet().remove(key, items);
  1360. log.info("sRemove(...) => count -> {}", count);
  1361. if (count == null) {
  1362. throw new RedisOpsResultIsNullException();
  1363. }
  1364. return count;
  1365. }
  1366. /**
  1367. * 从(key对应的)set中随机移出一个item,并返回这个item
  1368. * <p>
  1369. * 注: 因为set是无序的,所以移出的这个item,是随机的; 并且,哪怕
  1370. * 是数据一样的set,多次测试移出操作,移除的元素也是随机的
  1371. * <p>
  1372. * 注: 若已经将(key对应的)set中的项pop完了,那么对应的key会被删除
  1373. *
  1374. * @param key 定位setkey
  1375. * @return 移出的项
  1376. */
  1377. public static String sPop(String key) {
  1378. log.info("sPop(...) => key -> {}", key);
  1379. String popItem = redisTemplate.opsForSet().pop(key);
  1380. log.info("sPop(...) => popItem -> {}", popItem);
  1381. return popItem;
  1382. }
  1383. /**
  1384. * 将(sourceKey对应的)sourceSet中的元素item,移动到(destinationKey对应的)destinationSet中
  1385. * <p>
  1386. * 注: 当sourceKey不存在时,返回false
  1387. * 注: 当item不存在时,返回false
  1388. * 注: 若destinationKey不存在,那么在移动时会自动创建
  1389. * 注: 若已经将(sourceKey对应的)set中的项move出去完了,那么对应的sourceKey会被删除
  1390. *
  1391. * @param sourceKey 定位sourceSet的key
  1392. * @param item 要移动的项目
  1393. * @param destinationKey 定位destinationSet的key
  1394. * @return 移动成功与否
  1395. */
  1396. public static boolean sMove(String sourceKey, String item, String destinationKey) {
  1397. Boolean result = redisTemplate.opsForSet().move(sourceKey, item, destinationKey);
  1398. log.info("sMove(...) => sourceKey -> {},destinationKey -> {},item -> {}",
  1399. sourceKey, destinationKey, item);
  1400. log.info("sMove(...) => result -> {}", result);
  1401. if (result == null) {
  1402. throw new RedisOpsResultIsNullException();
  1403. }
  1404. return result;
  1405. }
  1406. /**
  1407. * 获取(key对应的)set中的元素个数
  1408. * <p>
  1409. * 注: 若key不存在,则返回0
  1410. *
  1411. * @param key 定位setkey
  1412. * @return (key对应的)set中的元素个数
  1413. */
  1414. public static long sSize(String key) {
  1415. log.info("sSize(...) => key -> {}", key);
  1416. Long size = redisTemplate.opsForSet().size(key);
  1417. log.info("sSize(...) => size -> {}", size);
  1418. if (size == null) {
  1419. throw new RedisOpsResultIsNullException();
  1420. }
  1421. return size;
  1422. }
  1423. /**
  1424. * 判断(key对应的)set中是否含有item
  1425. * <p>
  1426. * 注: 若key不存在,则返回false
  1427. *
  1428. * @param key 定位setkey
  1429. * @param item 被查找的项
  1430. * @return (key对应的)set中是否含有item
  1431. */
  1432. public static boolean sIsMember(String key, Object item) {
  1433. log.info("sSize(...) => key -> {},size -> {}", key, item);
  1434. Boolean result = redisTemplate.opsForSet().isMember(key, item);
  1435. log.info("sSize(...) => result -> {}", result);
  1436. if (result == null) {
  1437. throw new RedisOpsResultIsNullException();
  1438. }
  1439. return result;
  1440. }
  1441. /**
  1442. * 获取两个(key对应的)Set的交集
  1443. * <p>
  1444. * 注: 若不存在任何交集,那么返回空的集合(,而不是null)
  1445. * 注: 若其中一个key不存在(或两个key都不存在),那么返回空的集合(,而不是null)
  1446. *
  1447. * @param key 定位其中一个set的键
  1448. * @param otherKey 定位其中另一个set的键
  1449. * @return item交集
  1450. */
  1451. public static Set<String> sIntersect(String key, String otherKey) {
  1452. log.info("sIntersect(...) => key -> {},otherKey -> {}", key, otherKey);
  1453. Set<String> intersectResult = redisTemplate.opsForSet().intersect(key, otherKey);
  1454. log.info("sIntersect(...) => intersectResult -> {}", intersectResult);
  1455. return intersectResult;
  1456. }
  1457. /**
  1458. * 获取多个(key对应的)Set的交集
  1459. * <p>
  1460. * 注: 若不存在任何交集,那么返回空的集合(,而不是null)
  1461. * 注: 若>=1key不存在,那么返回空的集合(,而不是null)
  1462. *
  1463. * @param key 定位其中一个set的键
  1464. * @param otherKeys 定位其它set的键集
  1465. * @return item交集
  1466. */
  1467. public static Set<String> sIntersect(String key, Collection<String> otherKeys) {
  1468. log.info("sIntersect(...) => key -> {},otherKeys -> {}", key, otherKeys);
  1469. Set<String> intersectResult = redisTemplate.opsForSet().intersect(key, otherKeys);
  1470. log.info("sIntersect(...) => intersectResult -> {}", intersectResult);
  1471. return intersectResult;
  1472. }
  1473. /**
  1474. * 获取两个(key对应的)Set的交集,并将结果add到storeKey对应的Set
  1475. * <p>
  1476. * case1: 交集不为空,storeKey不存在,则 会创建对应的storeKey,并将交集添加到(storeKey对应的)set
  1477. * case2: 交集不为空,storeKey已存在,则 会清除原(storeKey对应的)set中所有的项,然后将交集添加到(storeKey对应的)set
  1478. * case3: 交集为空,则不进行下面的操作,直接返回0
  1479. * <p>
  1480. * 注: 求交集的部分,详见{@link SetOps#sIntersect(String, String)}
  1481. *
  1482. * @param key 定位其中一个set的键
  1483. * @param otherKey 定位其中另一个set的键
  1484. * @param storeKey 定位(要把交集添加到哪个)setkey
  1485. * @return add到(storeKey对应的)Set后, 该set对应的size
  1486. */
  1487. public static long sIntersectAndStore(String key, String otherKey, String storeKey) {
  1488. log.info("sIntersectAndStore(...) => key -> {},otherKey -> {},storeKey -> {}",
  1489. key, otherKey, storeKey);
  1490. Long size = redisTemplate.opsForSet().intersectAndStore(key, otherKey, storeKey);
  1491. log.info("sIntersectAndStore(...) => size -> {}", size);
  1492. if (size == null) {
  1493. throw new RedisOpsResultIsNullException();
  1494. }
  1495. return size;
  1496. }
  1497. /**
  1498. * 获取多个(key对应的)Set的交集,并将结果add到storeKey对应的Set
  1499. * <p>
  1500. * case1: 交集不为空,storeKey不存在,则 会创建对应的storeKey,并将交集添加到(storeKey对应的)set
  1501. * case2: 交集不为空,storeKey已存在,则 会清除原(storeKey对应的)set中所有的项,然后将交集添加到(storeKey对应的)set
  1502. * case3: 交集为空,则不进行下面的操作,直接返回0
  1503. * <p>
  1504. * 注: 求交集的部分,详见{@link SetOps#sIntersect(String, Collection)}
  1505. */
  1506. public static long sIntersectAndStore(String key, Collection<String> otherKeys, String storeKey) {
  1507. log.info("sIntersectAndStore(...) => key -> {},otherKeys -> {},storeKey -> {}", key, otherKeys, storeKey);
  1508. Long size = redisTemplate.opsForSet().intersectAndStore(key, otherKeys, storeKey);
  1509. log.info("sIntersectAndStore(...) => size -> {}", size);
  1510. if (size == null) {
  1511. throw new RedisOpsResultIsNullException();
  1512. }
  1513. return size;
  1514. }
  1515. /**
  1516. * 获取两个(key对应的)Set的并集
  1517. * <p>
  1518. * 注: 并集中的元素也是唯一的,这是Set保证的
  1519. *
  1520. * @param key 定位其中一个set的键
  1521. * @param otherKey 定位其中另一个set的键
  1522. * @return item并集
  1523. */
  1524. public static Set<String> sUnion(String key, String otherKey) {
  1525. log.info("sUnion(...) => key -> {},otherKey -> {}", key, otherKey);
  1526. Set<String> unionResult = redisTemplate.opsForSet().union(key, otherKey);
  1527. log.info("sUnion(...) => unionResult -> {}", unionResult);
  1528. return unionResult;
  1529. }
  1530. /**
  1531. * 获取两个(key对应的)Set的并集
  1532. * <p>
  1533. * 注: 并集中的元素也是唯一的,这是Set保证的
  1534. *
  1535. * @param key 定位其中一个set的键
  1536. * @param otherKeys 定位其它set的键集
  1537. * @return item并集
  1538. */
  1539. public static Set<String> sUnion(String key, Collection<String> otherKeys) {
  1540. log.info("sUnion(...) => key -> {},otherKeys -> {}", key, otherKeys);
  1541. Set<String> unionResult = redisTemplate.opsForSet().union(key, otherKeys);
  1542. log.info("sUnion(...) => unionResult -> {}", unionResult);
  1543. return unionResult;
  1544. }
  1545. /**
  1546. * 获取两个(key对应的)Set的并集,并将结果add到storeKey对应的Set
  1547. * <p>
  1548. * case1: 并集不为空,storeKey不存在,则 会创建对应的storeKey,并将并集添加到(storeKey对应的)set
  1549. * case2: 并集不为空,storeKey已存在,则 会清除原(storeKey对应的)set中所有的项,然后将并集添加到(storeKey对应的)set
  1550. * case3: 并集为空,则不进行下面的操作,直接返回0
  1551. * <p>
  1552. * 注: 求并集的部分,详见{@link SetOps#sUnion(String, String)}
  1553. *
  1554. * @param key 定位其中一个set的键
  1555. * @param otherKey 定位其中另一个set的键
  1556. * @param storeKey 定位(要把并集添加到哪个)setkey
  1557. * @return add到(storeKey对应的)Set后, 该set对应的size
  1558. */
  1559. public static long sUnionAndStore(String key, String otherKey, String storeKey) {
  1560. log.info("sUnionAndStore(...) => key -> {},otherKey -> {},storeKey -> {}",
  1561. key, otherKey, storeKey);
  1562. Long size = redisTemplate.opsForSet().unionAndStore(key, otherKey, storeKey);
  1563. log.info("sUnionAndStore(...) => size -> {}", size);
  1564. if (size == null) {
  1565. throw new RedisOpsResultIsNullException();
  1566. }
  1567. return size;
  1568. }
  1569. /**
  1570. * 获取两个(key对应的)Set的并集,并将结果add到storeKey对应的Set
  1571. * <p>
  1572. * case1: 并集不为空,storeKey不存在,则 会创建对应的storeKey,并将并集添加到(storeKey对应的)set
  1573. * case2: 并集不为空,storeKey已存在,则 会清除原(storeKey对应的)set中所有的项,然后将并集添加到(storeKey对应的)set
  1574. * case3: 并集为空,则不进行下面的操作,直接返回0
  1575. * <p>
  1576. * 注: 求并集的部分,详见{@link SetOps#sUnion(String, Collection)}
  1577. *
  1578. * @param key 定位其中一个set的键
  1579. * @param otherKeys 定位其它set的键集
  1580. * @param storeKey 定位(要把并集添加到哪个)setkey
  1581. * @return add到(storeKey对应的)Set后, 该set对应的size
  1582. */
  1583. public static long sUnionAndStore(String key, Collection<String> otherKeys, String storeKey) {
  1584. log.info("sUnionAndStore(...) => key -> {},otherKeys -> {},storeKey -> {}",
  1585. key, otherKeys, storeKey);
  1586. Long size = redisTemplate.opsForSet().unionAndStore(key, otherKeys, storeKey);
  1587. log.info("sUnionAndStore(...) => size -> {}", size);
  1588. if (size == null) {
  1589. throw new RedisOpsResultIsNullException();
  1590. }
  1591. return size;
  1592. }
  1593. /**
  1594. * 获取 (key对应的)Set 减去 (otherKey对应的)Set 的差集
  1595. * <p>
  1596. * 注: 如果被减数key不存在,那么结果为空的集合(,而不是null)
  1597. * 注: 如果被减数key存在,但减数key不存在,那么结果即为(被减数key对应的)Set
  1598. *
  1599. * @param key 定位"被减数set"的键
  1600. * @param otherKey 定位"减数set"的键
  1601. * @return item差集
  1602. */
  1603. public static Set<String> sDifference(String key, String otherKey) {
  1604. log.info("sDifference(...) => key -> {},otherKey -> {}",
  1605. key, otherKey);
  1606. Set<String> differenceResult = redisTemplate.opsForSet().difference(key, otherKey);
  1607. log.info("sDifference(...) => differenceResult -> {}", differenceResult);
  1608. return differenceResult;
  1609. }
  1610. /**
  1611. * 获取 (key对应的)Set 减去 (otherKeys对应的)Sets 的差集
  1612. * <p>
  1613. * 注: 如果被减数key不存在,那么结果为空的集合(,而不是null)
  1614. * 注: 如果被减数key存在,但减数key不存在,那么结果即为(被减数key对应的)Set
  1615. * <p>
  1616. * 提示: 当有多个减数时,被减数先减去哪一个减数,后减去哪一个减数,是无所谓的,是不影响最终结果的
  1617. *
  1618. * @param key 定位"被减数set"的键
  1619. * @param otherKeys 定位"减数集sets"的键集
  1620. * @return item差集
  1621. */
  1622. public static Set<String> sDifference(String key, Collection<String> otherKeys) {
  1623. log.info("sDifference(...) => key -> {},otherKeys -> {}", key, otherKeys);
  1624. Set<String> differenceResult = redisTemplate.opsForSet().difference(key, otherKeys);
  1625. log.info("sDifference(...) => differenceResult -> {}", differenceResult);
  1626. return differenceResult;
  1627. }
  1628. /**
  1629. * 获取 (key对应的)Set 减去 (otherKey对应的)Set 的差集,并将结果add到storeKey对应的Set
  1630. * <p>
  1631. * case1: 差集不为空,storeKey不存在,则 会创建对应的storeKey,并将差集添加到(storeKey对应的)set
  1632. * case2: 差集不为空,storeKey已存在,则 会清除原(storeKey对应的)set中所有的项,然后将差集添加到(storeKey对应的)set
  1633. * case3: 差集为空,则不进行下面的操作,直接返回0
  1634. * <p>
  1635. * 注: 求并集的部分,详见{@link SetOps#sDifference(String, String)}
  1636. *
  1637. * @param key 定位"被减数set"的键
  1638. * @param otherKey 定位"减数set"的键
  1639. * @param storeKey 定位(要把差集添加到哪个)setkey
  1640. * @return add到(storeKey对应的)Set后, 该set对应的size
  1641. */
  1642. public static long sDifferenceAndStore(String key, String otherKey, String storeKey) {
  1643. log.info("sDifferenceAndStore(...) => key -> {},otherKey -> {},storeKey -> {}",
  1644. key, otherKey, storeKey);
  1645. Long size = redisTemplate.opsForSet().differenceAndStore(key, otherKey, storeKey);
  1646. log.info("sDifferenceAndStore(...) => size -> {}", size);
  1647. if (size == null) {
  1648. throw new RedisOpsResultIsNullException();
  1649. }
  1650. return size;
  1651. }
  1652. /**
  1653. * 获取 (key对应的)Set 减去 (otherKey对应的)Set 的差集,并将结果add到storeKey对应的Set
  1654. * <p>
  1655. * case1: 差集不为空,storeKey不存在,则 会创建对应的storeKey,并将差集添加到(storeKey对应的)set
  1656. * case2: 差集不为空,storeKey已存在,则 会清除原(storeKey对应的)set中所有的项,然后将差集添加到(storeKey对应的)set
  1657. * case3: 差集为空,则不进行下面的操作,直接返回0
  1658. * <p>
  1659. * 注: 求并集的部分,详见{@link SetOps#sDifference(String, String)}
  1660. *
  1661. * @param key 定位"被减数set"的键
  1662. * @param otherKeys 定位"减数集sets"的键集
  1663. * @param storeKey 定位(要把差集添加到哪个)setkey
  1664. * @return add到(storeKey对应的)Set后, 该set对应的size
  1665. */
  1666. public static long sDifferenceAndStore(String key, Collection<String> otherKeys, String storeKey) {
  1667. log.info("sDifferenceAndStore(...) => key -> {},otherKeys -> {},storeKey -> {}",
  1668. key, otherKeys, storeKey);
  1669. Long size = redisTemplate.opsForSet().differenceAndStore(key, otherKeys, storeKey);
  1670. log.info("sDifferenceAndStore(...) => size -> {}", size);
  1671. if (size == null) {
  1672. throw new RedisOpsResultIsNullException();
  1673. }
  1674. return size;
  1675. }
  1676. /**
  1677. * 获取key对应的set
  1678. * <p>
  1679. * 注: 若key不存在,则返回的是空的set(,而不是null)
  1680. *
  1681. * @param key 定位setkey
  1682. * @return (key对应的)set
  1683. */
  1684. public static Set<String> sMembers(String key) {
  1685. log.info("sMembers(...) => key -> {}", key);
  1686. Set<String> members = redisTemplate.opsForSet().members(key);
  1687. log.info("sMembers(...) => members -> {}", members);
  1688. return members;
  1689. }
  1690. /**
  1691. *key对应的set中随机获取一项
  1692. *
  1693. * @param key 定位setkey
  1694. * @return 随机获取到的项
  1695. */
  1696. public static String sRandomMember(String key) {
  1697. log.info("sRandomMember(...) => key -> {}", key);
  1698. String randomItem = redisTemplate.opsForSet().randomMember(key);
  1699. log.info("sRandomMember(...) => randomItem -> {}", randomItem);
  1700. return randomItem;
  1701. }
  1702. /**
  1703. *key对应的set中获取count次随机项(,set中的同一个项可能被多次获取)
  1704. * <p>
  1705. * 注: count可大于setsize
  1706. * 注: 取出来的结果里可能存在相同的值
  1707. *
  1708. * @param key 定位setkey
  1709. * @param count 要取多少项
  1710. * @return 随机获取到的项集
  1711. */
  1712. public static List<String> sRandomMembers(String key, long count) {
  1713. log.info("sRandomMembers(...) => key -> {},count -> {}", key, count);
  1714. List<String> randomItems = redisTemplate.opsForSet().randomMembers(key, count);
  1715. log.info("sRandomMembers(...) => randomItems -> {}", randomItems);
  1716. return randomItems;
  1717. }
  1718. /**
  1719. *key对应的set中随机获取count个项
  1720. * <p>
  1721. * 注: 若count >= setsize,那么返回的即为这个key对应的set
  1722. * 注: 取出来的结果里没有重复的项
  1723. *
  1724. * @param key 定位setkey
  1725. * @param count 要取多少项
  1726. * @return 随机获取到的项集
  1727. */
  1728. public static Set<String> sDistinctRandomMembers(String key, long count) {
  1729. log.info("sDistinctRandomMembers(...) => key -> {},count -> {}", key, count);
  1730. Set<String> distinctRandomItems = redisTemplate.opsForSet().distinctRandomMembers(key, count);
  1731. log.info("sDistinctRandomMembers(...) => distinctRandomItems -> {}", distinctRandomItems);
  1732. return distinctRandomItems;
  1733. }
  1734. /**
  1735. * 根据options匹配到(key对应的)set中的对应的item,并返回对应的item集
  1736. * <p>
  1737. * <p>
  1738. * 注: ScanOptions实例的创建方式举例:
  1739. * 1、ScanOptions.NONE
  1740. * 2、ScanOptions.scanOptions().match("n??e").build()
  1741. *
  1742. * @param key 定位setkey
  1743. * @param options 匹配set中的item的条件
  1744. * 注: ScanOptions.NONE表示全部匹配
  1745. * 注: ScanOptions.scanOptions().match(pattern).build()表示按照pattern匹配,
  1746. * 其中pattern中可以使用通配符 * ? 等,
  1747. * * 表示>=0个字符
  1748. * ? 表示有且只有一个字符
  1749. * 此处的匹配规则与{@link KeyOps#keys(String)}处的一样
  1750. * @return 匹配到的(key对应的)set中的项
  1751. */
  1752. @SneakyThrows
  1753. public static Cursor<String> sScan(String key, ScanOptions options) {
  1754. log.info("sScan(...) => key -> {},options -> {}", key, mapper.writeValueAsString(options));
  1755. Cursor<String> cursor = redisTemplate.opsForSet().scan(key, options);
  1756. log.info("sScan(...) => cursor -> {}", mapper.writeValueAsString(cursor));
  1757. return cursor;
  1758. }
  1759. }
  1760. /**
  1761. * ZSet相关操作
  1762. * <p>
  1763. * 特别说明: ZSet是有序的,
  1764. * 不仅体现在: redis中的存储上有序
  1765. * 还体现在: 此工具类ZSetOps中返回值类型为Set<?>的方法,实际返回类型是LinkedHashSet<?>
  1766. * <p>
  1767. * 提示: redis中的ZSet,一定程度等于redis中的Set + redis中的Hash的结合体
  1768. * 提示: redis中String的数据结构可参考resources/data-structure/ZSet(有序集合)的数据结构(示例一).png
  1769. * redis中String的数据结构可参考resources/data-structure/ZSet(有序集合)的数据结构(示例二).png
  1770. * 提示: ZSet中的entryKey即为成员项,entryValue即为这个成员项的分值,ZSet根据成员的分值,来堆成员进行排序
  1771. */
  1772. public static class ZSetOps {
  1773. /**
  1774. * 向(key对应的)zset中添加(item,score)
  1775. * <p>
  1776. * 注: item为entryKey成员项,score为entryValue分数值
  1777. * <p>
  1778. * 注: 若(key对应的)zset中已存在(与此次要添加的项)相同的item项,那么此次添加操作会失败,返回false
  1779. * 但是!!! zset中原item的score会被更新为此次add的相同item项的score
  1780. * 所以,也可以通过zAdd达到更新item对应score的目的
  1781. * <p>
  1782. * 注: score可为正、可为负、可为0; 总之,double范围内都可以
  1783. * <p>
  1784. * 注: 若score的值一样,则按照item排序
  1785. *
  1786. * @param key 定位setkey
  1787. * @param item 要往(key对应的)zset中添加的成员项
  1788. * @param score item的分值
  1789. * @return 是否添加成功
  1790. */
  1791. public static boolean zAdd(String key, String item, double score) {
  1792. log.info("zAdd(...) => key -> {},item -> {},score -> {}", key, item, score);
  1793. Boolean result = redisTemplate.opsForZSet().add(key, item, score);
  1794. log.info("zAdd(...) => result -> {}", result);
  1795. if (result == null) {
  1796. throw new RedisOpsResultIsNullException();
  1797. }
  1798. return result;
  1799. }
  1800. /**
  1801. * 批量添加entry<item,score>
  1802. * <p>
  1803. * 注: 若entry<item,score>集中存在item相同的项(,score不一样),那么redis在执行真正的批量add操作前,会
  1804. * 将其中一个item过滤掉
  1805. * 注: 同样的,若(key对应的)zset中已存在(与此次要添加的项)相同的item项,那么此次批量添加操作中,
  1806. * 对该item项的添加会失败,会失败,成功计数器不会加1;但是!!! zset中原item的score会被更新为此
  1807. *add的相同item项的score所以,也可以通过zAdd达到更新item对应score的目的
  1808. *
  1809. * @param key 定位setkey
  1810. * @param entries 要添加的entry<item,score>
  1811. * @return 本次添加进(key对应的)zset中的entry的个数
  1812. */
  1813. @SneakyThrows
  1814. public static long zAdd(String key, Set<TypedTuple<String>> entries) {
  1815. log.info("zAdd(...) => key -> {},entries -> {}", key, mapper.writeValueAsString(entries));
  1816. Long count = redisTemplate.opsForZSet().add(key, entries);
  1817. log.info("zAdd(...) => count -> {}", count);
  1818. if (count == null) {
  1819. throw new RedisOpsResultIsNullException();
  1820. }
  1821. return count;
  1822. }
  1823. /**
  1824. * 从(key对应的)zset中移除项
  1825. * <p>
  1826. * 注:若key不存在,则返回0
  1827. *
  1828. * @param key 定位setkey
  1829. * @param items 要移除的项集
  1830. * @return 实际移除了的项的个数
  1831. */
  1832. public static long zRemove(String key, Object... items) {
  1833. log.info("zRemove(...) => key -> {},items -> {}", key, items);
  1834. Long count = redisTemplate.opsForZSet().remove(key, items);
  1835. log.info("zRemove(...) => count -> {}", count);
  1836. if (count == null) {
  1837. throw new RedisOpsResultIsNullException();
  1838. }
  1839. return count;
  1840. }
  1841. /**
  1842. * 移除(key对应的)zset中,排名范围在[startIndex,endIndex]内的item
  1843. * <p>
  1844. * 注:默认的,按score.item升序排名,排名从0开始
  1845. * <p>
  1846. * 注: 类似于List中的索引,排名可以分为多个方式:
  1847. * 从前到后(正向)的排名: 012...
  1848. * 从后到前(反向)的排名: -1、-2、-3...
  1849. * <p>
  1850. * 注: 不论是使用正向排名,还是使用反向排名,使用此方法时,应保证 startRange代表的元素的位置
  1851. * 在endRange代表的元素的位置的前面,如:
  1852. * 示例一: RedisUtil.ZSetOps.zRemoveRange("name",0,2);
  1853. * 示例二: RedisUtil.ZSetOps.zRemoveRange("site",-2,-1);
  1854. * 示例三: RedisUtil.ZSetOps.zRemoveRange("foo",0,-1);
  1855. * <p>
  1856. * 注:若key不存在,则返回0
  1857. *
  1858. * @param key 定位setkey
  1859. * @param startRange 开始项的排名
  1860. * @param endRange 结尾项的排名
  1861. * @return 实际移除了的项的个数
  1862. */
  1863. public static long zRemoveRange(String key, long startRange, long endRange) {
  1864. log.info("zRemoveRange(...) => key -> {},startRange -> {},endRange -> {}",
  1865. key, startRange, endRange);
  1866. Long count = redisTemplate.opsForZSet().removeRange(key, startRange, endRange);
  1867. log.info("zRemoveRange(...) => count -> {}", count);
  1868. if (count == null) {
  1869. throw new RedisOpsResultIsNullException();
  1870. }
  1871. return count;
  1872. }
  1873. /**
  1874. * 移除(key对应的)zset中,score范围在[minScore,maxScore]内的item
  1875. * <p>
  1876. * 提示: 虽然删除范围包含两侧的端点(即:包含minScore和maxScore),但是由于double存在精度问题,所以建议:
  1877. * 设置值时,minScore应该设置得比要删除的项里,最小的score还小一点
  1878. * maxScore应该设置得比要删除的项里,最大的score还大一点
  1879. * 追注: 本人简单测试了几组数据,暂未出现精度问题
  1880. * <p>
  1881. * 注:若key不存在,则返回0
  1882. *
  1883. * @param key 定位setkey
  1884. * @param minScore score下限(含这个值)
  1885. * @param maxScore score上限(含这个值)
  1886. * @return 实际移除了的项的个数
  1887. */
  1888. public static long zRemoveRangeByScore(String key, double minScore, double maxScore) {
  1889. log.info("zRemoveRangeByScore(...) => key -> {},startIndex -> {},startIndex -> {}",
  1890. key, minScore, maxScore);
  1891. Long count = redisTemplate.opsForZSet().removeRangeByScore(key, minScore, maxScore);
  1892. log.info("zRemoveRangeByScore(...) => count -> {}", count);
  1893. if (count == null) {
  1894. throw new RedisOpsResultIsNullException();
  1895. }
  1896. return count;
  1897. }
  1898. /**
  1899. */减 (key对应的zset中,)item的分数值
  1900. *
  1901. * @param key 定位zset的key
  1902. * @param item 项
  1903. * @param delta 变化量(正 - 增,负 - 减)
  1904. * @return 修改后的score值
  1905. */
  1906. public static double zIncrementScore(String key, String item, double delta) {
  1907. log.info("zIncrementScore(...) => key -> {},item -> {},delta -> {}", key, item, delta);
  1908. Double scoreValue = redisTemplate.opsForZSet().incrementScore(key, item, delta);
  1909. log.info("zIncrementScore(...) => scoreValue -> {}", scoreValue);
  1910. if (scoreValue == null) {
  1911. throw new RedisOpsResultIsNullException();
  1912. }
  1913. return scoreValue;
  1914. }
  1915. /**
  1916. * 返回item在(key对应的)zset中的(按score从小到大的)排名
  1917. * <p>
  1918. * 注: 排名从0开始 即意味着,此方法等价于: 返回item在(key对应的)zset中的位置索引
  1919. * 注: 若key或item不存在,返回null
  1920. * 注: 排序规则是score,item,即:优先以score排序,若score相同,则再按item排序
  1921. *
  1922. * @param key 定位zset的key
  1923. * @param item 项
  1924. * @return 排名(等价于 : 索引)
  1925. */
  1926. public static long zRank(String key, Object item) {
  1927. log.info("zRank(...) => key -> {},item -> {}", key, item);
  1928. Long rank = redisTemplate.opsForZSet().rank(key, item);
  1929. log.info("zRank(...) => rank -> {}", rank);
  1930. if (rank == null) {
  1931. throw new RedisOpsResultIsNullException();
  1932. }
  1933. return rank;
  1934. }
  1935. /**
  1936. * 返回item在(key对应的)zset中的(按score从大到小的)排名
  1937. * <p>
  1938. * 注: 排名从0开始补充: 因为是按score从大到小排序的,所以最大score对应的item的排名为0
  1939. * 注: 若key或item不存在,返回null
  1940. * 注: 排序规则是score,item,即:优先以score排序,若score相同,则再按item排序
  1941. *
  1942. * @param key 定位zset的key
  1943. * @param item 项
  1944. * @return 排名(等价于 : 索引)
  1945. */
  1946. public static long zReverseRank(String key, Object item) {
  1947. log.info("zReverseRank(...) => key -> {},item -> {}", key, item);
  1948. Long reverseRank = redisTemplate.opsForZSet().reverseRank(key, item);
  1949. log.info("zReverseRank(...) => reverseRank -> {}", reverseRank);
  1950. if (reverseRank == null) {
  1951. throw new RedisOpsResultIsNullException();
  1952. }
  1953. return reverseRank;
  1954. }
  1955. /**
  1956. * 根据索引位置,获取(key对应的)zset中排名处于[start,end]中的item项集
  1957. * <p>
  1958. * 注: 不论是使用正向排名,还是使用反向排名,使用此方法时,应保证 startIndex代表的元素的
  1959. * 位置在endIndex代表的元素的位置的前面,如:
  1960. * 示例一: RedisUtil.ZSetOps.zRange("name",0,2);
  1961. * 示例二: RedisUtil.ZSetOps.zRange("site",-2,-1);
  1962. * 示例三: RedisUtil.ZSetOps.zRange("foo",0,-1);
  1963. * <p>
  1964. * 注: 若key不存在,则返回空的集合
  1965. * <p>
  1966. * 注: 当[start,end]的范围比实际zset的范围大时,返回范围上"交集"对应的项集合
  1967. *
  1968. * @param key 定位zset的key
  1969. * @param start 排名开始位置
  1970. * @param end 排名结束位置
  1971. * @return 对应的item项集
  1972. */
  1973. public static Set<String> zRange(String key, long start, long end) {
  1974. log.info("zRange(...) => key -> {},start -> {},end -> {}", key, start, end);
  1975. Set<String> result = redisTemplate.opsForZSet().range(key, start, end);
  1976. log.info("zRange(...) => result -> {}", result);
  1977. return result;
  1978. }
  1979. /**
  1980. * 获取(key对应的)zset中的所有item项
  1981. *
  1982. * @param key 定位zset的键
  1983. * @return (key对应的)zset中的所有item项
  1984. * @see ZSetOps#zRange(String, long, long)
  1985. */
  1986. public static Set<String> zWholeZSetItem(String key) {
  1987. log.info("zWholeZSetItem(...) => key -> {}", key);
  1988. Set<String> result = redisTemplate.opsForZSet().range(key, 0, -1);
  1989. log.info("zWholeZSetItem(...) =>result -> {}", result);
  1990. return result;
  1991. }
  1992. /**
  1993. * 根据索引位置,获取(key对应的)zset中排名处于[start,end]中的entry集
  1994. * <p>
  1995. * 注: 不论是使用正向排名,还是使用反向排名,使用此方法时,应保证 startIndex代表的元素的
  1996. * 位置在endIndex代表的元素的位置的前面,如:
  1997. * 示例一: RedisUtil.ZSetOps.zRange("name",0,2);
  1998. * 示例二: RedisUtil.ZSetOps.zRange("site",-2,-1);
  1999. * 示例三: RedisUtil.ZSetOps.zRange("foo",0,-1);
  2000. * <p>
  2001. * 注: 若key不存在,则返回空的集合
  2002. * <p>
  2003. * 注: 当[start,end]的范围比实际zset的范围大时,返回范围上"交集"对应的项集合
  2004. * <p>
  2005. * 注: 此方法和{@link ZSetOps#zRange(String, long, long)}类似,不过此方法返回的不是item集,而是entry集
  2006. *
  2007. * @param key 定位zset的key
  2008. * @param start 排名开始位置
  2009. * @param end 排名结束位置
  2010. * @return 对应的entry集
  2011. */
  2012. @SneakyThrows
  2013. public static Set<TypedTuple<String>> zRangeWithScores(String key, long start, long end) {
  2014. log.info("zRangeWithScores(...) => key -> {},start -> {},end -> {}", key, start, end);
  2015. Set<TypedTuple<String>> entries = redisTemplate.opsForZSet().rangeWithScores(key, start, end);
  2016. log.info("zRangeWithScores(...) => entries -> {}", mapper.writeValueAsString(entries));
  2017. return entries;
  2018. }
  2019. /**
  2020. * 获取(key对应的)zset中的所有entry
  2021. *
  2022. * @param key 定位zset的键
  2023. * @return (key对应的)zset中的所有entry
  2024. * @see ZSetOps#zRangeWithScores(String, long, long)
  2025. */
  2026. @SneakyThrows
  2027. public static Set<TypedTuple<String>> zWholeZSetEntry(String key) {
  2028. log.info("zWholeZSetEntry(...) => key -> {}", key);
  2029. Set<TypedTuple<String>> entries = redisTemplate.opsForZSet().rangeWithScores(key, 0, -1);
  2030. log.info("zWholeZSetEntry(...) => entries -> {}", key, mapper.writeValueAsString(entries));
  2031. return entries;
  2032. }
  2033. /**
  2034. * 根据score,获取(key对应的)zset中分数值处于[minScore,maxScore]中的item项集
  2035. * <p>
  2036. * 注: 若key不存在,则返回空的集合
  2037. * 注: 当[minScore,maxScore]的范围比实际zset中score的范围大时,返回范围上"交集"对应的项集合
  2038. * <p>
  2039. * 提示: 虽然删除范围包含两侧的端点(即:包含minScore和maxScore),但是由于double存在精度问题,所以建议:
  2040. * 设置值时,minScore应该设置得比要删除的项里,最小的score还小一点
  2041. * maxScore应该设置得比要删除的项里,最大的score还大一点
  2042. * 追注: 本人简单测试了几组数据,暂未出现精度问题
  2043. *
  2044. * @param key 定位zset的key
  2045. * @param minScore score下限
  2046. * @param maxScore score上限
  2047. * @return 对应的item项集
  2048. */
  2049. public static Set<String> zRangeByScore(String key, double minScore, double maxScore) {
  2050. log.info("zRangeByScore(...) => key -> {},minScore -> {},maxScore -> {}", key, minScore, maxScore);
  2051. Set<String> items = redisTemplate.opsForZSet().rangeByScore(key, minScore, maxScore);
  2052. log.info("zRangeByScore(...) => items -> {}", items);
  2053. return items;
  2054. }
  2055. /**
  2056. * 根据score,获取(key对应的)zset中分数值处于[minScore,maxScore]中的,score处于[minScore,
  2057. * 排名大于等于offset的count个item项
  2058. * <p>
  2059. * 特别注意: 对于不是特别熟悉redis的人来说,offset 和 count最好都使用正数,避免引起理解上的歧义
  2060. * <p>
  2061. * 注: 若key不存在,则返回空的集合
  2062. * <p>
  2063. * 提示: 虽然删除范围包含两侧的端点(即:包含minScore和maxScore),但是由于double存在精度问题,所以建议:
  2064. * 设置值时,minScore应该设置得比要删除的项里,最小的score还小一点
  2065. * maxScore应该设置得比要删除的项里,最大的score还大一点
  2066. * 追注: 本人简单测试了几组数据,暂未出现精度问题
  2067. *
  2068. * @param key 定位zset的key
  2069. * @param minScore score下限
  2070. * @param maxScore score上限
  2071. * @param offset 偏移量(即:排名下限)
  2072. * @param count 期望获取到的元素个数
  2073. * @return 对应的item项集
  2074. */
  2075. public static Set<String> zRangeByScore(String key, double minScore, double maxScore,
  2076. long offset, long count) {
  2077. log.info("zRangeByScore(...) => key -> {},minScore -> {},maxScore -> {},offset -> {},"
  2078. + "count -> {}", key, minScore, maxScore, offset, count);
  2079. Set<String> items = redisTemplate.opsForZSet().rangeByScore(key, minScore, maxScore, offset, count);
  2080. log.info("zRangeByScore(...) => items -> {}", items);
  2081. return items;
  2082. }
  2083. /**
  2084. * 获取(key对应的)zset中的所有score处于[minScore,maxScore]中的entry
  2085. *
  2086. * @param key 定位zset的键
  2087. * @param minScore score下限
  2088. * @param maxScore score上限
  2089. * @return (key对应的)zset中的所有score处于[minScore, maxScore]中的entry
  2090. * @see ZSetOps#zRangeByScore(String, double, double)
  2091. * <p>
  2092. * 注: 若key不存在,则返回空的集合
  2093. * 注: 当[minScore,maxScore]的范围比实际zset中score的范围大时,返回范围上"交集"对应的项集合
  2094. */
  2095. @SneakyThrows
  2096. public static Set<TypedTuple<String>> zRangeByScoreWithScores(String key, double minScore, double maxScore) {
  2097. log.info("zRangeByScoreWithScores(...) => key -> {},minScore -> {},maxScore -> {}",
  2098. key, minScore, maxScore);
  2099. Set<TypedTuple<String>> entries = redisTemplate.opsForZSet().rangeByScoreWithScores(key, minScore, maxScore);
  2100. log.info("zRangeByScoreWithScores(...) => entries -> {}", mapper.writeValueAsString(entries));
  2101. return entries;
  2102. }
  2103. /**
  2104. * 获取(key对应的)zset中,score处于[minScore,maxScore]里的、排名大于等于offset的count个entry
  2105. * <p>
  2106. * 特别注意: 对于不是特别熟悉redis的人来说,offset 和 count最好都使用正数,避免引起理解上的歧义
  2107. *
  2108. * @param key 定位zset的键
  2109. * @param minScore score下限
  2110. * @param maxScore score上限
  2111. * @param offset 偏移量(即:排名下限)
  2112. * @param count 期望获取到的元素个数
  2113. * @return [startIndex, endIndex] & [minScore,maxScore]里的entry
  2114. */
  2115. @SneakyThrows
  2116. public static Set<TypedTuple<String>> zRangeByScoreWithScores(String key, double minScore,
  2117. double maxScore, long offset,
  2118. long count) {
  2119. log.info("zRangeByScoreWithScores(...) => key -> {},minScore -> {},maxScore -> {},"
  2120. + " offset -> {},count -> {}",
  2121. key, minScore, maxScore, offset, count);
  2122. Set<TypedTuple<String>> entries = redisTemplate.opsForZSet().rangeByScoreWithScores(key, minScore,
  2123. maxScore, offset, count);
  2124. log.info("zRangeByScoreWithScores(...) => entries -> {}", mapper.writeValueAsString(entries));
  2125. return entries;
  2126. }
  2127. /**
  2128. * 获取时,先按score倒序,然后根据索引位置,获取(key对应的)zset中排名处于[start,end]中的item项集
  2129. *
  2130. * @see ZSetOps#zRange(String, long, long) 只是zReverseRange这里会提前多一个倒序
  2131. */
  2132. public static Set<String> zReverseRange(String key, long start, long end) {
  2133. log.info("zReverseRange(...) => key -> {},start -> {},end -> {}", key, start, end);
  2134. Set<String> entries = redisTemplate.opsForZSet().reverseRange(key, start, end);
  2135. log.info("zReverseRange(...) => entries -> {}", entries);
  2136. return entries;
  2137. }
  2138. /**
  2139. * 获取时,先按score倒序,然后根据索引位置,获取(key对应的)zset中排名处于[start,end]中的entry集
  2140. *
  2141. * @see ZSetOps#zRangeWithScores(String, long, long) 只是zReverseRangeWithScores这里会提前多一个倒序
  2142. */
  2143. @SneakyThrows
  2144. public static Set<TypedTuple<String>> zReverseRangeWithScores(String key, long start, long end) {
  2145. log.info("zReverseRangeWithScores(...) => key -> {},start -> {},end -> {}", key, start, end);
  2146. Set<TypedTuple<String>> entries = redisTemplate.opsForZSet().reverseRangeWithScores(key, start, end);
  2147. log.info("zReverseRangeWithScores(...) => entries -> {}", mapper.writeValueAsString(entries));
  2148. return entries;
  2149. }
  2150. /**
  2151. * 获取时,先按score倒序,然后根据score,获取(key对应的)zset中分数值处于[minScore,maxScore]中的item项集
  2152. *
  2153. * @see ZSetOps#zRangeByScore(String, double, double) 只是zReverseRangeByScore这里会提前多一个倒序
  2154. */
  2155. public static Set<String> zReverseRangeByScore(String key, double minScore, double maxScore) {
  2156. log.info("zReverseRangeByScore(...) => key -> {},minScore -> {},maxScore -> {}",
  2157. key, minScore, maxScore);
  2158. Set<String> items = redisTemplate.opsForZSet().reverseRangeByScore(key, minScore, maxScore);
  2159. log.info("zReverseRangeByScore(...) => items -> {}", items);
  2160. return items;
  2161. }
  2162. /**
  2163. * 获取时,先按score倒序,然后获取(key对应的)zset中的所有score处于[minScore,maxScore]中的entry
  2164. *
  2165. * @see ZSetOps#zRangeByScoreWithScores(String, double, double) 只是zReverseRangeByScoreWithScores这里会提前多一个倒序
  2166. */
  2167. @SneakyThrows
  2168. public static Set<TypedTuple<String>> zReverseRangeByScoreWithScores(String key, double minScore, double maxScore) {
  2169. log.info("zReverseRangeByScoreWithScores(...) => key -> {},minScore -> {},maxScore -> {}",
  2170. key, minScore, maxScore);
  2171. Set<TypedTuple<String>> entries = redisTemplate.opsForZSet().reverseRangeByScoreWithScores(key,
  2172. minScore, maxScore);
  2173. log.info("zReverseRangeByScoreWithScores(...) => entries -> {}", mapper.writeValueAsString(entries));
  2174. return entries;
  2175. }
  2176. /**
  2177. * 获取时,先按score倒序,然后根据score,获取(key对应的)zset中分数值处于[minScore,maxScore]中的,
  2178. * score处于[minScore,排名大于等于offset的count个item项
  2179. *
  2180. * @see ZSetOps#zRangeByScore(String, double, double, long, long) 只是zReverseRangeByScore这里会提前多一个倒序
  2181. */
  2182. public static Set<String> zReverseRangeByScore(String key, double minScore, double maxScore, long offset, long count) {
  2183. log.info("zReverseRangeByScore(...) => key -> {},minScore -> {},maxScore -> {},offset -> {},"
  2184. + "count -> {}", key, minScore, maxScore, offset, count);
  2185. Set<String> items = redisTemplate.opsForZSet().reverseRangeByScore(key, minScore, maxScore, offset, count);
  2186. log.info("items -> {}", items);
  2187. return items;
  2188. }
  2189. /**
  2190. * 统计(key对应的zset中)score处于[minScore,maxScore]中的item的个数
  2191. *
  2192. * @param key 定位zset的key
  2193. * @param minScore score下限
  2194. * @param maxScore score上限
  2195. * @return [minScore, maxScore]中item的个数
  2196. */
  2197. public static long zCount(String key, double minScore, double maxScore) {
  2198. log.info("zCount(...) => key -> {},minScore -> {},maxScore -> {}", key, minScore, maxScore);
  2199. Long count = redisTemplate.opsForZSet().count(key, minScore, maxScore);
  2200. log.info("zCount(...) => count -> {}", count);
  2201. if (count == null) {
  2202. throw new RedisOpsResultIsNullException();
  2203. }
  2204. return count;
  2205. }
  2206. /**
  2207. * 统计(key对应的)zset中item的个数
  2208. * <p>
  2209. * 注: 此方法等价于{@link ZSetOps#zZCard(String)}
  2210. *
  2211. * @param key 定位zset的key
  2212. * @return zset中item的个数
  2213. */
  2214. public static long zSize(String key) {
  2215. log.info("zSize(...) => key -> {}", key);
  2216. Long size = redisTemplate.opsForZSet().size(key);
  2217. log.info("zSize(...) => size -> {}", size);
  2218. if (size == null) {
  2219. throw new RedisOpsResultIsNullException();
  2220. }
  2221. return size;
  2222. }
  2223. /**
  2224. * 统计(key对应的)zset中item的个数
  2225. * <p>
  2226. * 注: 此方法等价于{@link ZSetOps#zSize(String)}
  2227. *
  2228. * @param key 定位zset的key
  2229. * @return zset中item的个数
  2230. */
  2231. public static long zZCard(String key) {
  2232. log.info("zZCard(...) => key -> {}", key);
  2233. Long size = redisTemplate.opsForZSet().zCard(key);
  2234. log.info("zZCard(...) => size -> {}", size);
  2235. if (size == null) {
  2236. throw new RedisOpsResultIsNullException();
  2237. }
  2238. return size;
  2239. }
  2240. /**
  2241. * 统计(key对应的)zset中指定item的score
  2242. *
  2243. * @param key 定位zset的key
  2244. * @param item zset中的item
  2245. * @return item的score
  2246. */
  2247. public static double zScore(String key, Object item) {
  2248. log.info("zScore(...) => key -> {},item -> {}", key, item);
  2249. Double score = redisTemplate.opsForZSet().score(key, item);
  2250. log.info("zScore(...) => score -> {}", score);
  2251. if (score == null) {
  2252. throw new RedisOpsResultIsNullException();
  2253. }
  2254. return score;
  2255. }
  2256. /**
  2257. * 获取两个(key对应的)ZSet的并集,并将结果add到storeKey对应的ZSet中
  2258. * <p>
  2259. * 注: 和set一样,zset中item是唯一的,在多个zset进行Union时,处理相同的item时,score的值会变为对应的score之和,如:
  2260. * RedisUtil.ZSetOps.zAdd("name1","a",1);和RedisUtil.ZSetOps.zAdd("name2","a",2);
  2261. * 对(name1和name2对应的)zset进行zUnionAndStore之后,新的zset中的项a,对应的score值为3
  2262. * <p>
  2263. * case1: 交集不为空,storeKey不存在,则 会创建对应的storeKey,并将并集添加到(storeKey对应的)ZSet中
  2264. * case2: 交集不为空,storeKey已存在,则 会清除原(storeKey对应的)ZSet中所有的项,然后将并集添加到(storeKey对应的)ZSet中
  2265. * case3: 交集为空,则不进行下面的操作,直接返回0
  2266. *
  2267. * @param key 定位其中一个zset的键
  2268. * @param otherKey 定位另外的zset的键
  2269. * @param storeKey 定位(要把交集添加到哪个)setkey
  2270. * @return add到(storeKey对应的)ZSet后, 该ZSet对应的size
  2271. */
  2272. public static long zUnionAndStore(String key, String otherKey, String storeKey) {
  2273. log.info("zUnionAndStore(...) => key -> {},otherKey -> {},storeKey -> {}", key, otherKey, storeKey);
  2274. Long size = redisTemplate.opsForZSet().unionAndStore(key, otherKey, storeKey);
  2275. log.info("zUnionAndStore(...) => size -> {}", size);
  2276. if (size == null) {
  2277. throw new RedisOpsResultIsNullException();
  2278. }
  2279. return size;
  2280. }
  2281. /**
  2282. * 获取两个(key对应的)ZSet的并集,并将结果add到storeKey对应的ZSet中
  2283. * <p>
  2284. * 注: 和set一样,zset中item是唯一的,在多个zset进行Union时,处理相同的item时,score的值会变为对应的score之和,如:
  2285. * RedisUtil.ZSetOps.zAdd("name1","a",1);和RedisUtil.ZSetOps.zAdd("name2","a",2);
  2286. * 对(name1和name2对应的)zset进行zUnionAndStore之后,新的zset中的项a,对应的score值为3
  2287. * <p>
  2288. * case1: 并集不为空,storeKey不存在,则 会创建对应的storeKey,并将并集添加到(storeKey对应的)ZSet中
  2289. * case2: 并集不为空,storeKey已存在,则 会清除原(storeKey对应的)ZSet中所有的项,然后将并集添加到(storeKey对应的)ZSet中
  2290. * case3: 并集为空,则不进行下面的操作,直接返回0
  2291. *
  2292. * @param key 定位其中一个set的键
  2293. * @param otherKeys 定位其它set的键集
  2294. * @param storeKey 定位(要把并集添加到哪个)setkey
  2295. * @return add到(storeKey对应的)ZSet后, 该ZSet对应的size
  2296. */
  2297. public static long zUnionAndStore(String key, Collection<String> otherKeys, String storeKey) {
  2298. log.info("zUnionAndStore(...) => key -> {},otherKeys -> {},storeKey -> {}", key, otherKeys, storeKey);
  2299. Long size = redisTemplate.opsForZSet().unionAndStore(key, otherKeys, storeKey);
  2300. log.info("zUnionAndStore(...) => size -> {}", size);
  2301. if (size == null) {
  2302. throw new RedisOpsResultIsNullException();
  2303. }
  2304. return size;
  2305. }
  2306. /**
  2307. * 获取两个(key对应的)ZSet的交集,并将结果add到storeKey对应的ZSet中
  2308. * <p>
  2309. * 注: 和set一样,zset中item是唯一的,在多个zset进行Intersect时,处理相同的item时,score的值会变为对应的score之和,如:
  2310. * RedisUtil.ZSetOps.zAdd("name1","a",1);
  2311. * RedisUtil.ZSetOps.zAdd("name1","b",100);
  2312. * 和R
  2313. * edisUtil.ZSetOps.zAdd("name2","a",2);
  2314. * edisUtil.ZSetOps.zAdd("name2","c",200);
  2315. * 对(name1和name2对应的)zset进行zIntersectAndStore之后,新的zset中的项a,对应的score值为3
  2316. * <p>
  2317. * case1: 交集不为空,storeKey不存在,则 会创建对应的storeKey,并将交集添加到(storeKey对应的)ZSet中
  2318. * case2: 交集不为空,storeKey已存在,则 会清除原(storeKey对应的)ZSet中所有的项,然后将交集添加到(storeKey对应的)ZSet中
  2319. * case3: 交集为空,则不进行下面的操作,直接返回0
  2320. *
  2321. * @param key 定位其中一个ZSet的键
  2322. * @param otherKey 定位其中另一个ZSet的键
  2323. * @param storeKey 定位(要把交集添加到哪个)ZSet的key
  2324. * @return add到(storeKey对应的)ZSet后, 该ZSet对应的size
  2325. */
  2326. public static long zIntersectAndStore(String key, String otherKey, String storeKey) {
  2327. log.info("zIntersectAndStore(...) => key -> {},otherKey -> {},storeKey -> {}", key, otherKey, storeKey);
  2328. Long size = redisTemplate.opsForZSet().intersectAndStore(key, otherKey, storeKey);
  2329. log.info("zIntersectAndStore(...) => size -> {}", size);
  2330. if (size == null) {
  2331. throw new RedisOpsResultIsNullException();
  2332. }
  2333. return size;
  2334. }
  2335. /**
  2336. * 获取多个(key对应的)ZSet的交集,并将结果add到storeKey对应的ZSet中
  2337. * <p>
  2338. * case1: 交集不为空,storeKey不存在,则 会创建对应的storeKey,并将交集添加到(storeKey对应的)ZSet中
  2339. * case2: 交集不为空,storeKey已存在,则 会清除原(storeKey对应的)ZSet中所有的项,然后将交集添加到(storeKey对应的)ZSet中
  2340. * case3: 交集为空,则不进行下面的操作,直接返回0
  2341. *
  2342. * @param key 定位其中一个set的键
  2343. * @param otherKeys 定位其它set的键集
  2344. * @param storeKey 定位(要把并集添加到哪个)setkey
  2345. * @return add到(storeKey对应的)ZSet后, 该ZSet对应的size
  2346. */
  2347. public static long zIntersectAndStore(String key, Collection<String> otherKeys, String storeKey) {
  2348. log.info("zIntersectAndStore(...) => key -> {},otherKeys -> {},storeKey -> {}",
  2349. key, otherKeys, storeKey);
  2350. Long size = redisTemplate.opsForZSet().intersectAndStore(key, otherKeys, storeKey);
  2351. log.info("zIntersectAndStore(...) => size -> {}", size);
  2352. if (size == null) {
  2353. throw new RedisOpsResultIsNullException();
  2354. }
  2355. return size;
  2356. }
  2357. }
  2358. /**
  2359. * redis分布式锁(单机版).
  2360. * <p>
  2361. * 使用方式(示例):
  2362. * boolean flag = false;
  2363. * String lockName = "sichuan:mianyang:fucheng:ds";
  2364. * String lockValue = UUID.randomUUID().toString();
  2365. * try {
  2366. * // 非阻塞获取(锁的最大存活时间采用默认值)
  2367. * flag = RedisUtil.LockOps.getLock(lockName,lockValue);
  2368. * // 非阻塞获取e.g.
  2369. * flag = RedisUtil.LockOps.getLock(lockName,lockValue,3,TimeUnit.SECONDS);
  2370. * // 阻塞获取(锁的最大存活时间采用默认值)
  2371. * flag = RedisUtil.LockOps.getLockUntilTimeout(lockName,lockValue,2000);
  2372. * // 阻塞获取e.g.
  2373. * flag = RedisUtil.LockOps.getLockUntilTimeout(lockName,lockValue,2,TimeUnit.SECONDS,2000);
  2374. * if (!flag) {
  2375. * throw new RuntimeException(" obtain redis-lock[" + lockName + "] fail");
  2376. * }
  2377. * // your logic
  2378. * // ...
  2379. * } finally {
  2380. * if (flag) {
  2381. * RedisUtil.LockOps.releaseLock(lockName,lockValue);
  2382. * }
  2383. * }
  2384. * <p>
  2385. * |--------------------------------------------------------------------------------------------------------------------|
  2386. * |单机版分布式锁、集群版分布式锁,特别说明: |
  2387. * | - 此锁是针对单机Redis的分布式锁; |
  2388. * | - 对于Redis集群而言,此锁可能存在失效的情况考虑如下情况: |
  2389. * | 首先,当客户端A通过key-value(假设为key名为key123)在Master上获取到一个锁 |
  2390. * | 然后,Master试着把这个数据同步到Slave的时候突然挂了(此时Slave上没有该分布式锁的key123) |
  2391. * | 接着,Slave变成了Master |
  2392. * | 不巧的是,客户端B此时也一以相同的key去获取分布式锁; |
  2393. * | 因为现在的Master上没有key123代表的分布式锁, |
  2394. * | 所以客户端B此时再通过key123去获取分布式锁时, |
  2395. * | 就能获取成功 |
  2396. * | 那么此时,客户端A和客户端B同时获取到了同一把分布式锁,分布式锁失效 |
  2397. * | - 在Redis集群模式下,如果需要严格的分布式锁的话,可使用Redlock算法来实现Redlock算法原理简述: |
  2398. * | - 获取分布式锁: |
  2399. * | 1. 客户端获取服务器当前的的时间t0 |
  2400. * | 2. 使用相同的key和value依次向5个实例获取锁 |
  2401. * | 注:为了避免在某个redis节点耗时太久而影响到对后面的Redis节点的锁的获取; |
  2402. * | 客户端在获取每一个Redis节点的锁的时候,自身需要设置一个较小的等待获取锁超时的时间, |
  2403. * | 一旦都在某个节点获取分布式锁的时间超过了超时时间,那么就认为在这个节点获取分布式锁失败, |
  2404. * | (不把时间浪费在这一个节点上),继续获取下一个节点的分布式锁 |
  2405. * | 3. 客户端通过当前时间(t1)减去t0,计算(从所有redis节点)获取锁所消耗的总时间t2(注:t2=t1-t0) |
  2406. * | 只有t2小于锁本身的锁定时长(注:若锁的锁定时长是1小时,假设下午一点开始上锁,那么锁会在下午两点 |
  2407. * | 的时候失效,而你却在两点后才获取到锁,这个时候已经没意义了),并且,客户端在至少在多半Redis |
  2408. * | 节点上获取到锁,我们才认为分布式锁获取成功 |
  2409. * | 5. 如果锁已经获取,那么 锁的实际有效时长 = 锁的总有效时长 - 获取分布式锁所消耗的时长; 锁的实际有效时长 应保证 > 0 |
  2410. * | 注: 也就是说,如果获取锁失败,那么 |
  2411. * | A. 可能是 获取到的锁的个数,不满足大多数原则 |
  2412. * | B. 也可能是 锁的实际有效时长不大于0 |
  2413. * | - 释放分布式锁: 在每个redis节点上试着删除锁(,不论有没有在该节点上获取到锁) |
  2414. * | - 集群下的分布式锁,可直接使用现有类库<a href="https://github.com/redisson/redisson"/> |
  2415. * | |
  2416. * | 注: 如果Redis集群项目能够容忍master宕机导致单机版分布式锁失效的情况的话,那么是直接使用单机版分布式锁在Redis集群的项目中的; |
  2417. * | 如果Redis集群项目不能容忍单机版分布式锁失效的情况的话,那么请使用基于RedLock算法的集群版分布式锁; |
  2418. * |--------------------------------------------------------------------------------------------------------------------|
  2419. */
  2420. public static class LockOps {
  2421. /**
  2422. * lua脚本,保证 释放锁脚本 的原子性(以避免,并发场景下,释放了别人的锁)
  2423. */
  2424. private static final String RELEASE_LOCK_LUA;
  2425. /**
  2426. * 分布式锁默认(最大)存活时长
  2427. */
  2428. public static final long DEFAULT_LOCK_TIMEOUT = 3;
  2429. /**
  2430. * DEFAULT_LOCK_TIMEOUT的单位
  2431. */
  2432. public static final TimeUnit DEFAULT_TIMEOUT_UNIT = TimeUnit.SECONDS;
  2433. static {
  2434. // 不论lua中0是否代表失败; 对于java的Boolean而言,返回0,则会被解析为false
  2435. RELEASE_LOCK_LUA = "if redis.call('get',KEYS[1]) == ARGV[1] "
  2436. + "then "
  2437. + " return redis.call('del',KEYS[1]) "
  2438. + "else "
  2439. + " return 0 "
  2440. + "end ";
  2441. }
  2442. /**
  2443. * 获取(分布式)锁.
  2444. * <p>
  2445. * 注: 获取结果是即时返回的、是非阻塞的
  2446. *
  2447. * @see LockOps#getLock(String, String, long, TimeUnit)
  2448. */
  2449. public static boolean getLock(final String key, final String value) {
  2450. return getLock(key, value, DEFAULT_LOCK_TIMEOUT, DEFAULT_TIMEOUT_UNIT);
  2451. }
  2452. /**
  2453. * 获取(分布式)锁
  2454. * 若成功,则直接返回;
  2455. * 若失败,则进行重试,直到成功 或 超时为止
  2456. * <p>
  2457. * 注: 获取结果是阻塞的,要么成功,要么超时,才返回
  2458. *
  2459. * @param retryTimeoutLimit 重试的超时时长(ms)
  2460. * 其它参数可详见:
  2461. * @return 是否成功
  2462. * @see LockOps#getLock(String, String, long, TimeUnit)
  2463. */
  2464. public static boolean getLockUntilTimeout(final String key, final String value,
  2465. final long retryTimeoutLimit) {
  2466. return getLockUntilTimeout(key, value, DEFAULT_LOCK_TIMEOUT, DEFAULT_TIMEOUT_UNIT, retryTimeoutLimit);
  2467. }
  2468. /**
  2469. * 获取(分布式)锁
  2470. * 若成功,则直接返回;
  2471. * 若失败,则进行重试,直到成功 或 超时为止
  2472. * <p>
  2473. * 注: 获取结果是阻塞的,要么成功,要么超时,才返回
  2474. *
  2475. * @param retryTimeoutLimit 重试的超时时长(ms)
  2476. * 其它参数可详见:
  2477. * @return 是否成功
  2478. * @see LockOps#getLock(String, String, long, TimeUnit, boolean)
  2479. */
  2480. public static boolean getLockUntilTimeout(final String key, final String value,
  2481. final long timeout, final TimeUnit unit,
  2482. final long retryTimeoutLimit) {
  2483. log.info("getLockUntilTimeout(...) => key -> {},value -> {},timeout -> {},unit -> {},"
  2484. + "retryTimeoutLimit -> {}ms", key, value, timeout, unit, retryTimeoutLimit);
  2485. long startTime = Instant.now().toEpochMilli();
  2486. long now = startTime;
  2487. do {
  2488. try {
  2489. boolean alreadyGotLock = getLock(key, value, timeout, unit, false);
  2490. if (alreadyGotLock) {
  2491. log.info("getLockUntilTimeout(...) => consume time -> {}ms,result -> true", now - startTime);
  2492. return true;
  2493. }
  2494. } catch (Exception e) {
  2495. log.warn("getLockUntilTimeout(...) => try to get lock failure! e.getMessage -> {}",
  2496. e.getMessage());
  2497. }
  2498. now = Instant.now().toEpochMilli();
  2499. } while (now < startTime + retryTimeoutLimit);
  2500. log.info("getLockUntilTimeout(...) => consume time -> {}ms,result -> false", now - startTime);
  2501. return false;
  2502. }
  2503. /**
  2504. * 获取(分布式)锁
  2505. * <p>
  2506. * 注: 获取结果是即时返回的、是非阻塞的
  2507. *
  2508. * @see LockOps#getLock(String, String, long, TimeUnit, boolean)
  2509. */
  2510. public static boolean getLock(final String key, final String value,
  2511. final long timeout, final TimeUnit unit) {
  2512. return getLock(key, value, timeout, unit, true);
  2513. }
  2514. /**
  2515. * 获取(分布式)锁
  2516. * <p>
  2517. * 注: 获取结果是即时返回的、是非阻塞的
  2518. *
  2519. * @param key 锁名
  2520. * @param value 锁名对应的value
  2521. * 注: value一般采用全局唯一的值,如: requestId、uuid等
  2522. * 这样,释放锁的时候,可以再次验证value值,
  2523. * 保证自己上的锁只能被自己释放,而不会被别人释放
  2524. * 当然,如果锁超时时,会被redis自动删除释放
  2525. * @param timeout 锁的(最大)存活时长
  2526. * 注: 一般的,获取锁与释放锁 都是成对使用的,在锁在达到(最大)存活时长之前,都会被主动释放
  2527. * 但是在某些情况下(如:程序获取锁后,释放锁前,崩了),锁得不到释放,这时就需要等锁过
  2528. * 了(最大)存活时长后,被redis自动删除清理了这样就能保证redis中不会留下死数据
  2529. * @param unit timeout的单位
  2530. * @param recordLog 是否记录日志
  2531. * @return 是否成功
  2532. */
  2533. public static boolean getLock(final String key, final String value,
  2534. final long timeout, final TimeUnit unit,
  2535. boolean recordLog) {
  2536. if (recordLog) {
  2537. log.info("getLock(...) => key -> {},value -> {},timeout -> {},unit -> {},recordLog -> {}",
  2538. key, value, timeout, unit, recordLog);
  2539. }
  2540. Boolean result = redisTemplate.execute((RedisConnection connection) ->
  2541. connection.set(key.getBytes(StandardCharsets.UTF_8),
  2542. value.getBytes(StandardCharsets.UTF_8),
  2543. Expiration.seconds(unit.toSeconds(timeout)),
  2544. RedisStringCommands.SetOption.SET_IF_ABSENT)
  2545. );
  2546. if (recordLog) {
  2547. log.info("getLock(...) => result -> {}", result);
  2548. }
  2549. if (result == null) {
  2550. throw new RedisOpsResultIsNullException();
  2551. }
  2552. return result;
  2553. }
  2554. /**
  2555. * 释放(分布式)锁
  2556. * <p>
  2557. * 注: 此方式能(通过value的唯一性)保证: 自己加的锁,只能被自己释放
  2558. * 注: 锁超时时,也会被redis自动删除释放
  2559. *
  2560. * @param key 锁名
  2561. * @param value 锁名对应的value
  2562. * @return 释放锁是否成功
  2563. */
  2564. public static boolean releaseLock(final String key, final String value) {
  2565. log.info("releaseLock(...) => key -> {},lockValue -> {}", key, value);
  2566. Boolean result = redisTemplate.execute((RedisConnection connection) ->
  2567. connection.eval(RELEASE_LOCK_LUA.getBytes(),
  2568. ReturnType.BOOLEAN, 1,
  2569. key.getBytes(StandardCharsets.UTF_8), value.getBytes(StandardCharsets.UTF_8))
  2570. );
  2571. log.info("releaseLock(...) => result -> {}", result);
  2572. if (result == null) {
  2573. throw new RedisOpsResultIsNullException();
  2574. }
  2575. return result;
  2576. }
  2577. /**
  2578. * 释放锁,不校验该key对应的value
  2579. * <p>
  2580. * 注: 此方式释放锁,可能导致: 自己加的锁,结果被别人释放了
  2581. * 所以不建议使用此方式释放锁
  2582. *
  2583. * @param key 锁名
  2584. */
  2585. @Deprecated
  2586. public static void releaseLock(final String key) {
  2587. KeyOps.delete(key);
  2588. }
  2589. }
  2590. /**
  2591. * 当使用Pipeline 或 Transaction操作redis时,(不论redis中实际操作是否成功,这里)结果(都)会返回null
  2592. * 此时,如果试着将null转换为基本类型的数据时,会抛出此异常
  2593. * <p>
  2594. * 即: 此工具类中的某些方法,希望不要使用Pipeline或Transaction操作redis
  2595. * <p>
  2596. * 注: Pipeline 或 Transaction默认是不启用的,可详见源码:
  2597. *
  2598. * @see LettuceConnection#isPipelined()
  2599. * @see LettuceConnection#isQueueing()
  2600. * @see JedisConnection#isPipelined()
  2601. * @see JedisConnection#isQueueing()
  2602. */
  2603. public static class RedisOpsResultIsNullException extends NullPointerException {
  2604. public RedisOpsResultIsNullException() {
  2605. super();
  2606. }
  2607. public RedisOpsResultIsNullException(String message) {
  2608. super(message);
  2609. }
  2610. }
  2611. /**
  2612. * 提供一些基础功能支持
  2613. */
  2614. public static class Helper {
  2615. /**
  2616. * 默认拼接符
  2617. */
  2618. public static final String DEFAULT_SYMBOL = ":";
  2619. /**
  2620. * 拼接args
  2621. *
  2622. * @see Helper#joinBySymbol(String, String...)
  2623. */
  2624. public static String join(String... args) {
  2625. return Helper.joinBySymbol(DEFAULT_SYMBOL, args);
  2626. }
  2627. /**
  2628. * 使用symbol拼接args
  2629. *
  2630. * @param symbol 分隔符,如: 【:】
  2631. * @param args 要拼接的元素数组,如: 【a b c】
  2632. * @return 拼接后的字符串, 如 【a:b:c】
  2633. */
  2634. public static String joinBySymbol(String symbol, String... args) {
  2635. if (symbol == null || symbol.trim().length() == 0) {
  2636. throw new RuntimeException(" symbol must not be empty!");
  2637. }
  2638. if (args == null || args.length == 0) {
  2639. throw new RuntimeException(" args must not be empty!");
  2640. }
  2641. StringBuilder sb = new StringBuilder(16);
  2642. for (String arg : args) {
  2643. sb.append(arg).append(symbol);
  2644. }
  2645. sb.replace(sb.length() - symbol.length(), sb.length(), "");
  2646. return sb.toString();
  2647. }
  2648. }
  2649. }

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/很楠不爱3/article/detail/703881
推荐阅读
相关标签
  

闽ICP备14008679号