新闻详情

新闻详情

首页 / 资讯中心 / 详情

【Spring AI】AiMessageChatMemory 实战:简单通过MySQL数据库实现项目重启后对话状态恢复

发布时间:2026/9/18 22:15:50来源:尧图网络
【Spring AI】AiMessageChatMemory 实战:简单通过MySQL数据库实现项目重启后对话状态恢复
一、背景与需求在开发智能对话系统时我们经常遇到一个挑战如何在项目重启后保持对话状态。想象一下用户正在与你的 AI 助手进行多轮对话突然服务器重启所有对话历史都丢失了用户不得不重新开始这会严重影响用户体验。Spring AI 提供了AiMessageChatMemory 接口它为我们提供了管理对话历史的能力。但默认实现通常是基于内存的项目重启后数据会丢失。本文将结合腾讯云开发者社区的相关实践详细介绍如何实现 AiMessageChatMemory 的持久化存储确保对话状态在项目重启后能够恢复。Spring AI 聊天记忆管理MessageWindowChatMemory 与 MessageChatMemoryAdvisor 详解-腾讯云开发者社区-腾讯云https://cloud.tencent.com/developer/article/2588729二、AiMessageChatMemory 简介AiMessageChatMemory 是 Spring AI 中用于管理对话历史的核心接口它提供了以下主要功能存储用户和 AI 的对话消息管理对话上下文长度提供对话历史的访问和修改方法默认的实现类SimpleAiMessageChatMemory是基于内存的适合单会话场景但不支持持久化。三、实现持久化存储方案1. 数据库设计首先我们需要设计一个数据库表来存储对话历史。CREATE TABLE chat_memory ( id BIGINT PRIMARY KEY AUTO_INCREMENT, session_id VARCHAR(255) NOT NULL, message_type VARCHAR(50) NOT NULL, -- USER, AI, SYSTEM content TEXT NOT NULL, timestamp TIMESTAMP DEFAULT CURRENT_TIMESTAMP, embedding JSON NULL, -- 可选存储消息的嵌入向量 metadata JSON NULL -- 可选存储额外元数据 ); CREATE INDEX idx_session_id ON chat_memory(session_id); CREATE INDEX idx_timestamp ON chat_memory(timestamp);2. 实现自定义 ChatMemory创建一个实现AiMessageChatMemory接口的类import org.springframework.ai.chat.memory.AiMessageChatMemory; import org.springframework.ai.chat.messages.Message; import org.springframework.ai.chat.messages.UserMessage; import org.springframework.ai.chat.messages.AiMessage; import org.springframework.ai.chat.messages.SystemMessage; import org.springframework.stereotype.Component; import javax.sql.DataSource; import java.sql.*; import java.util.ArrayList; import java.util.List; Component public class PersistentAiMessageChatMemory implements AiMessageChatMemory { private final DataSource dataSource; private final String sessionId; private final int maxMessages; public PersistentAiMessageChatMemory(DataSource dataSource, String sessionId, int maxMessages) { this.dataSource dataSource; this.sessionId sessionId; this.maxMessages maxMessages; } Override public void add(UserMessage message) { saveMessage(USER, message.getContent()); } Override public void add(AiMessage message) { saveMessage(AI, message.getContent()); } Override public void add(SystemMessage message) { saveMessage(SYSTEM, message.getContent()); } Override public ListMessage getMessages() { ListMessage messages new ArrayList(); String sql SELECT message_type, content FROM chat_memory WHERE session_id ? ORDER BY timestamp DESC LIMIT ?; try (Connection conn dataSource.getConnection(); PreparedStatement stmt conn.prepareStatement(sql)) { stmt.setString(1, sessionId); stmt.setInt(2, maxMessages); ResultSet rs stmt.executeQuery(); ListMessage tempMessages new ArrayList(); while (rs.next()) { String type rs.getString(message_type); String content rs.getString(content); switch (type) { case USER: tempMessages.add(new UserMessage(content)); break; case AI: tempMessages.add(new AiMessage(content)); break; case SYSTEM: tempMessages.add(new SystemMessage(content)); break; } } // 反转列表使消息按时间顺序排列 for (int i tempMessages.size() - 1; i 0; i--) { messages.add(tempMessages.get(i)); } } catch (SQLException e) { e.printStackTrace(); } return messages; } Override public void clear() { String sql DELETE FROM chat_memory WHERE session_id ?; try (Connection conn dataSource.getConnection(); PreparedStatement stmt conn.prepareStatement(sql)) { stmt.setString(1, sessionId); stmt.executeUpdate(); } catch (SQLException e) { e.printStackTrace(); } } private void saveMessage(String type, String content) { String sql INSERT INTO chat_memory (session_id, message_type, content) VALUES (?, ?, ?); try (Connection conn dataSource.getConnection(); PreparedStatement stmt conn.prepareStatement(sql)) { stmt.setString(1, sessionId); stmt.setString(2, type); stmt.setString(3, content); stmt.executeUpdate(); } catch (SQLException e) { e.printStackTrace(); } // 清理超出限制的旧消息 cleanupOldMessages(); } private void cleanupOldMessages() { String sql DELETE FROM chat_memory WHERE session_id ? AND id NOT IN ( SELECT id FROM chat_memory WHERE session_id ? ORDER BY timestamp DESC LIMIT ? ); try (Connection conn dataSource.getConnection(); PreparedStatement stmt conn.prepareStatement(sql)) { stmt.setString(1, sessionId); stmt.setString(2, sessionId); stmt.setInt(3, maxMessages); stmt.executeUpdate(); } catch (SQLException e) { e.printStackTrace(); } } }3. 创建 Memory 管理服务Service服务层import org.springframework.ai.chat.memory.AiMessageChatMemory; import org.springframework.stereotype.Service; import javax.sql.DataSource; import java.util.Map; import java.util.concurrent.ConcurrentHashMap; Service public class ChatMemoryService { private final DataSource dataSource; private final MapString, AiMessageChatMemory memoryMap new ConcurrentHashMap(); private static final int DEFAULT_MAX_MESSAGES 50; public ChatMemoryService(DataSource dataSource) { this.dataSource dataSource; } public AiMessageChatMemory getMemory(String sessionId) { return memoryMap.computeIfAbsent(sessionId, id - new PersistentAiMessageChatMemory(dataSource, id, DEFAULT_MAX_MESSAGES)); } public void removeMemory(String sessionId) { memoryMap.remove(sessionId); } }4. 在控制器中使用import org.springframework.ai.chat.memory.AiMessageChatMemory; import org.springframework.ai.chat.messages.UserMessage; import org.springframework.ai.chat.messages.AiMessage; import org.springframework.ai.chat.ChatClient; import org.springframework.web.bind.annotation.*; RestController RequestMapping(/chat) public class ChatController { private final ChatClient chatClient; private final ChatMemoryService memoryService; public ChatController(ChatClient chatClient, ChatMemoryService memoryService) { this.chatClient chatClient; this.memoryService memoryService; } PostMapping(/message) public String chat(RequestParam String sessionId, RequestParam String message) { // 获取或创建对话记忆 AiMessageChatMemory memory memoryService.getMemory(sessionId); // 添加用户消息 memory.add(new UserMessage(message)); // 构建完整上下文 var prompt memory.getMessages(); // 调用 AI 模型 var response chatClient.call(prompt); // 添加 AI 响应 memory.add(new AiMessage(response.getResult().getOutput().getContent())); return response.getResult().getOutput().getContent(); } PostMapping(/clear) public String clearMemory(RequestParam String sessionId) { AiMessageChatMemory memory memoryService.getMemory(sessionId); memory.clear(); memoryService.removeMemory(sessionId); return 对话历史已清空; } }四、项目重启后恢复对话1. 会话管理为了在项目重启后能够恢复对话我们需要唯一会话标识 为每个用户会话生成唯一的sessionId会话状态存储 将会话信息存储在数据库或Redis中会话恢复机制 项目启动时加载活跃会话2. 实现会话恢复import org.springframework.boot.ApplicationRunner; import org.springframework.stereotype.Component; import javax.sql.DataSource; import java.sql.Connection; import java.sql.PreparedStatement; import java.sql.ResultSet; import java.util.HashSet; import java.util.Set; Component public class ChatMemoryInitializer implements ApplicationRunner { private final DataSource dataSource; private final ChatMemoryService memoryService; public ChatMemoryInitializer(DataSource dataSource, ChatMemoryService memoryService) { this.dataSource dataSource; this.memoryService memoryService; } Override public void run(ApplicationArguments args) throws Exception { // 加载活跃会话 SetString sessionIds getActiveSessions(); for (String sessionId : sessionIds) { // 预加载对话记忆 memoryService.getMemory(sessionId); System.out.println(已恢复会话: sessionId); } System.out.println(共恢复 sessionIds.size() 个活跃会话); } private SetString getActiveSessions() throws Exception { SetString sessionIds new HashSet(); String sql SELECT DISTINCT session_id FROM chat_memory; try (Connection conn dataSource.getConnection(); PreparedStatement stmt conn.prepareStatement(sql); ResultSet rs stmt.executeQuery()) { while (rs.next()) { sessionIds.add(rs.getString(session_id)); } } return sessionIds; } }五、总结本方案基于 MySQL Java 实现的会话状态持久化非常适合课程项目、中小规模应用或对实时性要求不高的场景。其核心优势在于数据持久化可靠 MySQL作为成熟的关系型数据库提供完整的事务支持和数据一致性保障开发成本低 SQL语法直观Java生态成熟学习曲线平缓功能完整 满足会话管理、消息存储、历史查询等基础需求可维护性强 便于后续功能扩展在高并发、大流量、低延迟的实战场景中更推荐采用Redis作为缓存层 MySQL作为持久化层的混合架构。通过实现AiMessageChatMemory的持久化存储我们可以确保在项目重启后能够恢复对话状态为用户提供连续、一致的对话体验。这种方案不仅适用于智能助手类应用也适用于任何需要保持对话上下文的场景。
网站建设高端定制企业官网
RELATED

相关资讯

更多精彩内容,欢迎继续阅读

较早相关资讯

最新相关资讯

Vue组件实现多行文本展开收起:从line-clamp到scrollHeight溢出检测 2026/9/18 22:57:56

Vue组件实现多行文本展开收起:从line-clamp到scrollHeight溢出检测

简介:面向Vue开发者的多行文字展开收起实现示例,聚焦长文本展示中“显示更多/收起”这一常见交互,适合内容列表、文章摘要、详情介绍等前端场景。资源为PDF格式,共1个文件,仅36KB,内容简明紧凑,…

阅读更多 →
国产电脑/笔记本安装Windows双系统手记 2026/9/18 22:57:56

国产电脑/笔记本安装Windows双系统手记

文章目录可行性可用性制作启动盘推荐系统国产系统下查看当前硬件型号图形界面方式命令行方式安装步骤前置准备动态无损分区WePE DiskGenius(推荐)国产系统下使用GParted国产系统下使用命令行安装Windows系统Ventoy直接引导Windows镜像(推荐&…

阅读更多 →
Shopify 从 React Native 回归原生:跨端选型的六年反思与启示 2026/9/18 22:57:56

Shopify 从 React Native 回归原生:跨端选型的六年反思与启示

倒反天罡!押注 React Native 6 年后,Shopify 又回到了原生开发最近移动开发圈里最热闹的一条消息,莫过于 Shopify 宣布核心应用开始从 React Native 回归原生开发。这个新闻一出,不少人直接刷屏“倒反天罡”,毕竟当年 …

阅读更多 →
Civitai 用户打击系统(User Strike)设计全解:积分阶梯、ToS 再接受门禁与执行边界 2026/9/18 22:57:56

Civitai 用户打击系统(User Strike)设计全解:积分阶梯、ToS 再接受门禁与执行边界

Civitai 用户打击系统(User Strike)设计全解:积分阶梯、ToS 再接受门禁与执行边界 【免费下载链接】civitai A repository of models, textual inversions, and more 项目地址: https://gitcode.com/GitHub_Trending/ci/civitai 导读 …

阅读更多 →
Diffusion Policy 具身智能 VLA 模型昇腾 310P 离线推理部署指南:ONNX 导出、ATC 转 OM 与 PushT 仿真评测 2026/9/18 22:57:56

Diffusion Policy 具身智能 VLA 模型昇腾 310P 离线推理部署指南:ONNX 导出、ATC 转 OM 与 PushT 仿真评测

Diffusion Policy 具身智能 VLA 模型昇腾 310P 离线推理部署指南:ONNX 导出、ATC 转 OM 与 PushT 仿真评测 【免费下载链接】cann-recipes-embodied-ai 本项目针对具身智能业务中的典型模型、加速算法,提供基于CANN平台的优化样例 项目地址: https://g…

阅读更多 →
MyBatis全局配置文件解析全流程:从XML到Configuration对象 2026/9/18 22:54:56

MyBatis全局配置文件解析全流程:从XML到Configuration对象

我刚入行那会儿&#xff0c;第一次翻mybatis-config.xml&#xff0c;满脑子都是“这玩意儿到底怎么跑起来的”。网上搜出来的教程清一色在教你写<settings>、<mappers>&#xff0c;但没人告诉你这些标签从 XML 变成Configuration对象的过程中&#xff0c;MyBatis 到…

阅读更多 →

今日资讯

本周资讯

本月资讯

看完文章仍有疑问?

联系尧图顾问,获取一对一建站咨询

立即免费咨询 📞 400-888-8888
📞