Spring AI ChatMemory 对话记忆配置JDBC方式到Mysql数据库实战示例与原理讲解
场景Spring AI ChatMemory 对话记忆配置指南概念、实战与常见问题https://blog.csdn.net/BADAO_LIUMANG_QIZHI/article/details/161020514上述示例对话记忆使用内存方式如何使用JDBC方式将对话记忆到Mysql中。之前我们使用的 InMemoryChatMemoryRepository 将所有对话历史存储在 JVM 堆内存中服务重启后所有会话数据都会丢失且无法在多个服务实例间共享。将存储层切换为 JDBC如 MySQL、PostgreSQL后这些痛点可以得到有效解决。Spring AI 对存储介质做过一次重要的架构演进。早期版本直接提供了 JdbcChatMemory 类。但在 1.1.0 版本之后框架采用了更清晰的职责分离设计将原先的 JdbcChatMemory 标记为 Deprecated并引入了 MessageWindowChatMemory JdbcChatMemoryRepository 的组合模式。这一改动的核心思想是存储逻辑Repository与内存窗口管理ChatMemory彻底解耦使架构更具扩展性也可以更灵活地混合搭配不同的记忆策略与存储后端。核心知识点JdbcChatMemoryRepositoryJdbcChatMemoryRepository 是 ChatMemoryRepository 接口的 JDBC 实现它通过 JdbcTemplate 直接与关系型数据库交互将消息存储在一张名为 SPRING_AI_CHAT_MEMORY 的约定表中。它开箱即用地支持 MySQL、PostgreSQL、SQL Server、HSQLDB、H2 等多种数据库。每个会话由 conversation_id 唯一标识该组件负责根据此 ID 进行存取操作框架会自动完成消息的序列化与反序列化。MessageWindowChatMemory这是 Spring AI 当前推荐的核心记忆实现负责维护一个固定大小的消息窗口可通过 maxMessages 配置。当消息数量超过上限时自动移除旧消息但会保留系统消息SystemMessage。在 JDBC 持久化场景下ChatMemory Bean 注入 JdbcChatMemoryRepository 即可实现底层存储切换上层的滑动窗口策略完全不变。MessageChatMemoryAdvisor作为拦截器层Advisor 负责在每次请求中自动注入对话历史从 ChatMemory 中通过 conversation_id 加载并在收到模型回复后将新的消息写回存储。建表脚本与自动初始化框架约定的表名为 SPRING_AI_CHAT_MEMORY需要包含以下列列名类型说明idBIGINT主键自增conversation_idVARCHAR(36)会话唯一标识contentTEXT消息内容JSON 序列化typeVARCHAR(10)消息类型USER / ASSISTANT / SYSTEM / TOOLtimestampTIMESTAMP消息时间戳框架支持通过配置自动执行建表脚本。spring.ai.chat.memory.repository.jdbc.initialize-schema 的可选值包括 always每次启动都建表、embedded仅内嵌数据库自动建表、never不自动建表。开发环境建议使用 always生产环境建议使用 never由 DBA 手动管理表结构。Spring AI 内置了针对各种数据库的建表 SQL存放在 org/springframework/ai/chat/memory/jdbc/sql/ 目录下框架会根据检测到的数据源类型自动选择对应的脚本。也可以通过 schema 属性指定自定义脚本路径。架构对比维度InMemoryJDBC 持久化数据生命周期随 JVM 重启丢失持久化到数据库重启不丢跨实例共享不支持支持多实例共享同一数据库内存压力长期运行占用堆内存数据库承担存储压力审计与分析不支持可直接查询数据库配置复杂度零配置需配置数据源引入 JDBC 依赖注博客https://blog.csdn.net/badao_liumang_qizhi实现MySQL 环境准备-- 创建数据库 CREATE DATABASE IF NOT EXISTS spring_ai DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;pom.xml在之前依赖的基础上移除 InMemoryChatMemoryRepository 的手动配置依赖新增 JDBC Starter 和 MySQL 驱动properties java.version17/java.version spring-ai.version1.1.2/spring-ai.version /properties !-- 新增使用 Spring AI BOM 统一管理所有模块版本 -- dependencyManagement dependencies dependency groupIdorg.springframework.ai/groupId artifactIdspring-ai-bom/artifactId version${spring-ai.version}/version typepom/type scopeimport/scope /dependency /dependencies /dependencyManagement dependencies dependency groupIdorg.springframework.boot/groupId artifactIdspring-boot-starter-web/artifactId /dependency !-- Spring AI Ollama 核心 -- dependency groupIdorg.springframework.ai/groupId artifactIdspring-ai-starter-model-ollama/artifactId /dependency !-- Spring AI JDBC 聊天记忆持久化 -- dependency groupIdorg.springframework.ai/groupId artifactIdspring-ai-starter-model-chat-memory-repository-jdbc/artifactId /dependency !-- MySQL 驱动 -- dependency groupIdcom.mysql/groupId artifactIdmysql-connector-j/artifactId scoperuntime/scope /dependency /dependencies依赖变化说明新增 spring-ai-starter-model-chat-memory-repository-jdbc提供 JdbcChatMemoryRepository 的自动配置包含 spring-ai-autoconfigure-model-chat-memory 和 JDBC 存储实现两个模块。新增 mysql-connector-jMySQL 的 JDBC 驱动推荐使用新 artifactId mysql-connector-j 而非旧的 mysql-connector-java。移除 InMemoryChatMemoryRepository 的显式 Bean 声明框架在检测到 JdbcChatMemoryRepository 时会自动将默认的 InMemoryChatMemoryRepository 替换为 JDBC 实现不再需要手动声明。建表脚本创建 src/main/resources/sql/schema-mysql.sqlCREATE TABLE IF NOT EXISTS SPRING_AI_CHAT_MEMORY ( id BIGINT NOT NULL AUTO_INCREMENT, conversation_id VARCHAR(36) NOT NULL, content TEXT NOT NULL, type VARCHAR(10) NOT NULL, timestamp TIMESTAMP NOT NULL, PRIMARY KEY (id), INDEX idx_conv_ts (conversation_id, timestamp), CONSTRAINT type_check CHECK (type IN (USER, ASSISTANT, SYSTEM, TOOL)) );application.yml server: port: 886 spring: datasource: driver-class-name: com.mysql.cj.jdbc.Driver url:jdbc:mysql://localhost:3306/spring_ai?serverTimezoneAsia/ShanghaiuseUnicodetruecharacterEncodingutf-8 username: root password: 123456 # 替换为实际密码 ai: ollama: base-url: http://localhost:11434 chat: model: qwen2.5:7b-instruct options: temperature: 0.7 num-ctx: 4096 # Ollama 上下文窗口大小 chat: memory: repository: jdbc: initialize-schema: always # 开发环境每次启动都执行建表脚本 schema: classpath:sql/schema-mysql.sql # 自定义建表脚本路径 logging: level: org.springframework.ai.chat.client.advisor: DEBUG org.springframework.jdbc: DEBUG # 可选查看 SQL 执行日志 配置重点解析spring.datasource.*标准的 Spring Boot 数据源配置连接 MySQL 数据库。spring.ai.chat.memory.repository.jdbc.initialize-schema: always每次启动时执行建表脚本开发环境推荐。spring.ai.chat.memory.repository.jdbc.schema指定自定义 SQL 脚本的路径覆盖框架内置的建表脚本。注意如果未指定 schema 属性框架会自动从 org/springframework/ai/chat/memory/jdbc/sql/ 加载内置的建表脚本支持 schema-mysql.sql、schema-postgresql.sql 等。ChatConfig 配置类关键变化不再手动创建 InMemoryChatMemoryRepository改为注入由 Starter 自动提供的 JdbcChatMemoryRepository。package com.badao.ai.config; import org.springframework.ai.chat.client.ChatClient; import org.springframework.ai.chat.client.advisor.MessageChatMemoryAdvisor; import org.springframework.ai.chat.memory.ChatMemory; import org.springframework.ai.chat.memory.MessageWindowChatMemory; import org.springframework.ai.chat.memory.repository.jdbc.JdbcChatMemoryRepository; import org.springframework.ai.chat.model.ChatModel; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; Configuration public class ChatConfig { Bean public ChatMemory chatMemory(JdbcChatMemoryRepository jdbcChatMemoryRepository) { // 只需将 Repository 替换为 JDBC 实现窗口策略完全不变 return MessageWindowChatMemory.builder() .chatMemoryRepository(jdbcChatMemoryRepository) .maxMessages(20) // 每次最多携带 20 条历史消息 .build(); } Bean public ChatClient chatClient(ChatModel chatModel, ChatMemory chatMemory) { return ChatClient.builder(chatModel) .defaultAdvisors( MessageChatMemoryAdvisor.builder(chatMemory).build() ) .build(); } }改造对比从 InMemory 到 JDBC只需要在 chatMemory Bean 中将构造参数从 new InMemoryChatMemoryRepository() 替换为注入 JdbcChatMemoryRepository其余代码和业务逻辑全部不变。MemoryChatController 控制器package com.badao.ai.controller; import org.springframework.ai.chat.client.ChatClient; import org.springframework.ai.chat.memory.ChatMemory; // 导入 ChatMemory 接口 import org.springframework.web.bind.annotation.*; RestController RequestMapping(/api) public class MemoryChatController { private final ChatClient chatClient; public MemoryChatController(ChatClient chatClient) { this.chatClient chatClient; } PostMapping(/chat/memory) public ChatResponse chatWithMemory(RequestBody MemoryChatRequest request) { String result chatClient.prompt() .user(request.message()) .advisors(advisor - advisor.param( ChatMemory.CONVERSATION_ID, request.conversationId() )) .call() .content(); return new ChatResponse(200, success, result); } public record MemoryChatRequest(String message, String conversationId) {} public record ChatResponse(int code, String msg, String data) {} }测试验证原理深入1、Spring Boot 自动配置如何发现 JdbcChatMemory 并替换 InMemory整个过程由 Spring Boot 的自动配置机制驱动依赖触发在 pom.xml 中引入 spring-ai-starter-model-chat-memory-repository-jdbc 后JdbcChatMemoryRepository 类被加入 Classpath。条件装配框架中的 ChatMemoryAutoConfiguration 会检测 Classpath 中是否存在 JdbcChatMemoryRepository。如果存在且已配置 DataSource则自动创建一个 JdbcChatMemoryRepository Bean。Bean 覆盖由于存在 JdbcChatMemoryRepository Bean默认的 InMemoryChatMemoryRepository 自动配置会失效被条件装配注解跳过ChatMemory Bean 直接注入 JdbcChatMemoryRepository从而实现存储介质的无缝切换。整个过程无需任何 ConditionalOnMissingBean 的手动干预框架已内置完整的条件装配逻辑。2、JdbcChatMemoryRepository 如何将消息写入 MySQLJdbcChatMemoryRepository 的核心实现基于 Spring 的 JdbcTemplate它直接操作 SPRING_AI_CHAT_MEMORY 表。一条消息的写入过程如下MessageChatMemoryAdvisor 在 AI 返回响应后调用 ChatMemory.add(conversationId, messages)。MessageWindowChatMemory 收到新消息后先合并窗口中已有的消息再将合并后的完整消息列表通过 JdbcChatMemoryRepository.saveAll(conversationId, messages) 写回数据库。JdbcChatMemoryRepository.saveAll() 内部采用先删后插策略先 DELETE 该会话的所有旧消息再 INSERT 全部新消息。这种“全量替换”策略的好处是避免并发场景下的增量同步困难同时配合自增主键保证原子性。查询消息时findByConversationId() 会按 timestamp 升序排列所有消息再由 MessageWindowChatMemory 截取最近 maxMessages 条注入到 Prompt 中。3、数据库表结构与索引设计框架约定的表结构如下CREATE TABLE IF NOT EXISTS SPRING_AI_CHAT_MEMORY ( id BIGINT NOT NULL AUTO_INCREMENT, conversation_id VARCHAR(36) NOT NULL, content TEXT NOT NULL, type VARCHAR(10) NOT NULL, timestamp TIMESTAMP NOT NULL, PRIMARY KEY (id), INDEX idx_conv_ts (conversation_id, timestamp), CONSTRAINT type_check CHECK (type IN (USER, ASSISTANT, SYSTEM, TOOL)) );索引设计分析联合索引 idx_conv_ts (conversation_id, timestamp) 覆盖了 findByConversationId() 的查询模式WHERE conversation_id ? ORDER BY timestamp形成覆盖索引查询性能极高无需回表。时间类型选择说明TIMESTAMP 类型能自动记录消息写入时的精确时间相比 DATETIME 更紧凑4 字节 vs 8 字节且受数据库时区设置影响适合追踪消息时序。序列化格式说明content 列存储的是消息的 JSON 序列化字符串。Spring AI 使用 Jackson 将 Message 对象序列化为 JSON包含消息文本、元数据如 Token 使用量、模型名称等完整信息直接以 TEXT 类型存储。4、滑动窗口如何与数据库配合MessageWindowChatMemory 在 get() 时只从数据库中取最近 maxMessages 条消息而非全部加载。SQL 查询层面按 timestamp 升序排列再在 Java 层截断。这保证了即使某次对话积累了成千上万条消息每次调用 AI 时仍然只携带可控数量的上下文。数据库中的旧消息不会被删除可随时通过直接查询表来获取完整的对话历史。5、ChatMemory 配置最佳实践配置项说明生产环境推荐值maxMessages记忆窗口大小10-20 条视模型上下文窗口而定initialize-schema建表策略never由 DBA 通过 Flyway/Liquibase 管理schema自定义脚本路径classpath:sql/schema-mysql.sql版本化管理