游乐游手机版
首页/数据库/文章详情

Redis中StringRedisTemplate的HashOperations使用详解

时间:2026-06-15 07:03
SpringBoot2 x默认采用Lettuce作为Redis客户端,通过注入StringRedisTemplate并调用opsForHash()获得HashOperations,可对Hash执行put、entries、delete、hasKey等操作,支持覆盖更新与原子自增,可用于计数器场景。Lettuce线程安全且支持连接池,处理Hash高效。同时支持批

Springboot2默认情况下使用lettuce框架访问Redis

先明确几个关键结论:Spring Boot 2.x 默认集成的 Redis 客户端为 Lettuce,并非 Jedis。当你在项目中添加 spring-boot-starter-data-redis 依赖后,底层将自动采用 Lettuce + commons-pool2 连接池方案。简单来说,引入依赖这一步,就是在告知 Spring 框架:即将启用 Redis,并使用默认的高性能配置。以下是 Maven 依赖配置示例,请务必同时引入 commons-pool2,否则连接池功能将无法正常使用。

Redis中StringRedisTemplate中HashOperations的使用详解


    org.springframework.boot
    spring-boot-starter-data-redis


    org.apache.commons
    commons-pool2

在需要操作Redis的类中注入StringRedisTemplate实例

依赖配置完成后,如何使用呢?操作非常简单:在任何由 Spring 管理的 Bean 中直接注入 StringRedisTemplate 即可,Spring Boot 已经为其完成了自动配置。你仅需添加一行 @Autowired 注解,其余工作全部交给框架处理。参考以下代码:

@Autowired
StringRedisTemplate stringRedisTemplate;

StringRedisTemplate基础操作详解

获取 StringRedisTemplate 实例后,最常用的场景之一是操作 Hash 数据结构。通过调用 opsForHash() 方法获取 HashOperations 实例,随后即可像操作 Map 一样进行增删改查。需要注意的是,put 方法在 key 已存在时会直接覆盖旧值,因此它兼具添加与更新两种功能。以下示例代码涵盖了 Hash 数据类型的大部分基础操作:

HashOperations hashOperations = stringRedisTemplate.opsForHash();

// 添加
hashOperations.put("user_hash", "zhangfei", "black face");

// 更新(put覆盖)
hashOperations.put("user_hash", "zhangfei", "mangfu");

// 查询全部(entries)
Map userMap = hashOperations.entries("user_hash");

// 查询所有 key
Set userKeys = hashOperations.keys("user_hash");

// 查询所有 value
List userValues = hashOperations.values("user_hash");

// 删除
hashOperations.delete("user_hash", "zhangfei3");

// 判断是否存在
Boolean aBoolean = hashOperations.hasKey("user_hash", "zhangfei");

封装StringRedisTemplate简化实际项目开发

在实际项目开发中,我们通常会将 Redis 操作封装为独立的 Controller 或 Service 层,以便于测试和代码复用。以下是一个完整的 HashController 示例,演示了如何通过 RESTful 接口对 Hash 数据结构执行添加、更新、查询和删除操作。特别说明:getAll() 方法被多个接口复用,有效避免了代码冗余。

@Controller
@RequestMapping("hash")
public class HashController {

    @Autowired
    private StringRedisTemplate stringRedisTemplate;

    @ResponseBody
    @RequestMapping("/add")
    public Map add() {
        HashOperations hashOperations = stringRedisTemplate.opsForHash();
        hashOperations.put("user_hash", "zhangfei", "black face");
        hashOperations.put("user_hash", "guanyu", "red face");
        return getAll();
    }

    @ResponseBody
    @RequestMapping("/update")
    public Map update() {
        HashOperations hashOperations = stringRedisTemplate.opsForHash();
        Boolean exists = hashOperations.hasKey("user_hash", "zhangfei");
        System.out.println(exists);
        hashOperations.put("user_hash", "zhangfei", "mangfu"); // 覆盖旧值 = 更新
        return getAll();
    }

    @ResponseBody
    @RequestMapping("/all")
    public Map all() {
        return getAll();
    }

    @ResponseBody
    @RequestMapping("/delete")
    public Map delete() {
        HashOperations hashOperations = stringRedisTemplate.opsForHash();
        hashOperations.delete("user_hash", "zhangfei3");
        return getAll();
    }

    public Map getAll() {
        HashOperations hashOperations = stringRedisTemplate.opsForHash();
        Map userMap = hashOperations.entries("user_hash");
        Set userKeys = hashOperations.keys("user_hash");
        List userValues = hashOperations.values("user_hash");
        return userMap;
    }
}

String字符串类型操作详解

String 是 Redis 中最基础的数据类型,但 StringRedisTemplate 提供的操作远不止 set 和 get。例如,setIfAbsent 可实现简易分布式锁,increment 支持原子性计数器操作,append 则可用于字符串拼接。以下方法演示了这些常用功能的具体用法:

private void testValue() {
    ValueOperations value = stringRedisTemplate.opsForValue();

    value.getOperations().delete("aaa");
    value.getOperations().delete("bbb");

    value.set("aaa", "123");
    System.out.println(value.setIfAbsent("aaa", "123")); // key已存在则返回false

    System.out.println(value.size("aaa"));  // 返回字符串长度
    System.out.println(value.get("aaa"));

    value.append("aaa", "456");            // 追加
    System.out.println(value.get("aaa"));

    value.increment("bbb", 1);             // 数值自增
    System.out.println(value.get("bbb"));

    value.multiGet(Arrays.asList("aaa", "bbb", "ccc"))
            .stream().forEach(System.out::println);
}

Hash散列类型操作详解

Hash 在 Redis 中相当于一个内嵌的字典结构,非常适合存储对象类型数据(例如用户信息)。putIfAbsent 方法可避免覆盖已有字段,increment 方法能对 Hash 中的数值字段执行原子性自增操作。此外,entrieskeysvalues 方法可分别一次性获取全部或部分数据。以下是完整的操作示例:

private void testHash() {
    HashOperations hash = stringRedisTemplate.opsForHash();
    hash.getOperations().delete("hash1");

    hash.put("hash1", "aaa", "111");
    hash.put("hash1", "bbb", "222");
    hash.put("hash1", "ccc", "333");

    System.out.println(hash.size("hash1"));

    hash.entries("hash1").forEach((k, v) -> System.out.println(k + "=" + v));

    hash.keys("hash1").stream().forEach(System.out::println);
    hash.values("hash1").stream().forEach(System.out::println);

    System.out.println(hash.putIfAbsent("hash1", "aaa", "aaa")); // 已有key,返回false

    hash.increment("hash1", "count", 1);
    System.out.println(hash.get("hash1", "count"));
    hash.increment("hash1", "count", 1);
    System.out.println(hash.get("hash1", "count"));
    hash.increment("hash1", "count", -1);
    System.out.println(hash.get("hash1", "count"));

    System.out.println(hash.hasKey("hash1", "amount"));
    System.out.println(hash.get("hash1", "count"));
    hash.delete("hash1", "count");
}

Set集合类型操作详解

Set 类型的核心特点是元素唯一且无序,适用于标签系统、社交关系等业务场景。difference(差集)、intersect(交集)、union(并集)是极为实用的集合运算方法。pop 用于随机弹出一个元素,randomMember 可随机查看元素但不移除,move 则能将元素从一个集合迁移至另一个集合。以下代码将这些操作串联演示:

private void testSet() {
    SetOperations set = stringRedisTemplate.opsForSet();
    set.getOperations().delete("set1");
    set.getOperations().delete("set2");

    set.add("set1", "111", "222", "333", "111"); // 重复元素会自动去重
    set.add("set2", "222", "333", "444");

    System.out.println(set.size("set1"));
    System.out.println(set.members("set1"));
    System.out.println(set.members("set2"));

    System.out.println(set.difference("set1", "set2")); // set1中set2没有的
    System.out.println(set.intersect("set1", "set2"));  // 交集
    System.out.println(set.union("set1", "set2"));      // 并集

    set.remove("set1", "111");
    System.out.println(set.pop("set1"));     // 随机弹出一个
    System.out.println(set.randomMember("set2"));  // 随机查看一个
    System.out.println(set.isMember("set2", "444"));

    set.move("set2", "444", "set1");        // 从set2移到set1
    System.out.println(set.members("set1"));
}

List列表类型操作详解

List 类型是一个有序的链表结构,支持从左端或右端插入与弹出元素,非常适合实现消息队列或最新消息列表等场景。leftPush 从左侧插入元素,rightPush 从右侧插入元素,trim 可截取指定范围内的元素子集。特别提示:leftPushIfPresent 仅在 key 存在时执行插入操作,可避免在空列表上产生无效写入。参考以下示例:

private void testList() {
    ListOperations list = stringRedisTemplate.opsForList();
    list.getOperations().delete("list");

    list.leftPush("list", "111");
    list.leftPushIfPresent("list", "222"); // 此时key不存在,不会插入
    list.rightPush("list", "333");
    list.rightPushIfPresent("list", "444");

    System.out.println(list.index("list", 0));
    System.out.println(list.range("list", 0, -1));

    list.trim("list", 0, 2);  // 只保留索引0~2的元素
    System.out.println(list.range("list", 0, -1));

    System.out.println(list.leftPop("list"));  // 弹出并移除左侧第一个
    System.out.println(list.rightPop("list"));
}

Zset有序集合类型操作详解

Zset 在 Set 的基础上引入了 score(分数)概念,支持按分数进行排序,特别适用于排行榜、优先级队列等场景。range 按分数升序排列元素,reverseRange 则按分数降序排列。rank 返回元素在集合中的索引位置(基于升序排序),score 可单独查询指定元素的分数。以下代码演示了这些核心操作:

private void testZSet() {
    ZSetOperations zset = stringRedisTemplate.opsForZSet();
    zset.getOperations().delete("zset1");
    zset.getOperations().delete("zset2");

    zset.add("zset1", "aaa", 1);
    zset.add("zset1", "bbb", 1);
    zset.add("zset1", "ccc", 1);

    zset.add("zset2", "bbb", 1);
    zset.add("zset2", "ccc", 1);
    zset.add("zset2", "ddd", 1);

    System.out.println(zset.size("zset1"));
    System.out.println(zset.range("zset1", 0, -1));

    zset.incrementScore("zset1", "bbb", 1);
    zset.incrementScore("zset1", "aaa", 2);

    System.out.println(zset.range("zset1", 0, -1));
    zset.rangeWithScores("zset1", 0, -1).stream()
            .forEach(t -> System.out.println(t.getValue() + "-" + t.getScore()));

    System.out.println(zset.reverseRange("zset1", 0, -1));
    zset.reverseRangeWithScores("zset1", 0, -1).stream()
            .forEach(t -> System.out.println(t.getValue() + "-" + t.getScore()));

    System.out.println(zset.rank("zset1", "ccc"));          // 递增排序下的索引
    System.out.println(zset.reverseRank("zset1", "ccc"));   // 递减排序下的索引

    System.out.println(zset.score("zset1", "bbb"));

    zset.remove("zset2", "bbb");
    System.out.println(zset.range("zset2", 0, -1));
}

实战案例:基于Redis Hash实现登录与Token管理

学习再多理论知识,都不如一个完整的实战案例更有价值。以下是一个基于 Redis Hash 数据结构存储 Token 与 RefreshToken 的登录、登出及刷新接口实现。核心设计思路如下:以 userId 作为 Hash 的 key,在其内部存储 token 和 refreshToken,并借助 Redis 的过期机制实现 Token 的自动失效。

package com.cjs.example.controller;

import com.cjs.example.ResponseResult;
import com.cjs.example.domain.LoginRequest;
import com.cjs.example.domain.LoginResponse;
import com.cjs.example.domain.RefreshRequest;
import com.cjs.example.enums.ResponseCodeEnum;
import com.cjs.example.utils.JWTUtil;
import org.apache.commons.lang3.StringUtils;
import org.apache.tomcat.util.security.MD5Encoder;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.data.redis.core.HashOperations;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.validation.BindingResult;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*;

import ja va.util.UUID;
import ja va.util.concurrent.TimeUnit;

@RestController
public class LoginController {

    @Value("${secretKey:123456}")
    private String secretKey;

    @Autowired
    private StringRedisTemplate stringRedisTemplate;

    @PostMapping("/login")
    public ResponseResult login(@RequestBody @Validated LoginRequest request, BindingResult bindingResult) {
        if (bindingResult.hasErrors()) {
            return ResponseResult.error(ResponseCodeEnum.PARAMETER_ILLEGAL.getCode(),
                    ResponseCodeEnum.PARAMETER_ILLEGAL.getMessage());
        }
        String username = request.getUsername();
        String password = request.getPassword();
        String userId = "01"; // 假设数据库查询得到

        if ("hello".equals(username) && "world".equals(password)) {
            String token = JWTUtil.generateToken(userId, secretKey);
            String refreshToken = UUID.randomUUID().toString().replace("-", "");

            HashOperations hashOperations = stringRedisTemplate.opsForHash();
            // 以 userId 作为 key,内部存 token 和 refreshToken
            String key = userId;
            hashOperations.put(key, "token", token);
            hashOperations.put(key, "refreshToken", refreshToken);
            stringRedisTemplate.expire(key, JWTUtil.TOKEN_EXPIRE_TIME, TimeUnit.MILLISECONDS);

            LoginResponse loginResponse = new LoginResponse();
            loginResponse.setToken(token);
            loginResponse.setRefreshToken(refreshToken);
            loginResponse.setUsername(userId);
            return ResponseResult.success(loginResponse);
        }
        return ResponseResult.error(ResponseCodeEnum.LOGIN_ERROR.getCode(),
                ResponseCodeEnum.LOGIN_ERROR.getMessage());
    }

    @GetMapping("/logout")
    public ResponseResult logout(@RequestParam("userId") String userId) {
        HashOperations hashOperations = stringRedisTemplate.opsForHash();
        hashOperations.delete(userId);
        return ResponseResult.success();
    }

    @PostMapping("/refreshToken")
    public ResponseResult refreshToken(@RequestBody @Validated RefreshRequest request, BindingResult bindingResult) {
        String userId = request.getUserId();
        String refreshToken = request.getRefreshToken();

        HashOperations hashOperations = stringRedisTemplate.opsForHash();
        String originalRefreshToken = hashOperations.get(userId, "refreshToken");

        if (StringUtils.isBlank(originalRefreshToken) || !originalRefreshToken.equals(refreshToken)) {
            return ResponseResult.error(ResponseCodeEnum.REFRESH_TOKEN_INVALID.getCode(),
                    ResponseCodeEnum.REFRESH_TOKEN_INVALID.getMessage());
        }

        String newToken = JWTUtil.generateToken(userId, secretKey);
        hashOperations.put(userId, "token", newToken);
        stringRedisTemplate.expire(userId, JWTUtil.TOKEN_EXPIRE_TIME, TimeUnit.MILLISECONDS);

        return ResponseResult.success(newToken);
    }
}

总结与核心要点回顾

以上内容全面介绍了 Spring Boot 中 StringRedisTemplate 操作 Redis 五种核心数据类型的详细用法,并附上了一个贴近真实业务场景的登录案例。从依赖配置到具体 API 调用,再到实战中的 Token 生命周期管理,涵盖了日常开发中最常用的 Redis 操作场景。希望这份技术资料能帮助你在实际项目中快速掌握 Redis 集成与开发技巧。

来源:https://www.jb51.net/database/3601504jp.htm
上一篇Redis集群节点换IP后恢复并保留数据 下一篇如何快速发现并解决Redis中大键问题的完整步骤
本站内容用于信息整理与展示,如有侵权或内容问题请及时联系处理。

相关推荐

补充同频道和同主题内容,方便继续浏览更多相关内容。

同类最新

继续查看同栏目最近更新的文章。

更多
Redis 7.0增量AOF重写RDB前导码配置详解
数据库 · 2026-07-02

Redis 7.0增量AOF重写RDB前导码配置详解

先说一个几乎所有人都踩过的典型误区:很多人把 aof-use-rdb-preamble yes 当作开启“增量重写”的开关。实际上,这个配置只干了一件事——让重写后的 AOF 文件头部带上 RDB 快照。它解决的是加载速度问题,跟“增量重写”本身的概念压根不是一回事。真正的增量重写,依赖的是 Red

在Python Tornado异步框架中安全执行SQL命令的方法与最佳实践
数据库 · 2026-07-02

在Python Tornado异步框架中安全执行SQL命令的方法与最佳实践

直接在Tornado里用SQLAlchemy同步执行SQL,结果就是阻塞IOLoop,所谓“异步框架里写同步数据库代码”,等于白搭。安全执行的关键不是“怎么写SQL”,而是“怎么不卡住事件循环”。 为什么不能在RequestHandler里直接调用session execute() 因为sessio

利用SQL触发器实现在INSERT数据时自动同步到审计表
数据库 · 2026-07-02

利用SQL触发器实现在INSERT数据时自动同步到审计表

先说结论:可以用触发器把 INSERT 数据同步到审计表,但必须用 AFTER INSERT,并且审计表的字段顺序、类型、字符集得和源表严格一致。否则,轻则写入错位、数据截断,重则直接报错、丢数据。下面把这些坑一个一个掰开说。 能,但必须用 AFTER INSERT,且审计表字段顺序、类型、字符集要

如何用SQL编写按不同工作日统计员工出勤率
数据库 · 2026-07-02

如何用SQL编写按不同工作日统计员工出勤率

在实际业务中,统计不同工作日的出勤率是HR系统里的高频需求。如果直接按日期函数分组,很容易掉进语言环境、索引失效或分母口径的坑里。下面就来拆解具体的实现要点。 必须用 CASE WHEN 将日期映射为固定 weekday 标签(如 Mon )再分组,避免语言环境导致的分组断裂;需过滤 DOW IN

Spring Boot 3动态拼接SQL为何引发严重安全漏洞
数据库 · 2026-07-02

Spring Boot 3动态拼接SQL为何引发严重安全漏洞

SQL注入漏洞的核心成因,本质上是因为用户输入直接参与了SQL语句的字符串拼接,而未采用参数化绑定机制。在MyBatis中使用${}、QueryWrapper中调用apply()与last()、JPA的@Query注解进行拼接等操作,都会绕过PreparedStatement的安全防护。动态字段必须