在 Spring Boot 项目里接 Redis 很常见,但一旦需求从简单缓存变成 JSON 存储加检索,很多人会卡在索引怎么建、对象怎么存、搜索语法怎么写这几步。下面这篇示例按实际落地顺序,把 Redisson 接入 Redis Stack 的关键代码拆开说明,读完你可以直接判断它适不适合用在自己的 JSON 搜索场景里,也能快速照着搭出一套可验证的最小实现。
依赖与基础配置怎么准备
这套方案的基础很简单:Spring Boot 负责应用框架和 Web 接口,spring-boot-starter-data-redis 提供 Redis 基础接入,redisson-spring-boot-starter 负责把 Redis Stack 的 JSON 与搜索能力封装成更顺手的客户端 API。
原文里指定的 Redisson 版本是 3.28.0,这个版本需要保留,因为后面的代码就是基于这一版的 API 写法展开的。
org.springframework.boot
spring-boot-starter-data-redis
org.redisson
redisson-spring-boot-starter
3.28.0
org.springframework.boot
spring-boot-starter-web
org.projectlombok
lombok
true
配置文件部分也不复杂,示例环境直接连本地 Redis,端口使用 8088。如果你只是想先跑通功能,这组配置已经够用:
spring.application.name=demo-redisstack
server.port: 8088
spring.data.redis.host: localhost
spring.data.redis.port: 6379
这里可以顺带明确两点:一是示例默认 Redis Stack 服务已经运行在 localhost:6379;二是本文关注的是应用层接入流程,不涉及容器部署、权限控制和集群配置这些扩展话题。
核心服务如何完成索引、JSON 存取与搜索
真正的关键在服务类 RedisStackService。它把 Redis Stack 的三个核心动作串了起来:启动时创建索引、按 JSON 形式写入对象、再基于索引执行搜索。

package com.example.demoredisstack.service;
import com.example.demoredisstack.entity.User;
import jakarta.annotation.PostConstruct;
import org.redisson.api.RJsonBucket;
import org.redisson.api.RedissonClient;
import org.redisson.api.options.JsonBucketOptions;
import org.redisson.api.search.index.FieldIndex;
import org.redisson.api.search.index.IndexOptions;
import org.redisson.api.search.index.IndexType;
import org.redisson.api.search.query.QueryOptions;
import org.redisson.api.search.query.SearchResult;
import org.redisson.client.codec.StringCodec;
import org.redisson.codec.JacksonCodec;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
@Service
public class RedisStackService {
@Autowired
private RedissonClient redissonClient;
private static final String INDEX_NAME = "idx:users";
private static final String KEY_PREFIX = "user:";
// 项目启动时自动创建索引
@PostConstruct
public void initIndex() {
try {
redissonClient.getSearch().createIndex(INDEX_NAME,
IndexOptions.defaults()
.on(IndexType.JSON)
.prefix(KEY_PREFIX),
FieldIndex.text("$.name").as("name"),
FieldIndex.numeric("$.age").as("age"),
FieldIndex.tag("$.tags").as("tags")
);
} catch (Exception e) {
System.out.println("索引可能已存在或创建失败: " + e.getMessage());
}
}
// 1. 保存 JSON 数据
public void saveUser(User user) {
JsonBucketOptions
索引初始化在启动阶段完成什么事
这段代码先定义了两个常量:索引名 idx:users,以及数据键前缀 user:。后续所有可搜索的数据,都会围绕这两个约定组织。
@PostConstruct 标注的 initIndex() 会在 Spring 容器完成初始化后自动执行。这里通过 redissonClient.getSearch().createIndex(...) 创建一个基于 JSON 的搜索索引,并明确指定:
.on(IndexType.JSON):索引目标是 JSON 文档,而不是普通字符串或 Hash。.prefix(KEY_PREFIX):只为键名前缀是user:的数据建立索引。FieldIndex.text("$.name").as("name"):把 JSON 路径$.name建成文本索引,用于全文匹配。FieldIndex.numeric("$.age").as("age"):把$.age建成数值索引,用于范围查询。FieldIndex.tag("$.tags").as("tags"):把$.tags建成标签索引,便于分类过滤。
示例里还用 try-catch 包住索引创建逻辑。这样做的目的不是忽略错误,而是兼容“索引已经存在”的常见场景。首次启动时它会创建索引,后续重启如果索引仍在,异常会被捕获并输出提示,不会影响服务继续对外提供读写和搜索能力。
JSON 数据为什么用 RJsonBucket 来读写
保存与读取数据时,示例没有走传统的字符串序列化方式,而是直接使用 Redisson 的 RJsonBucket。这让代码表达更贴近“把一个对象当成 JSON 文档存进 Redis”这个动作本身。
保存时,saveUser(User user) 会先用 KEY_PREFIX + user.getId() 组装出最终键名,例如 user:1001。接着通过 JsonBucketOptions.name(...).codec(new JacksonCodec<>(Object.class)) 指定键名和编解码方式,再调用 jsonBucket.set(user) 把 Java 对象写进去。
读取时逻辑基本对称,同样先拼键名,再获取对应的 RJsonBucket,最后通过 bucket.get() 取回 JSON 数据。示例中返回类型写成了 Object,便于直接演示效果;如果放进实际业务里,通常会进一步收敛成明确的领域对象或 DTO。
这一层的价值在于:索引、存储、查询操作都围绕同一份 JSON 数据组织,而不是手动在 Java 对象、Redis 字符串和搜索字段之间来回转换。
搜索语句是怎么拼出来的
搜索入口 searchUsers(String keyword, int minAge) 的代码不长,但几乎把 Redis Stack 搜索场景里最常见的组合都展示出来了。
它先用下面这句构造查询条件:
String query = String.format("@name:%s @age:[%d +inf]", keyword, minAge);
这里的含义可以拆开看:
@name:%s:按name字段做文本匹配,示例里用于搜索姓名关键字。@age:[%d +inf]:按age字段做范围筛选,表示年龄大于等于minAge。- 两个条件并列写在同一条查询语句里,表示同时满足文本匹配和数值过滤。
然后,代码通过:
return redissonClient.getSearch(StringCodec.INSTANCE).search(INDEX_NAME, query, QueryOptions.defaults());
对索引 idx:users 发起查询。这里保留了 StringCodec.INSTANCE 和 QueryOptions.defaults() 这两个细节,说明当前示例采用的是默认查询选项,没有额外指定分页、排序或返回字段裁剪。
原文还特别提到,搜索时 name 参数可以只传部分关键字。例如传入 Alice,就能匹配到 Alice Developer。这也解释了为什么 name 字段在索引里被定义成 text 类型,而不是精确匹配导向的其他类型。
测试接口如何把功能串起来
服务层准备好之后,示例再用一个简单的控制器把“写入、读取、搜索”三个动作暴露成 REST 接口,方便直接从浏览器或接口工具验证。
package com.example.demoredisstack.controller;
import com.example.demoredisstack.entity.User;
import com.example.demoredisstack.service.RedisStackService;
import org.redisson.api.search.query.SearchResult;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;
import java.util.List;
@RestController
@RequestMapping("/test")
public class TestController {
@Autowired
private RedisStackService redisStackService;
// 测试存入数据
@GetMapping("/save")
public String save() {
User user = new User();
user.setId("1001");
user.setName("Alice Developer");
user.setAge(30);
user.setTags(List.of("developer", "docker"));
redisStackService.saveUser(user);
return "User Saved";
}
// 测试获取数据
@GetMapping("/get/{id}")
public Object get(@PathVariable String id) {
return redisStackService.getUser(id);
}
// 测试搜索功能
@GetMapping("/search")
public SearchResult search(@RequestParam String name, @RequestParam(defaultValue = "0") int minAge) {
return redisStackService.searchUsers(name, minAge);
}
}
这个控制器的设计非常直接:
GET /test/save:构造一个固定用户并写入 Redis。GET /test/get/{id}:按用户 ID 读取 JSON 数据。GET /test/search:接收name和minAge参数,调用搜索方法返回结果。
为了让测试路径尽量短,示例把用户数据直接硬编码在接口里:id 为 1001,姓名是 Alice Developer,年龄 30,标签为 developer 和 docker。这不是面向生产的写法,但非常适合验证 Redis Stack 的索引和查询是否按预期工作。
另外,原代码里有几处因排版导致的断词,例如 save、java 被拆开。成稿里已经按正常 Java 写法整理,以免读者直接复制时出错,但方法含义、接口路径、字段值和返回逻辑都保持不变。
怎么验证这套 Redis Stack 接入已经跑通
按原文的测试顺序,应用启动后只需要依次访问三个接口,就能把整条链路验证清楚。

第一步:先写入一条用户数据
先调用 /test/save。如果接口返回 User Saved,说明控制器已经成功构造出用户对象,并通过 RJsonBucket 写进 Redis。此时 Redis 中应该已经存在类似 user:1001 这样的 JSON 文档键。
第二步:按 ID 读取刚写入的 JSON
接着访问 /test/get/1001。如果能够返回刚才写入的用户内容,至少可以确认两件事:一是键名前缀和 ID 组合正确,二是 JacksonCodec 的序列化与反序列化流程没有问题。
第三步:用关键字加年龄条件做搜索
最后访问搜索接口,并传入 name 与 minAge 参数。例如用姓名关键字 Alice,再配合最小年龄条件,就可以验证索引字段和查询语法是否真正生效。
如果返回结果里能找到姓名为 Alice Developer、年龄为 30 的那条数据,就说明这条查询链路已经贯通:JSON 文档被成功索引,文本字段 name 可以匹配关键字,数值字段 age 也能参与范围筛选。
从这组结果来看,Redisson 对 Redis Stack 的封装确实比较适合用来快速落地 JSON 搜索场景。它的优势不在“替你做很多魔法”,而在于把原本分散的索引定义、JSON 读写和搜索调用,收拢成了比较统一的 Java API。对于想在 Spring Boot 里先做一个可跑通原型的团队,这种写法足够省事,也保留了后续继续扩展查询能力的空间。







