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

目录

  • 🌴一、实现"双路"删除
  • 🌴二、关键代码
    • [1. UserContext 工具类](#1. UserContext 工具类)
    • [2. 删除逻辑:手动编排 RedisSearch 实现"知识同步销毁"](#2. 删除逻辑:手动编排 RedisSearch 实现“知识同步销毁”)
  • 🌴三、踩坑记录
  • [🌴四、 可复用的工程经验](#🌴四、 可复用的工程经验)
    • [1. 向量数据库的"增删改查"同步模版](#1. 向量数据库的“增删改查”同步模版)
    • [2. 可观测性是第一生产力](#2. 可观测性是第一生产力)
    • [3. 框架解耦与底层兜底](#3. 框架解耦与底层兜底)
    • [4. 线程上下文的"清洁工"习惯](#4. 线程上下文的“清洁工”习惯)

✨本篇内容:基于(7)实现数据持久化的情况下,当前端执行删除操作时,MySQL和向量数据库中的内容都能被删除。


🌴一、实现"双路"删除

当我在前端点击"删除"时,文章从MySQL移除,同时系统自动在 Redis 向量库中检索并销毁对应的所有分片。

🌴二、关键代码

1. UserContext 工具类

这是解决 user_id 为空及身份泄露的核心。

java 复制代码
public class UserContext {
    private static final ThreadLocal<Long> USER_ID_HOLDER = new ThreadLocal<>();

    public static void setUserId(Long userId) { USER_ID_HOLDER.set(userId); }
    public static Long getUserId() { return USER_ID_HOLDER.get(); }

    // 在 JwtFilter 的 finally 块中调用,防止线程池污染和内存泄漏
    public static void clear() { USER_ID_HOLDER.remove(); }
}

2. 删除逻辑:手动编排 RedisSearch 实现"知识同步销毁"

由于 LangChain4j 适配器暂不支持 Redis 的批量删除,我们直接操作底层 Jedis 解决了 Not supported yet 报错。

java 复制代码
@Transactional
public void deletePost(Long postId) {
    // 1. MySQL 删除
    postMapper.deleteById(postId);

    // 2. 向量库手动同步清理 (使用原生 RedisSearch 指令)
    try (UnifiedJedis jedis = new UnifiedJedis("redis://127.0.0.1:6379")) {
        // 构建标签查询语法:@{articleId}:{23}
        String queryText = String.format("@articleId:%d", postId);
        
        // 在 article-index 索引中搜索所有关联片段
        SearchResult result = unifiedJedis.ftSearch("article-index", queryText);
        
        if (result.getTotalResults() > 0) {
                System.out.println("--- [Debug] 发现 " + result.getTotalResults() + " 个向量片段 ---");

                for (redis.clients.jedis.search.Document doc : result.getDocuments()) {
                    String redisKey = doc.getId(); // 获取如 article:uuid 这种 Key
                    unifiedJedis.del(redisKey);
                    System.out.println("已物理删除 Redis Key: " + redisKey);
                }
            }
    }
}

🌴三、踩坑记录

坑1:在这里插入代码片`1:Redis 插件缺失

【报错信息】: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时数字和文本类型要用不同的语法。

坑3:URL 路径变量无法解析

【报错信息】: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指令实现手动清理。

坑6:Java 类名冲突

【报错信息】: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(),这是预防高并发环境下身份泄露和内存泄漏的"工业级金标准"。

相关推荐
Revolution6126 分钟前
第一次运行 Node.js:终端里的 JavaScript 怎样执行
后端·面试·node.js
paopaokaka_luck1 小时前
基于springboot3+vue3的智能文库平台(AI智能搜索、AI智能汇总、实时在线状态展示、多格式文档预览与富文本编辑、Echarts图形化分析)
前端·网络·spring boot·网络协议·echarts
Python私教1 小时前
Django 6.1 RC1 实测:FETCH_PEERS 两条 SQL 解决 N+1,select_related 还需要吗?
后端·python·django
大侠在此在在此1 小时前
教你用最简单的数学自底向上地高效学习SQL注入
后端
Python私教1 小时前
Django 6.0 自带 Tasks 到底能不能替代 Celery?跑完 3 组后台任务后我有答案了
后端·python·django
看昭奚恤哭2 小时前
ontainer App】Container App无法从Container Registries 拉取镜像 - 报错 Forbidden
后端·python·flask
程序员cxuan3 小时前
DeepSeek-V4-Flash 正式版来了!这次提升有点夸张。
人工智能·后端·程序员
小杍随笔5 小时前
2025年Rust GUI框架实战万字避坑指南
开发语言·后端·rust
geovindu6 小时前
CSharp: LogHelper
开发语言·后端·c#·.net
fliter6 小时前
Go设计取舍之一: goroutine 为什么保持匿名、无状态
后端