当前位置: 首页 > news >正文

LangChain4j 会话记忆存数据库?手把手教你自定义 ChatMemoryStore 接口实现

LangChain4j 会话记忆持久化实战:从接口设计到 MySQL 实现

当开发者需要构建一个具备长期记忆能力的对话系统时,LangChain4j 提供的ChatMemoryStore接口就像一把瑞士军刀——小巧但功能完备。这个看似简单的接口背后,隐藏着对话系统持久化存储的核心逻辑。本文将带您深入理解接口设计哲学,并手把手实现一个基于 MySQL 的存储方案。

1. 理解 ChatMemoryStore 的设计哲学

ChatMemoryStore接口只有三个方法,这种极简设计体现了"约定优于配置"的理念。在 Spring Boot 项目中引入 LangChain4j 依赖后,您会发现这个接口就像对话系统的记忆中枢:

<dependency> <groupId>dev.langchain4j</groupId> <artifactId>langchain4j</artifactId> <version>0.28.0</version> </dependency>

接口的三个核心方法构成一个完整的 CRUD 循环:

  • getMessages:对话系统的"记忆读取"操作
  • updateMessages:兼具插入和更新功能的"记忆写入"操作
  • deleteMessages:记忆清除功能

这种设计有两大精妙之处:

  1. 将复杂的对话树结构序列化责任交给实现类
  2. 通过合并 insert 和 update 操作简化接口

提示:在实际项目中,updateMessages 的实现要特别注意线程安全问题,特别是在高并发场景下。

2. MySQL 存储方案设计与实现

2.1 数据库表结构设计

我们采用关系型数据库最经典的"会话-消息"双表结构。以下是经过生产环境验证的表设计:

CREATE TABLE `chat_session` ( `id` BIGINT NOT NULL AUTO_INCREMENT, `session_id` VARCHAR(64) NOT NULL COMMENT 'LangChain4j 生成的会话ID', `user_id` VARCHAR(64) NOT NULL COMMENT '业务系统用户ID', `created_at` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, `updated_at` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, PRIMARY KEY (`id`), UNIQUE KEY `uk_session` (`session_id`), KEY `idx_user` (`user_id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; CREATE TABLE `chat_message` ( `id` BIGINT NOT NULL AUTO_INCREMENT, `session_id` VARCHAR(64) NOT NULL, `message_id` VARCHAR(64) NOT NULL COMMENT '消息唯一标识', `message_type` ENUM('SYSTEM','AI','USER') NOT NULL, `content` JSON NOT NULL COMMENT '结构化存储消息内容', `created_at` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, PRIMARY KEY (`id`), UNIQUE KEY `uk_message` (`message_id`), KEY `idx_session` (`session_id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;

这个设计有几个关键优化点:

  • 使用 JSON 类型存储序列化后的消息内容
  • 通过 created_at/updated_at 实现自动时间戳管理
  • 为所有查询条件建立合适索引

2.2 MyBatis 实现数据访问层

在 Spring Boot 项目中整合 MyBatis 和 MySQL:

<dependency> <groupId>org.mybatis.spring.boot</groupId> <artifactId>mybatis-spring-boot-starter</artifactId> <version>3.0.3</version> </dependency> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <version>8.0.33</version> </dependency>

定义 Mapper 接口时,特别注意批量操作的方法:

@Mapper public interface ChatMessageMapper { @Select("SELECT content FROM chat_message WHERE session_id = #{sessionId} ORDER BY created_at ASC") List<String> findMessagesBySession(String sessionId); @Insert({ "<script>", "INSERT INTO chat_message(session_id, message_id, message_type, content) VALUES ", "<foreach collection='messages' item='msg' separator=','>", "(#{sessionId}, #{msg.id}, #{msg.type}, #{msg.content})", "</foreach>", "</script>" }) int insertMessages(@Param("sessionId") String sessionId, @Param("messages") List<ChatMessageDTO> messages); @Delete("DELETE FROM chat_message WHERE session_id = #{sessionId}") int deleteMessages(String sessionId); }

3. 实现 ChatMemoryStore 接口

完整的实现类需要考虑事务管理、异常处理和性能优化:

@Component @RequiredArgsConstructor public class MySQLChatMemoryStore implements ChatMemoryStore { private final ChatSessionMapper sessionMapper; private final ChatMessageMapper messageMapper; private final ObjectMapper objectMapper; @Override @Transactional(readOnly = true) public List<ChatMessage> getMessages(Object memoryId) { String sessionId = (String) memoryId; if (!sessionMapper.exists(sessionId)) { return Collections.emptyList(); } return messageMapper.findMessagesBySession(sessionId).stream() .map(json -> ChatMessageDeserializer.messageFromJson(json)) .collect(Collectors.toList()); } @Override @Transactional public void updateMessages(Object memoryId, List<ChatMessage> messages) { String sessionId = (String) memoryId; // 原子性的"存在即更新,不存在则插入" if (sessionMapper.exists(sessionId)) { sessionMapper.touch(sessionId); } else { sessionMapper.insert(new ChatSession(sessionId, "default-user")); } // 先删除旧消息再插入新消息 messageMapper.deleteMessages(sessionId); List<ChatMessageDTO> dtos = messages.stream() .map(msg -> new ChatMessageDTO( UUID.randomUUID().toString(), msg.type().name(), ChatMessageSerializer.messageToJson(msg))) .collect(Collectors.toList()); messageMapper.insertMessages(sessionId, dtos); } @Override @Transactional public void deleteMessages(Object memoryId) { String sessionId = (String) memoryId; messageMapper.deleteMessages(sessionId); sessionMapper.delete(sessionId); } }

实现时的几个技术要点:

  1. 使用@Transactional确保操作原子性
  2. 采用批量插入提高性能
  3. 通过 touch 操作更新会话时间戳
  4. 合理处理对象序列化/反序列化

4. 在 Spring Boot 中集成自定义存储

最后,将我们的实现注入到 LangChain4j 的对话系统中:

@Configuration public class ChatConfig { @Bean public ChatMemoryProvider chatMemoryProvider(MySQLChatMemoryStore memoryStore) { return memoryId -> MessageWindowChatMemory.builder() .id(memoryId) .maxMessages(20) .chatMemoryStore(memoryStore) .build(); } @Bean public Assistant assistant(ChatLanguageModel model, ChatMemoryProvider provider) { return AiServices.builder(Assistant.class) .chatLanguageModel(model) .chatMemoryProvider(provider) .build(); } public interface Assistant { String chat(@MemoryId String sessionId, @UserMessage String message); } }

实际测试时,您会发现即使重启应用,对话历史也能完整恢复:

@SpringBootTest class ChatMemoryTest { @Autowired Assistant assistant; @Test void testPersistence() { String sessionId = "test-session-1"; // 第一次对话 String reply1 = assistant.chat(sessionId, "我叫张三"); assertThat(reply1).contains("你好"); // 模拟应用重启... // 第二次对话 String reply2 = assistant.chat(sessionId, "我叫什么名字?"); assertThat(reply2).contains("张三"); } }

对于需要处理高并发的场景,可以在实现类中加入 Redis 缓存层,形成"MySQL + Redis"的双存储架构。缓存策略建议采用写穿透模式,确保数据一致性。

http://www.cnnetsun.cn/news/1775055.html

相关文章:

  • 【ESP32_IDF】利用LVGL实现高效GIF动画播放的实战指南
  • AWPortrait-Z WebUI二次开发解析:科哥定制界面布局与交互逻辑
  • Realistic Vision V5.1与STM32F103C8T6:嵌入式设备图像生成交互原型
  • Chandra OCR应用案例:数学试卷/表单/PDF批量转结构化文本
  • 在Ubuntu 22.04上搞定CanFestival主站:从源码下载到编译验证的保姆级教程
  • 深入FreeRTOS SMP调度器:主核与从核如何“默契配合”完成第一次任务切换?
  • Alpamayo-R1-10B高算力适配:PyTorch 2.8+bf16混合精度部署优化
  • Qwen3-14B-Int4-AWQ效果集锦:从技术文档到创意写作的多风格文本生成
  • 快速入门:Ollama部署Yi-Coder-1.5B,5分钟搭建编程助手
  • 【数据结构与算法】第34篇:选择排序:简单选择排序与堆排序
  • 谷歌更新Gemini心理健康安全防护措施
  • 人脸识别OOD模型真实效果:会议直播截图中关键人物人脸的OOD分标注集
  • 【网络层-ICMP互联网控制报文协议】
  • Hunyuan-MT Pro实际应用:跨国远程医疗问诊记录多语种结构化摘要生成
  • 基于PySide6的YOLO通用检测平台:从零搭建与多场景适配
  • GPU拓扑结构
  • 实测效果惊艳:DeepSeek-R1-Distill-Qwen-1.5B推理能力展示
  • Gemma-3-12b-it真实作品集:10组高质量图片问答对话效果分享
  • nli-distilroberta-base在智能客服中的应用:自动判断用户意图与诉求
  • 和AI一起搞事情#:边剥龙虾边做个中医技能来起号缓
  • 英语时态全解析:从“时”与“态”的底层逻辑到实战应用
  • Z-Image-Turbo-辉夜巫女轻量部署:8GB显存GPU稳定运行的LoRA文生图方案
  • 保姆级教程:用PSIM+Simulink搭建一个移相全桥的联合仿真模型(从电路简化到结果分析)
  • One API中转搭建完整教程(2026最新)
  • 告别复杂配置!mPLUG-Owl3-2B一键部署,小白也能玩转AI识图
  • Transformer 架构学习笔记
  • ModelEngine的‘会话式API’和‘知识库溯源’到底香不香?一个全栈开发者的深度拆解与性能实测
  • OpenClaw技能扩展指南:用Qwen3-4B实现公众号自动发布
  • Python爬虫终极提速:异步IO(asyncio+aiohttp)优化,比多线程还快4倍
  • 一文读懂私有化即时通讯,企业数据安全的“专属防线”