位置:首页 > Java > Spring Boot 整合 Redis Stack:用 Redisson 实现 JSON 存储与搜索

Spring Boot 整合 Redis Stack:用 Redisson 实现 JSON 存储与搜索

时间:2026-08-24  |  作者:云端旅人  |  阅读:0

目录

  1. 依赖与基础配置怎么准备
  2. 核心服务如何完成索引、JSON 存取与搜索
  3. 测试接口如何把功能串起来
  4. 怎么验证这套 Redis Stack 接入已经跑通

前言

在 Spring Boot 项目里接 Redis 不难,难的是把 JSON 存储、索引建立和条件搜索顺畅地串起来。本文按依赖、配置、服务实现到接口验证的顺序,拆解 Redisson 接入 Redis Stack 的最小可用方案,并结合查询语法与测试结果说明每一步为什么这样写。

在 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 形式写入对象、再基于索引执行搜索。

RedisStackService 中索引创建与字段映射关系图
Redis Stack 索引结构示意用结构图展示 idx:users 索引如何绑定 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 codec = JsonBucketOptions
				.name(KEY_PREFIX + user.getId()).codec(new JacksonCodec<>(Object.class));
		RJsonBucket jsonBucket = redissonClient.getJsonBucket(codec);
		jsonBucket.set(user);
	}

	// 2. 获取 JSON 数据
	public Object getUser(String id) {
		JsonBucketOptions codec = JsonBucketOptions
				.name(KEY_PREFIX + id).codec(new JacksonCodec<>(Object.class));
		RJsonBucket bucket = redissonClient.getJsonBucket(codec);
		return bucket.get();
	}

	// 3. 执行搜索 (例如:搜索名字包含 keyword 且年龄大于 minAge 的用户)
	public SearchResult searchUsers(String keyword, int minAge) {
		// 构建查询语句:名字模糊匹配 AND 年龄范围查询
		// @name:keyword 表示全文检索包含 keyword
		// @age:[minAge +inf] 表示 age >= minAge
		String query = String.format("@name:%s @age:[%d +inf]", keyword, minAge);
		return redissonClient.getSearch(StringCodec.INSTANCE).search(INDEX_NAME, query, QueryOptions.defaults());
	}
}

索引初始化在启动阶段完成什么事

这段代码先定义了两个常量:索引名 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.INSTANCEQueryOptions.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:接收 nameminAge 参数,调用搜索方法返回结果。

为了让测试路径尽量短,示例把用户数据直接硬编码在接口里:id1001,姓名是 Alice Developer,年龄 30,标签为 developerdocker。这不是面向生产的写法,但非常适合验证 Redis Stack 的索引和查询是否按预期工作。

另外,原代码里有几处因排版导致的断词,例如 savejava 被拆开。成稿里已经按正常 Java 写法整理,以免读者直接复制时出错,但方法含义、接口路径、字段值和返回逻辑都保持不变。

怎么验证这套 Redis Stack 接入已经跑通

按原文的测试顺序,应用启动后只需要依次访问三个接口,就能把整条链路验证清楚。

Redis Stack 查询语句与接口验证流程图
接口验证与查询链路把保存、读取、搜索三步测试串起来,突出查询语句中 name 文本匹配与 age。

第一步:先写入一条用户数据

先调用 /test/save。如果接口返回 User Saved,说明控制器已经成功构造出用户对象,并通过 RJsonBucket 写进 Redis。此时 Redis 中应该已经存在类似 user:1001 这样的 JSON 文档键。

第二步:按 ID 读取刚写入的 JSON

接着访问 /test/get/1001。如果能够返回刚才写入的用户内容,至少可以确认两件事:一是键名前缀和 ID 组合正确,二是 JacksonCodec 的序列化与反序列化流程没有问题。

最后访问搜索接口,并传入 nameminAge 参数。例如用姓名关键字 Alice,再配合最小年龄条件,就可以验证索引字段和查询语法是否真正生效。

如果返回结果里能找到姓名为 Alice Developer、年龄为 30 的那条数据,就说明这条查询链路已经贯通:JSON 文档被成功索引,文本字段 name 可以匹配关键字,数值字段 age 也能参与范围筛选。

从这组结果来看,Redisson 对 Redis Stack 的封装确实比较适合用来快速落地 JSON 搜索场景。它的优势不在“替你做很多魔法”,而在于把原本分散的索引定义、JSON 读写和搜索调用,收拢成了比较统一的 Java API。对于想在 Spring Boot 里先做一个可跑通原型的团队,这种写法足够省事,也保留了后续继续扩展查询能力的空间。

免责声明:文中图文均来自网络,如有侵权请联系删除,心愿游戏发布此文仅为传递信息,不代表心愿游戏认同其观点或证实其描述。

精选合集

更多

大家都在玩