4.2 hash
4.2.1 概述
- 需求背景
要在 Redis 中存储包含多属性的用户信息(如生日、姓名、年龄等),对比 String 类型存储方案的问题,引出 Hash 类型的优势。 - String 类型存储的痛点
将用户对象序列化为 JSON 字符串存入 Redis 后,若仅需更新单个属性(如 age),必须先读取整个 JSON 字符串、反序列化为对象、修改属性后再序列化回存,会造成传输和处理的资源浪费。 - Redis Hash 类型核心特点
- 是 "字段 - 字段值" 的映射结构,专门适配多属性对象的存储;
- 字段值仅支持字符串类型,不支持嵌套 Hash、集合等复杂类型;
- 可直接操作单个字段(如仅更新 age),无需处理整个对象,解决了 String 类型的资源浪费问题。

4.2.2 命令
-
赋值和取值
为指定 hash 的单个字段赋值,支持插入 / 更新,通过返回值可判断操作类型。
返回值为1:插入(字段不存在)
返回值2:更新(字段存在)hset key field value

获取指定 hash 的单个字段值
hget key field

-
批量赋值和取值
#为指定 hash 批量赋值
hmset key field value [field value ...]
#获取指定 hash 的多个字段值
hmget key field [field ...]

-
获取指定 hash 的所有字段和值
hgetall key
返回 "字段 + 值" 交替的列表

-
判断字段是否存在
查看字段是否存在,存在返回1,不存在返回0。hexists key field

查看字段是否存在,当字段不存在时赋值,类似HSET(返回1),区别在于如果字段已经存在,该命令不执行任何操作(返回0)。
hsetnx key field value

-
删除字段
可以删除一个或多个字段,返回值是被删除的字段个数。hdel key field [field ...]
删除多个字段时,只删除存在的字段,不存在的直接跳过。

-
只获取字段名或字段值
hkeys key
hvals key

7.获取字段数量
hlen key

-
获取key下的所有字段和对应的值
获取指定 Hash 键(key)下所有的字段和对应的值,返回结果是 "字段 + 值" 交替的列表。
返回格式是 "字段 1、值 1、字段 2、值 2......" 的顺序,Redis 会保证字段和值的一一对应。hgetall key

-
字段的增量运算
给指定 Hash 键(key)下的指定字段(field)的整数值,加上增量值 increment(本质就是做加法运算)。hincrby key field increment
注:
-
仅支持整数运算,字段值必须是整数格式的字符串(如 "100"),小数会报错;
-
原子性操作:多线程 / 多服务并发操作时,不会出现计算错误(比如并发加 1,结果不会少加);
-
字段不存在时:自动将字段值初始化为 0,再执行加法(比如给不存在的 count 字段加 10,结果就是 10)。
- 给hashs的entryKey字段值加123(字段初始值为0)
127.0.0.1:6379> hincrby hashs entryKey 123
(integer) 123 - 再次执行,继续加50(123+50=173)
127.0.0.1:6379> hincrby hashs entryKey 50
(integer) 173 - 减法(增量为负数即可):173-80=93
127.0.0.1:6379> hincrby hashs entryKey -80
(integer) 93
- 给hashs的entryKey字段值加123(字段初始值为0)
4.2.3 方法
- Hash 类型核心操作
|----------|-----------------------------------------|-------------|-------------------------|
| 功能 | StringRedisTemplate 方法 | 对应 Redis 命令 | 代码中的作用 |
| 单个设置字段 | opsForHash().put(key, field, value) | HSET | 给指定 hash 的单个字段赋值 |
| 批量设置字段 | opsForHash().putAll(key, Map) | HMSET | 一次性给 hash 设置多个字段 - 值 |
| 单个获取字段值 | opsForHash().get(key, field) | HGET | 获取指定 hash 的单个字段值 |
| 批量获取字段值 | opsForHash().multiGet(key, List) | HMGET | 一次性获取 hash 的多个字段值 |
| 删除指定字段 | opsForHash().delete(key, fields...) | HDEL | 删除 hash 中的一个 / 多个字段 |
| 获取字段数量 | opsForHash().size(key) | HLEN | 返回 hash 中字段的总个数 |
| 判断字段是否存在 | opsForHash().hasKey(key, field) | HEXISTS | 检查 hash 中指定字段是否存在 |
| 获取所有字段名 | opsForHash().keys(key) | HKEYS | 返回 hash 中所有字段名(Set 集合) |
| 获取所有字段值 | opsForHash().values(key) | HVALS | 返回 hash 中所有字段值(List 集合) |
| 获取所有字段和值 | opsForHash().entries(key) | HGETALL | 返回 hash 中所有字段 - 值(Map) |
| 字段值增量更新 | opsForHash().increment(key, field, num) | HINCRBY | 给 hash 字段值做数字增量更新 |
- Hash 关联的 Key 通用操作
同4.1.3 - 注
-
返回值类型:Hash 操作的返回值多为 Object/Set
*- 增量更新限制:increment 仅支持数字类型字段值,需先确保字段值为数字(如代码中先设 entryKey 为 "0"),否则抛异常;
- 连接管理:所有 Hash 操作底层自动复用连接池,无需手动创建 / 关闭连接。
- 结论
- Hash 类型操作的核心套路:stringRedisTemplate.opsForHash().xxx();
- 批量操作(putAll/multiGet)适配 Map/List 入参,与 String 类型的 multiSet/multiGet 逻辑一致;
- Hash 操作区分 "字段级(如 hasKey(key, field))" 和 "键级(如 hasKey(key))",前者查字段、后者查整个 hash 键。
- 代码
package com.qcby.springbootredis;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.data.redis.core.StringRedisTemplate;
import java.util.*;
@SpringBootTest
public class HashTest {
// 注入SpringBoot默认的StringRedisTemplate(替代Jedis)
@Autowired
private StringRedisTemplate stringRedisTemplate;/**
-
Hash类型核心操作
*/
@Test
public void testHash() {
System.out.println("==Hash==");
try {
// ========== 1. 批量设置Hash字段(对应hmset) ==========
Map<String, String> kidMap = new HashMap<>();
kidMap.put("name", "Akshi");
kidMap.put("age", "2");
kidMap.put("sex", "Female");
stringRedisTemplate.opsForHash().putAll("kid", kidMap);// ========== 2. 获取单个/多个Hash字段值(对应hmget) ========== // 获取单个字段 List<Object> nameList = stringRedisTemplate.opsForHash().multiGet("kid", Collections.singletonList("name")); System.out.println(nameList); // 输出 [Akshi] // ========== 3. 删除指定Hash字段(对应hdel) ========== stringRedisTemplate.opsForHash().delete("kid", "age"); // 获取已删除的字段(返回null) List<Object> pwdList = stringRedisTemplate.opsForHash().multiGet("kid", Collections.singletonList("pwd")); System.out.println(pwdList); // 输出 [null] // ========== 4. Hash通用操作 ========== System.out.println(stringRedisTemplate.opsForHash().size("kid")); // 字段数量(hlen),输出 2 System.out.println(stringRedisTemplate.hasKey("kid")); // 判断key是否存在(exists),输出 true System.out.println(stringRedisTemplate.opsForHash().keys("kid")); // 获取所有字段名(hkeys),输出 [name, sex] System.out.println(stringRedisTemplate.opsForHash().values("kid")); // 获取所有字段值(hvals),输出 [Akshi, Female] // 遍历所有字段和值 Set<Object> kidKeys = stringRedisTemplate.opsForHash().keys("kid"); for (Object key : kidKeys) { String value = (String) stringRedisTemplate.opsForHash().get("kid", key); System.out.println(key + ":" + value); } // 批量获取指定字段值 List<Object> values = stringRedisTemplate.opsForHash().multiGet("kid", Arrays.asList("name", "age", "sex")); System.out.println(values); // 输出 [Akshi, null, Female] // 获取Hash所有字段和值(hgetAll) Map<Object, Object> kidAll = stringRedisTemplate.opsForHash().entries("kid"); System.out.println(kidAll); // 输出 {name=Akshi, sex=Female} // ========== 5. 清空当前库(对应flushDB) ========== stringRedisTemplate.getConnectionFactory().getConnection().flushDb(); System.out.println("清空数据库成功"); // ========== 6. 单个设置Hash字段(对应hset) ========== stringRedisTemplate.opsForHash().put("hashs", "entryKey", "entryValue"); stringRedisTemplate.opsForHash().put("hashs", "entryKey1", "entryValue1"); stringRedisTemplate.opsForHash().put("hashs", "entryKey2", "entryValue2"); // ========== 7. 判断Hash字段是否存在(对应hexists) ========== System.out.println(stringRedisTemplate.opsForHash().hasKey("hashs", "entryKey")); // 输出 true // ========== 8. 获取单个Hash字段值(对应hget) ========== System.out.println(stringRedisTemplate.opsForHash().get("hashs", "entryKey")); // 输出 entryValue // 批量获取字段值 System.out.println(stringRedisTemplate.opsForHash().multiGet("hashs", Arrays.asList("entryKey", "entryKey1"))); // 输出 [entryValue, entryValue1] // ========== 9. 删除Hash字段(对应hdel) ========== stringRedisTemplate.opsForHash().delete("hashs", "entryKey"); // ========== 10. 字段值增量更新(对应hincrBy) ========== // 先给entryKey设值为数字(否则会报错) stringRedisTemplate.opsForHash().put("hashs", "entryKey", "0"); Long incrResult = stringRedisTemplate.opsForHash().increment("hashs", "entryKey", 123L); System.out.println(incrResult); // 输出 123 // ========== 11. 再次获取所有字段名和值 ========== System.out.println(stringRedisTemplate.opsForHash().keys("hashs")); // 输出 [entryKey, entryKey1, entryKey2] System.out.println(stringRedisTemplate.opsForHash().values("hashs")); // 输出 [123, entryValue1, entryValue2]} catch (Exception e) {
e.printStackTrace();
}
}
/**
-
商品信息存储示例(对应业务场景)
*/
@Test
public void testGoodsHash() {
// 1. 存储商品信息(对应HMSET items:1001 id 3 name apple price 999.9)
Map<String, String> goodsMap = new HashMap<>();
goodsMap.put("id", "3");
goodsMap.put("name", "apple");
goodsMap.put("price", "999.9");
stringRedisTemplate.opsForHash().putAll("items:1001", goodsMap);// 2. 获取单个字段值(对应HGET items:1001 id)
String goodsId = (String) stringRedisTemplate.opsForHash().get("items:1001", "id");
System.out.println("商品ID:" + goodsId); // 输出 3// 3. 获取商品所有信息(对应HGETALL items:1001)
Map<Object, Object> goodsAll = stringRedisTemplate.opsForHash().entries("items:1001");
System.out.println("商品完整信息:" + goodsAll); // 输出 {id=3, name=apple, price=999.9}
}
}