(8):实现双删(MySQL+Redis)

张开发
2026/4/19 17:18:27 15 分钟阅读

分享文章

(8):实现双删(MySQL+Redis)
目录一、实现“双路”删除二、关键代码1. UserContext 工具类2. 删除逻辑手动编排 RedisSearch 实现“知识同步销毁”三、踩坑记录坑1在这里插入代码片1Redis 插件缺失坑2索引配置缺失导致的“数据幽灵”坑3URL 路径变量无法解析坑4路径匹配失败静态资源混淆坑5框架接口未实现坑6Java 类名冲突四、 可复用的工程经验1. 向量数据库的“增删改查”同步模版2. 可观测性是第一生产力3. 框架解耦与底层兜底4. 线程上下文的“清洁工”习惯✨本篇内容基于7实现数据持久化的情况下当前端执行删除操作时MySQL和向量数据库中的内容都能被删除。一、实现“双路”删除当我在前端点击“删除”时文章从MySQL移除同时系统自动在 Redis 向量库中检索并销毁对应的所有分片。二、关键代码1. UserContext 工具类这是解决 user_id 为空及身份泄露的核心。publicclassUserContext{privatestaticfinalThreadLocalLongUSER_ID_HOLDERnewThreadLocal();publicstaticvoidsetUserId(LonguserId){USER_ID_HOLDER.set(userId);}publicstaticLonggetUserId(){returnUSER_ID_HOLDER.get();}// 在 JwtFilter 的 finally 块中调用防止线程池污染和内存泄漏publicstaticvoidclear(){USER_ID_HOLDER.remove();}}2. 删除逻辑手动编排 RedisSearch 实现“知识同步销毁”由于 LangChain4j 适配器暂不支持 Redis 的批量删除我们直接操作底层 Jedis 解决了 Not supported yet 报错。TransactionalpublicvoiddeletePost(LongpostId){// 1. MySQL 删除postMapper.deleteById(postId);// 2. 向量库手动同步清理 (使用原生 RedisSearch 指令)try(UnifiedJedisjedisnewUnifiedJedis(redis://127.0.0.1:6379)){// 构建标签查询语法{articleId}:{23}StringqueryTextString.format(articleId:%d,postId);// 在 article-index 索引中搜索所有关联片段SearchResultresultunifiedJedis.ftSearch(article-index,queryText);if(result.getTotalResults()0){System.out.println(--- [Debug] 发现 result.getTotalResults() 个向量片段 ---);for(redis.clients.jedis.search.Documentdoc:result.getDocuments()){StringredisKeydoc.getId();// 获取如 article:uuid 这种 KeyunifiedJedis.del(redisKey);System.out.println(已物理删除 Redis Key: redisKey);}}}}三、踩坑记录坑1在这里插入代码片1Redis 插件缺失【报错信息】Factory method embeddingStore threw exception; nested exception is redis.clients.jedis.exceptions.JedisDataException: ERR unknown command FT._LIST【真相】连接的是普通版 Redis。向量库搜索RediSearch是 Redis 的增强模块。【解决】使用 Docker 部署redis/redis-stack 镜像。坑2索引配置缺失导致的“数据幽灵”【报错信息】[debug]-----result:SearchResult{Total results:0, Documents:[]}即便数据已在 Redis 中但检索不到。【真相】Redis 索引中未包含 articleId字段。RedisSearch 只有在索引中声明的字段才可用于查询。【解决】在 AiConfig 中增加.metadataKeys(Collections.singletonList(“articleId”)) 并执行 FLUSHALL重建索引。技巧利用docker exec -it redis-stack redis-cli FT.INFO article-index命令来查看属性列表和类型。主义类型是因为在搜索queryText时数字和文本类型要用不同的语法。坑3URL 路径变量无法解析【报错信息】Required URI template variable postId for method parameter type Long is not present【真相】DeleteMapping(“/{id}”) 路径中的变量名与参数名 Long postId 不一致。【解决】统一变量名或使用 PathVariable(“id”) 显式指定映射。坑4路径匹配失败静态资源混淆【报错信息】org.springframework.web.servlet.resource.NoResourceFoundException: No static resource posts/delete/22【真相】Controller 未定义该路径。Spring Boot3 将无法匹配的 URL 默认转向静态资源目录。【解决】修正接口路径格式。坑5框架接口未实现【报错信息】java.lang.UnsupportedOperationException: Not supported yet在执行embeddingStore.removeAll(filter) 时。【真相】LangChain4j 的 Redis适配器尚未完整实现批量删除接口。【解决】弃用框架 API引入 UnifiedJedis 直接调用 Redis 原生 FT.SEARCH指令实现手动清理。坑6Java 类名冲突【报错信息】java: 不兼容的类型: redis.clients.jedis.search.Document 无法转换为 dev.langchain4j.data.document.Document【真相】两个框架都定义了 Document类但语义完全不同。【解决】在代码中使用全限定名 redis.clients.jedis.search.Document 进行显式区分。四、 可复用的工程经验1. 向量数据库的“增删改查”同步模版经验在双存储架构MySQL VectorDB中删除操作比写入更难。模式必须在写入时绑定文章唯一 ID 到元数据Metadata删除时采用 “先搜索元数据、再批量销毁 Key” 的原子操作。2. 可观测性是第一生产力经验面对“黑盒”一样的 AI 检索不要瞎猜。模式后端日志打印检索得分Score、召回片段内容、生成的原始 Prompt。中间件工具熟练使用 RedisInsight 查看数据的物理存储格式利用 FT.INFO 检查索引配置。3. 框架解耦与底层兜底经验框架LangChain4j是工具但不是终点。模式当框架能力不足时应具备直接操控底层原生协议Jedis/RedisSearch的能力。这种**“能上能下”**的技术栈掌握能力是大厂面试官最看重的“硬实力”。4. 线程上下文的“清洁工”习惯经验在处理跨层身份传递ThreadLocal时必须严格遵守生命周期闭环。模式JwtFilter 的 finally 块中调用 UserContext.clear()这是预防高并发环境下身份泄露和内存泄漏的“工业级金标准”。

更多文章