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

相关推荐
鬼蛟3 小时前
springcloud
后端·spring·spring cloud
lhbian3 小时前
PHP vs Java vs Go:编程语言终极对比
java·spring boot·后端·kafka·linq
掘金者阿豪3 小时前
实测4大AI做PPT神器:千问 vs Kimi vs 豆包 vs 扣子,谁才是最强生产力?(附完整对比)
后端
石榴树下的七彩鱼3 小时前
身份证 OCR 识别 API 接入详解(Python / Java 示例)
java·开发语言·人工智能·后端·python·ocr·api
Go_error3 小时前
Go 并发控制 Wait & Cancel
后端·go
随风,奔跑4 小时前
Spring Cloud Alibaba学习笔记(一)
java·后端·spring cloud
奔5大叔学编程4 小时前
一个参数取名导致的 DRF 下 GET 方法的行为异常
后端
我叫黑大帅4 小时前
Go 项目中 Redis 缓存的实用设计与实现(Cache-Aside 模式)
redis·后端·面试
didadida2624 小时前
深度解析:现代单页应用(SPA)中微信授权登录的高可用架构实现
后端