Spring AI Chat Memory 实战指南:Local 与 JDBC 存储集成
一个面向 Java 开发者的 Sring-Ai 示例工程项目,该项目是一个 Spring AI 快速入门的样例工程项目,旨在通过一些小的案例展示 Spring AI 框架的核心功能和使用方法。 项目采用模块化设计,每个模块都专注于特定的功能领域,便于学习和扩展。欢迎感兴趣的小伙伴们关注和 Star。
项目地址:https://github.com/java-ai-tech/spring-ai-summary
更多文章:https://mp.weixin.qq.com/s/72omFtMqinJs4MMX41MCyw
前言
在构建智能对话系统时,保持对话上下文的连贯性是提升用户体验的关键。Spring AI 框架提供了强大的 Chat Memory 机制,支持多种存储方式来持久化对话历史。本文将深入解析 Spring AI Chat Memory 的核心机制,并通过实际代码演示如何实现基于本地内存(Local)和数据库(JDBC)的两种存储方案。
Spring AI Chat Memory 核心机制
架构概览 Architecture Overview
Spring AI Chat Memory 采用分层架构设计:
┌─────────────────────────────────────┐
│ ChatClient Layer │
├─────────────────────────────────────┤
│ ChatMemory Advisor │
├─────────────────────────────────────┤
│ ChatMemory Interface │
├─────────────────────────────────────┤
│ ChatMemoryRepository Layer │
├─────────────────────────────────────┤
│ Storage Layer (Local/JDBC) │
└─────────────────────────────────────┘
核心组件解析
- ChatMemory 接口:提供统一的对话记忆管理抽象
- ChatMemoryRepository:负责底层存储操作
- MessageChatMemoryAdvisor:基于 Advisor 模式的透明化处理
- MessageWindowChatMemory:支持消息窗口限制的实现
实现方案一:Local Memory (本地内存存储)
依赖配置
<!-- pom.xml -->
<dependency><groupId>org.springframework.ai</groupId><artifactId>spring-ai-starter-model-openai</artifactId>
</dependency>
Step 1: 创建 ChatClient 配置
@Configuration
public class ChatClientConfigs {@Beanpublic ChatClient chatClient(OpenAiChatModel chatModel, ChatMemory chatMemory) {return ChatClient.builder(chatModel).defaultAdvisors(MessageChatMemoryAdvisor.builder(chatMemory).build()).defaultSystem("You are deepseek chat bot, you answer questions in a concise and accurate manner.").build();}
}
关键点解析:
MessageChatMemoryAdvisor
:采用 Advisor 模式,自动处理消息的存储和检索defaultAdvisors
:为 ChatClient 配置默认的 advisor,使 memory 功能透明化
Step 2: 实现 ChatMemoryService
@Service
public class ChatMemoryService {// 模拟一个会话 IDprivate static final String CONVERSATION_ID = "naming-20250528";@Autowiredprivate ChatClient chatClient;/*** 基于 Advisor 模式的聊天方法* ChatClient 会自动处理消息的存储和检索* * @param message 用户输入消息* @param conversationId 对话会话ID,如果为null则使用默认ID* @return AI的响应内容*/public String chat(String message, String conversationId) {String answer = this.chatClient.prompt().user(message)// 关键:通过 advisor 参数指定对话ID.advisors(a -> a.param(ChatMemory.CONVERSATION_ID, conversationId == null ? CONVERSATION_ID : conversationId)).call().content();return answer;}
}
核心机制:
- 通过
ChatMemory.CONVERSATION_ID
参数指定对话会话 ID - ChatClient 自动从 memory 中检索历史消息并添加到 prompt 中
- 响应后自动将对话记录存储到 memory 中
Step 3: 配置应用属性
# application.properties
spring.application.name=spring-ai-chat-memory-local
server.port=8083
spring.profiles.active=deepseek# DeepSeek API 配置
spring.ai.openai.api-key=${spring.ai.openai.api-key}
spring.ai.openai.chat.base-url=https://api.deepseek.com
spring.ai.openai.chat.completions-path=/v1/chat/completions
spring.ai.openai.chat.options.model=deepseek-chat
Local Memory 优缺点
优点:
- 配置简单,开箱即用
- 响应速度快,无网络延迟
- 适合开发和测试环境
缺点:
- 数据不持久化,重启后丢失
- 不支持多实例间共享
- 内存使用量随对话量增长
实现方案二:JDBC Memory (数据库存储)
依赖配置
<!-- pom.xml -->
<dependencies><dependency><groupId>org.springframework.ai</groupId><artifactId>spring-ai-starter-model-chat-memory-repository-jdbc</artifactId></dependency><dependency><groupId>mysql</groupId><artifactId>mysql-connector-java</artifactId><version>8.0.33</version></dependency>
</dependencies>
Step 1: 数据库表结构
-- schema-mysql.sql
CREATE TABLE IF NOT EXISTS SPRING_AI_CHAT_MEMORY (conversation_id VARCHAR(36) NOT NULL,content TEXT NOT NULL,type VARCHAR(10) NOT NULL,`timestamp` TIMESTAMP NOT NULL,CONSTRAINT TYPE_CHECK CHECK (type IN ('USER', 'ASSISTANT', 'SYSTEM', 'TOOL'))
);CREATE INDEX IF NOT EXISTS SPRING_AI_CHAT_MEMORY_CONVERSATION_ID_TIMESTAMP_IDX
ON SPRING_AI_CHAT_MEMORY(conversation_id, `timestamp`);
表结构解析:
conversation_id
:对话会话标识,支持多会话隔离content
:消息内容type
:消息类型(用户、助手、系统、工具)timestamp
:时间戳,用于消息排序- 复合索引:优化按会话 ID 和时间的查询性能
Step 2: 实现 ChatMemoryService
@Service
public class ChatMemoryService {@Autowiredprivate ChatModel chatModel;@Autowiredprivate JdbcChatMemoryRepository chatMemoryRepository;private ChatMemory chatMemory;@PostConstructpublic void init() {this.chatMemory = MessageWindowChatMemory.builder().chatMemoryRepository(chatMemoryRepository).maxMessages(20) // 限制消息窗口大小.build();}public String call(String message, String conversationId) {// 1. 创建用户消息UserMessage userMessage = new UserMessage(message);// 2. 存储用户消息到 memorythis.chatMemory.add(conversationId, userMessage);// 3. 从 memory 获取对话历史List<Message> messages = chatMemory.get(conversationId);// 4. 调用 ChatModel 生成响应ChatResponse response = chatModel.call(new Prompt(messages));// 5. 存储 AI 响应到 memorychatMemory.add(conversationId, response.getResult().getOutput());return response.getResult().getOutput().getText();}
}
核心机制:
MessageWindowChatMemory
:支持消息窗口限制的内存实现maxMessages
:控制保留的最大消息数量,避免 token 超限- 手动管理消息的存储和检索流程
Step 3: 配置数据源
# application.properties
spring.application.name=spring-ai-chat-memory-jdbc
server.port=8083# JDBC Memory Repository 配置
spring.ai.chat.memory.repository.jdbc.initialize-schema=always
spring.ai.chat.memory.repository.jdbc.schema=classpath:schema-@@platform@@.sql
spring.ai.chat.memory.repository.jdbc.platform=mysql# MySQL 数据源配置
spring.datasource.url=jdbc:mysql://localhost:3306/spring_ai_chat_memory?useUnicode=true&characterEncoding=UTF-8&serverTimezone=UTC
spring.datasource.username=root
spring.datasource.password=${spring.datasource.password}
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
JDBC Memory 优缺点
优点:
- 数据持久化,支持服务重启
- 支持多实例间共享对话历史
- 可扩展性强,支持大规模应用
- 支持复杂查询和数据分析
缺点:
- 配置相对复杂
- 存在网络延迟
- 需要维护数据库
运行效果演示
Local Memory 运行日志
第一轮对话:
用户: hello, my name is glmapper
AI: Hello glmapper! Nice to meet you. How can I help you today?第二轮对话:
用户: do you remember my name?
AI: Yes, I remember! Your name is glmapper. Is there anything specific you'd like to discuss?
JDBC Memory 数据库记录
SELECT * FROM SPRING_AI_CHAT_MEMORY WHERE conversation_id = 'test-naming-202505281800';| conversation_id | content | type | timestamp |
|--------------------------|----------------------------|-----------|--------------------|
| test-naming-202505281800 | hello, my name is glmapper | USER | 2025-01-20 10:30:15 |
| test-naming-202505281800 | Hello glmapper! Nice to... | ASSISTANT | 2025-01-20 10:30:16 |
| test-naming-202505281800 | do you remember my name? | USER | 2025-01-20 10:31:20 |
| test-naming-202505281800 | Yes, I remember! Your... | ASSISTANT | 2025-01-20 10:31:21 |
实战测试验证
测试对话连续性
@Test
@DisplayName("测试聊天记忆功能 - 上下文保持")
void testChatMemoryContextRetention() {String CONVERSATION_ID = "test-naming-202505281800";// 第一轮对话:自我介绍String firstMessage = "hello, my name is glmapper";String firstResponse = chatMemoryService.call(firstMessage, CONVERSATION_ID);// 第二轮对话:询问之前提到的信息String secondMessage = "do you remember my name?";String secondResponse = chatMemoryService.call(secondMessage, CONVERSATION_ID);// 验证AI是否记住了用户的名字assertTrue(secondResponse.contains("glmapper"), "AI 应该记住用户的名字");
}
测试对话隔离性
@Test
@DisplayName("测试对话ID的非一致性")
void testConversationIdNonConsistency() {String CONVERSATION_ID1 = "test-naming-202505281801";String CONVERSATION_ID2 = "test-naming-202505281802";String message1 = "请记住这个数字:12345";String message2 = "刚才我说的数字是什么?";String response1 = chatMemoryService.call(message1, CONVERSATION_ID1);String response2 = chatMemoryService.call(message2, CONVERSATION_ID2);// 验证不同对话ID间的隔离性assertFalse(response2.contains("12345"), "不同对话ID应该相互隔离");
}
方案对比与选择建议
特性 | Local Memory | JDBC Memory |
---|---|---|
数据持久化 | ❌ | ✅ |
配置复杂度 | 低 | 中 |
性能 | 高 | 中 |
多实例共享 | ❌ | ✅ |
扩展性 | 低 | 高 |
适用场景 | 开发/测试 | 生产环境 |
选择建议:
- 开发/测试阶段:使用 Local Memory,快速验证功能
- 生产环境:使用 JDBC Memory,确保数据可靠性
- 高并发场景:考虑使用 Redis 等缓存方案
- 企业级应用:JDBC + 数据库集群方案
常见问题与解决方案
Q1: 为什么 AI 记不住之前的对话?
A: 检查对话 ID 是否一致,确保在同一会话中使用相同的 conversationId
。
Q2: JDBC Memory 初始化失败?
A: 确认数据库连接正常,检查 spring.ai.chat.memory.repository.jdbc.initialize-schema=always
配置。
Q3: 对话历史过长导致 Token 超限?
A: 设置合适的 maxMessages
参数限制消息窗口大小。
this.chatMemory = MessageWindowChatMemory.builder().chatMemoryRepository(chatMemoryRepository).maxMessages(10) // 根据模型 token 限制调整.build();
最佳实践
- 会话 ID 管理:使用 UUID 或有意义的业务标识,建议格式:
user_{userId}_{timestamp}
- 消息窗口控制:根据模型 token 限制合理设置
maxMessages
(通常 10-20 条) - 异常处理:实现 memory 操作的容错机制,避免单点故障
- 性能优化:使用数据库连接池,为高频查询字段建立索引
- 数据清理:定期清理过期对话数据,避免数据库膨胀
- 监控告警:监控 memory 操作的延迟和错误率
总结
Spring AI Chat Memory 提供了灵活的对话记忆管理能力,通过 Local 和 JDBC 两种存储方案,可以满足从开发测试到生产部署的不同需求。Local Memory 适合快速原型开发,而 JDBC Memory 则适合需要数据持久化的生产环境。
理解其核心机制和实现细节,有助于开发者根据实际场景选择合适的方案,构建出高质量的智能对话应用。
项目地址: spring-ai-summary
相关文档:
- Spring AI Documentation
- ChatMemory API Reference
相关文章:
Spring AI Chat Memory 实战指南:Local 与 JDBC 存储集成
一个面向 Java 开发者的 Sring-Ai 示例工程项目,该项目是一个 Spring AI 快速入门的样例工程项目,旨在通过一些小的案例展示 Spring AI 框架的核心功能和使用方法。 项目采用模块化设计,每个模块都专注于特定的功能领域,便于学习和…...
适应性Java用于现代 API:REST、GraphQL 和事件驱动
在快速发展的软件开发领域,REST、GraphQL 和事件驱动架构等新的 API 标准对于构建可扩展、高效的系统至关重要。Java 在现代 API 方面以其在企业应用中的稳定性而闻名,不断适应这些现代范式的需求。随着不断发展的生态系统,Java 在现代 API 方…...

Scrapy-Redis分布式爬虫架构的可扩展性与容错性增强:基于微服务与容器化的解决方案
在大数据时代,海量数据的采集与处理成为企业和研究机构获取信息的关键环节。Scrapy-Redis作为一种经典的分布式爬虫架构,在处理大规模数据抓取任务时展现出强大的能力。然而,随着业务规模的不断扩大和数据抓取需求的日益复杂,传统…...

pikachu靶场通关笔记19 SQL注入02-字符型注入(GET)
目录 一、SQL注入 二、字符型SQL注入 三、字符型注入与数字型注入 四、源码分析 五、渗透实战 1、渗透准备 2、SQL注入探测 (1)输入单引号 (2)万能注入语句 3、获取回显列orderby 4、获取数据库名database 5、获取表名…...

论文阅读笔记——Muffin: Testing Deep Learning Libraries via Neural Architecture Fuzzing
Muffin 论文 现有方法 CRADLE 和 LEMON,依赖模型推理阶段输出进行差分测试,但在训练阶段是不可行的,因为训练阶段直到最后才有固定输出,中间过程是不断变化的。API 库覆盖低,因为各个 API 都是在各种具体场景下使用。…...

【p2p、分布式,区块链笔记 MESH】Bluetooth蓝牙通信 BLE Mesh协议的拓扑结构 定向转发机制
目录 节点的功能承载层(GATT/Adv)局限性: 拓扑关系定向转发机制定向转发意义 CG 节点的功能 节点的功能由节点支持的特性和功能决定。所有节点都能够发送和接收网格消息。节点还可以选择支持一个或多个附加功能,如 Configuration …...

关于easyexcel动态下拉选问题处理
前些日子突然碰到一个问题,说是客户的导入文件模版想支持部分导入内容的下拉选,于是我就找了easyexcel官网寻找解决方案,并没有找到合适的方案,没办法只能自己动手并分享出来,针对Java生成Excel下拉菜单时因选项过多导…...

Ubuntu Cursor升级成v1.0
0. 当前版本低 使用当前 Cursor v0.50时 GitHub Copilot Chat 打不开,快捷键也不好用,当看到 Cursor 升级后,还是蛮高兴的 1. 下载 Cursor 下载地址:https://www.cursor.com/cn/downloads 点击下载 Linux (x64) ,…...
掌握 HTTP 请求:理解 cURL GET 语法
cURL 是一个强大的命令行工具,用于发送 HTTP 请求和与 Web 服务器交互。在 Web 开发和测试中,cURL 经常用于发送 GET 请求来获取服务器资源。本文将详细介绍 cURL GET 请求的语法和使用方法。 一、cURL 基本概念 cURL 是 "Client URL" 的缩写…...

wpf在image控件上快速显示内存图像
wpf在image控件上快速显示内存图像https://www.cnblogs.com/haodafeng/p/10431387.html 如果你在寻找能够快速在image控件刷新大图像(比如分辨率3000*3000的图像)的办法,尤其是想把内存中的裸数据(只有图像的数据,不包…...

android RelativeLayout布局
<?xml version"1.0" encoding"utf-8"?> <RelativeLayout xmlns:android"http://schemas.android.com/apk/res/android"android:layout_width"match_parent"android:layout_height"match_parent"android:gravity&…...

windows系统MySQL安装文档
概览:本文讨论了MySQL的安装、使用过程中涉及的解压、配置、初始化、注册服务、启动、修改密码、登录、退出以及卸载等相关内容,为学习者提供全面的操作指导。关键要点包括: 解压 :下载完成后解压压缩包,得到MySQL 8.…...

【Linux】自动化构建-Make/Makefile
前言 上文我们讲到了Linux中的编译器gcc/g 【Linux】编译器gcc/g及其库的详细介绍-CSDN博客 本来我们将一个对于编译来说很重要的工具:make/makfile 1.背景 在一个工程中源文件不计其数,其按类型、功能、模块分别放在若干个目录中,mak…...

如何应对敏捷转型中的团队阻力
应对敏捷转型中的团队阻力需要明确沟通敏捷转型目的、提升团队参与感、提供充分的培训与支持、逐步推进敏捷实践、建立清晰的奖励和反馈机制。其中,明确沟通敏捷转型目的尤为关键,团队成员只有清晰理解转型背后的原因和利益,才能降低对变化的…...
安卓基础(Java 和 Gradle 版本)
1. 设置项目的 JDK 版本 方法1:通过 Project Structure File → Project Structure... (或按 CtrlAltShiftS) 左侧选择 SDK Location 在 Gradle Settings 部分,设置 Gradle JDK 方法2:通过 Settings File → Settings... (或 CtrlAltS)…...

华为OD机试-最短木板长度-二分法(A卷,100分)
此题是一个最大化最小值的典型例题, 因为搜索范围是有界的,上界最大木板长度补充的全部木料长度,下界最小木板长度; 即left0,right10^6; 我们可以设置一个候选值x(mid),将木板的长度全部都补充到x,如果成功…...
【前端异常】JavaScript错误处理:分析 Uncaught (in promise) error
在前端开发中,JavaScript 异常是不可避免的。随着现代前端应用越来越多地使用异步操作(如 Promise、async/await 等),开发者常常会遇到 Uncaught (in promise) error 错误。这个错误是由于未正确处理 Promise 的拒绝(r…...
在 Spring Boot 项目里,MYSQL中json类型字段使用
前言: 因为程序特殊需求导致,需要mysql数据库存储json类型数据,因此记录一下使用流程 1.java实体中新增字段 private List<User> users 2.增加mybatis-plus注解 TableField(typeHandler FastjsonTypeHandler.class) private Lis…...
「全栈技术解析」推客小程序系统开发:从架构设计到裂变增长的完整解决方案
在移动互联网营销竞争白热化的当下,推客小程序系统凭借其裂变传播、精准营销等特性,成为企业抢占市场的利器。本文将深度解析推客小程序系统开发的核心技术与实现路径,助力开发者打造具有市场竞争力的营销工具。 一、系统核心功能架构&…...

破解路内监管盲区:免布线低位视频桩重塑停车管理新标准
城市路内停车管理常因行道树遮挡、高位设备盲区等问题,导致车牌识别率低、逃费率高,传统模式在复杂路段束手无策。免布线低位视频桩凭借超低视角部署与智能算法,正成为破局关键。该设备安装于车位侧方0.5-0.7米高度,直接规避树枝遮…...

通过 Ansible 在 Windows 2022 上安装 IIS Web 服务器
拓扑结构 这是一个用于通过 Ansible 部署 IIS Web 服务器的实验室拓扑。 前提条件: 在被管理的节点上安装WinRm 准备一张自签名的证书 开放防火墙入站tcp 5985 5986端口 准备自签名证书 PS C:\Users\azureuser> $cert New-SelfSignedCertificate -DnsName &…...
libfmt: 现代C++的格式化工具库介绍与酷炫功能
libfmt: 现代C的格式化工具库介绍与酷炫功能 libfmt 是一个开源的C格式化库,提供了高效、安全的文本格式化功能,是C20中引入的std::format的基础实现。它比传统的printf和iostream更安全、更灵活、性能更好。 基本介绍 主要特点 类型安全:…...

在 Spring Boot 中使用 JSP
jsp? 好多年没用了。重新整一下 还费了点时间,记录一下。 项目结构: pom: <?xml version"1.0" encoding"UTF-8"?> <project xmlns"http://maven.apache.org/POM/4.0.0" xmlns:xsi"http://ww…...

Chrome 浏览器前端与客户端双向通信实战
Chrome 前端(即页面 JS / Web UI)与客户端(C 后端)的交互机制,是 Chromium 架构中非常核心的一环。下面我将按常见场景,从通道、流程、技术栈几个角度做一套完整的分析,特别适合你这种在分析和改…...
LangFlow技术架构分析
🔧 LangFlow 的可视化技术栈 前端节点编辑器 底层框架:基于 (一个现代化的 React 节点绘图库) 功能: 拖拽式构建 LangGraph 状态机 实时连线定义节点依赖关系 可视化调试循环和分支逻辑 与 LangGraph 的深…...

elementUI点击浏览table所选行数据查看文档
项目场景: table按照要求特定的数据变成按钮可以点击 解决方案: <el-table-columnprop"mlname"label"名称"align"center"width"180"><template slot-scope"scope"><el-buttonv-if&qu…...
嵌入式常见 CPU 架构
架构类型架构厂商芯片厂商典型芯片特点与应用场景PICRISC (8/16 位)MicrochipMicrochipPIC16F877A、PIC18F4550简化指令集,单周期执行;低功耗、CIP 独立外设;用于家电、小电机控制、安防面板等嵌入式场景8051CISC (8 位)Intel(原始…...
LOOI机器人的技术实现解析:从手势识别到边缘检测
LOOI机器人作为一款创新的AI硬件产品,通过将智能手机转变为具有情感交互能力的桌面机器人,展示了前沿AI技术与传统硬件设计的完美结合。作为AI与玩具领域的专家,我将全面解析LOOI的技术实现架构,特别是其手势识别、物体识别和环境…...
Qt 事件处理中 return 的深入解析
Qt 事件处理中 return 的深入解析 在 Qt 事件处理中,return 语句的使用是另一个关键概念,它与 event->accept()/event->ignore() 密切相关但作用不同。让我们详细分析一下它们之间的关系和工作原理。 核心区别:不同层级的事件处理 方…...

Vue ③-生命周期 || 脚手架
生命周期 思考:什么时候可以发送初始化渲染请求?(越早越好) 什么时候可以开始操作dom?(至少dom得渲染出来) Vue生命周期: 一个Vue实例从 创建 到 销毁 的整个过程。 生命周期四个…...