跳到主要内容
Java AI java
Spring AI 使用 MySQL 持久化 ChatMemory 实战 Spring AI 的 ChatMemory 组件默认使用内存存储,存在应用重启数据丢失及无法跨实例共享的问题。解决方案是将 ChatMemory 持久化至 MySQL 数据库。主要内容包括依赖引入、配置详解、代码改造(仅需修改 Bean)、自动配置机制、表结构分析、工作流程及生产环境最佳实践。通过切换 JdbcChatMemoryRepository 实现平滑迁移,支持分布式共享与数据分析,满足合规性要求。
清心 发布于 2026/2/26 更新于 2026/6/2 25 浏览引言
在构建 AI 对话应用时,对话历史(Chat Memory)的管理至关重要。Spring AI 提供的 ChatMemory 组件能够帮助开发者轻松实现多轮对话能力,让大模型能够记住之前的对话内容,进而提供更连贯、更具上下文感知的回复。
然而,默认的内存存储(InMemoryChatMemoryRepository)存在明显的局限性:应用重启时数据丢失,无法跨实例共享,难以应对生产环境需求。这正是本篇文章的核心议题——如何将 ChatMemory 持久化到 MySQL 数据库中。
通过这篇文章,你将学会:
理解为什么需要持久化 ChatMemory
掌握 Spring AI JDBC ChatMemoryRepository 的工作原理
学习从 InMemory 到 JDBC 的平滑迁移过程
深入理解 Spring Boot 自动配置的魔力
了解数据库表结构的设计
一、为什么需要持久化 ChatMemory
1.1 内存存储的局限性
让我们先回顾一下之前使用的 InMemory 存储方式:
@Bean
public ChatMemory chatMemory () {
InMemoryChatMemoryRepository inMemoryChatMemoryRepository = new InMemoryChatMemoryRepository ();
return MessageWindowChatMemory.builder().chatMemoryRepository(inMemoryChatMemoryRepository).build();
}
看似简洁的代码,却隐藏着几个严重问题:
问题 1:应用重启数据丢失
所有对话历史存储在 JVM 内存中
应用停止运行,所有对话记录立即消失
用户无法恢复之前的对话上下文
在部署新版本、扩容缩容等运维操作时,用户体验中断
问题 2:无法跨实例共享
在微服务或负载均衡场景中,多个应用实例各自维护独立的内存
用户 A 的对话数据存在实例 1 中,但路由到实例 2 时,就无法访问该数据
导致多轮对话中断
特别是在 Kubernetes 环境中,Pod 重启是常见操作,内存数据会完全丢失
问题 3:内存压力大
长期运行的应用,对话历史不断积累
JVM 内存占用持续增长,可能导致 OOM 异常
没有自然的过期机制清理旧数据
每个用户的对话都存储在内存中,数百个并发用户会消耗大量堆内存
问题 4:无法进行数据分析
对话记录无法持久化,无法进行用户行为分析
无法构建对话数据仓库
难以优化模型和提升服务质量
无法进行 A/B 测试、用户路径分析等数据驱动的决策
问题 5:缺乏审计能力
在医疗、金融等受监管行业,必须保留完整的审计日志
内存存储无法满足合规性要求
无法追溯用户操作历史和系统决策过程
1.2 持久化的优势
采用 MySQL 存储后,这些问题迎刃而解:
用户可以在任何时间点恢复之前的对话上下文
支持灾备和数据恢复
分布式共享 :多个应用实例可共享同一数据库中的对话数据
支持水平扩展,轻松应对高并发场景
用户请求可以负载均衡到任意实例,不影响对话连续性
数据分析 :积累长期数据,支持用户行为分析和模型优化
通过分析用户对话数据优化模型 prompt
识别用户常见问题,优化知识库和 FAQ
量化产品改进的效果
合规性 :满足审计日志和数据留存的合规性要求
支持医疗、金融等受监管行业的法律要求
完整的审计追溯链
支持数据导出和隐私保护
二、依赖引入
2.1 Maven 依赖配置
<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 >
spring-ai-starter-model-chat-memory-repository-jdbc:Spring AI 提供的 JDBC 实现,包含自动配置和表初始化逻辑
mysql-connector-java 8.0.33:MySQL Java 驱动程序,确保与数据库的连接
这两个依赖是实现 JDBC ChatMemory 的最小必要配置。
三、配置详解
3.1 数据源配置 在 application.yml 中配置数据源连接:
spring:
datasource:
url: jdbc:mysql://127.0.0.1:3306/my_db?useUnicode=true&characterEncoding=utf-8&zeroDateTimeBehavior=convertToNull&transformedBitIsBoolean=true&allowMultiQueries=true&allowPublicKeyRetrieval=true&useSSL=false&serverTimezone=Asia/Shanghai
username: root
password: artisan123456
参数 说明 useUnicode=true使用 Unicode 编码,确保中文正常存储 characterEncoding=utf-8字符集为 UTF-8,支持中文和其他 Unicode 字符 zeroDateTimeBehavior=convertToNullMySQL 中 0000-00-00 转换为 null,避免异常 transformedBitIsBoolean=trueMySQL BIT 类型映射为 Java boolean allowMultiQueries=true允许多条 SQL 语句一起执行 allowPublicKeyRetrieval=true允许使用公钥检索进行认证 useSSL=false不使用 SSL 连接(开发环境) serverTimezone=Asia/Shanghai设置时区为上海,避免时间错位
3.2 ChatMemory JDBC 配置 spring:
ai:
chat:
memory:
repository:
jdbc:
initialize-schema: always
参数 可选值 说明 initialize-schemaalways / never / create-if-missingalways:每次启动时重建表结构;never:从不初始化;create-if-missing:表不存在时创建
开发环境:使用 always,每次启动都重新初始化,确保表结构最新
测试环境:使用 create-if-missing,只在首次运行时创建
生产环境:使用 never,由 DBA 负责初始化和维护表结构
3.3 完整配置示例 spring:
datasource:
url: jdbc:mysql://127.0.0.1:3306/my_db?useUnicode=true&characterEncoding=utf-8&zeroDateTimeBehavior=convertToNull&transformedBitIsBoolean=true&allowMultiQueries=true&allowPublicKeyRetrieval=true&useSSL=false&serverTimezone=Asia/Shanghai
username: root
password: artisan123456
ai:
openai:
base-url: https://dashscope.aliyuncs.com/compatible-mode
api-key: ${DASHSCOPE_API_KEY}
chat:
options:
model: qwen3-max
memory:
repository:
jdbc:
initialize-schema: always
server:
port: 8081
四、代码改造
4.1 只需改一个 Bean Spring AI 的优秀架构设计体现在这里:从 InMemory 切换到 JDBC,只需修改一个 Bean 定义 ,无需改动任何业务代码。
@Bean
public ChatMemory chatMemory () {
InMemoryChatMemoryRepository inMemoryChatMemoryRepository = new InMemoryChatMemoryRepository ();
return MessageWindowChatMemory.builder().chatMemoryRepository(inMemoryChatMemoryRepository).build();
}
@Bean
public ChatMemory chatMemory (JdbcChatMemoryRepository chatMemoryRepository) {
return MessageWindowChatMemory.builder().chatMemoryRepository(chatMemoryRepository).build();
}
删除 InMemoryChatMemoryRepository inMemoryChatMemoryRepository = new InMemoryChatMemoryRepository(); 这一行
在方法参数中添加 JdbcChatMemoryRepository chatMemoryRepository,让 Spring 自动注入
这正是依赖注入和接口抽象的威力——ChatMemoryRepository 是抽象接口,具体实现可以自由切换,业务代码完全不受影响。
这体现了 SOLID 设计原则中的依赖倒置原则(DIP):高层模块(业务层)依赖于抽象接口(ChatMemoryRepository),而不是依赖于具体实现。这样做的好处是:
灵活性 :可以轻松切换不同的存储实现(InMemory、JDBC、Redis 等)
可测试性 :单元测试时可以注入 mock 实现
可维护性 :存储实现变化不影响业务代码
可扩展性 :未来可以添加新的存储实现而无需修改现有代码
4.2 无缝使用 业务层代码完全无需改动。例如,在 Controller 中使用 ChatMemory:
@Autowired
private ChatMemory chatMemory;
@GetMapping("/memory")
public String memory (@RequestParam("chatId") String chatId, @RequestParam("question") String question) {
return chatClient
.prompt()
.advisors(MessageChatMemoryAdvisor.builder(chatMemory).build())
.advisors(advisorSpec -> advisorSpec.params(Map.of(ChatMemory.CONVERSATION_ID, chatId)))
.user(question)
.call()
.content();
}
这段代码在 InMemory 和 JDBC 之间切换时,一行都不需要改 。这就是良好的抽象设计的价值所在。
4.3 迁移影响分析
InMemory:立即可用,无任何初始化
JDBC:数据库必须存在,initialize-schema: always 时会自动创建表结构
InMemory:旧数据完全丢失,数据库中是空的
JDBC:需要考虑是否需要导入旧数据(通常需要一次性数据迁移)
InMemory:本地访问,纳秒级延迟,无网络开销
JDBC:涉及数据库往返,毫秒级延迟,需要网络连接
在高并发场景下,JDBC 的性能可能略低,但可以通过连接池、缓存等手段优化
InMemory:应用 crash 则数据丢失,但数据库无故障点
JDBC:数据库故障会导致对话功能不可用,需要实现数据库故障处理和降级方案
五、自动配置机制
5.1 Spring Boot 自动配置如何发现并注入 JdbcChatMemoryRepository 当我们在 pom.xml 中添加 spring-ai-starter-model-chat-memory-repository-jdbc 依赖后,Spring Boot 的自动配置机制会自动发现并应用相关配置。
Spring Boot 启动时扫描 classpath 下所有 jar 包中的 META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports 文件
spring-ai-starter-model-chat-memory-repository-jdbc 包含该文件,指向 JDBC ChatMemory 的自动配置类
自动配置类(如 JdbcChatMemoryRepositoryAutoConfiguration)被 Spring 加载
该类包含条件判断,确保存在必要的配置和依赖
@Configuration
@ConditionalOnClass(JdbcChatMemoryRepository.class)
@ConditionalOnProperty(
prefix = "spring.ai.chat.memory.repository.jdbc",
name = "initialize-schema",
havingValue = "always|create-if-missing|never"
)
public class JdbcChatMemoryRepositoryAutoConfiguration {
}
@ConditionalOnClass:classpath 中必须存在 JdbcChatMemoryRepository 类
@ConditionalOnProperty:application.yml 中必须配置 spring.ai.chat.memory.repository.jdbc.initialize-schema
第四步:Bean 创建
当所有条件满足时,自动配置类创建以下 Bean:
@Bean
@ConditionalOnMissingBean
public JdbcChatMemoryRepository chatMemoryRepository (JdbcOperations jdbcOperations, JdbcChatMemoryRepositoryProperties properties) {
return new JdbcChatMemoryRepository (jdbcOperations, properties);
}
JdbcOperations:Spring 提供的 JDBC 操作工具
JdbcChatMemoryRepositoryProperties:从配置文件读取的属性
@Bean
public ChatMemory chatMemory (JdbcChatMemoryRepository chatMemoryRepository) {
}
Spring 检测到参数 JdbcChatMemoryRepository,自动注入刚才创建的 Bean。
5.2 自动配置的优势
零配置 :不需要额外的 @Configuration 类或 @Bean 方法来创建 JdbcChatMemoryRepository
约定优于配置 :遵循命名约定和配置前缀,自动完成配置
条件装配 :只有当依赖和配置都存在时才自动装配
易于覆盖 :定义同类型的 Bean 可以覆盖自动配置
六、数据库表结构分析
6.1 自动建表机制 当应用启动且 initialize-schema: always 时,Spring AI 会自动执行初始化脚本。让我们看看生成的表结构:
CREATE TABLE message_store (
id BIGINT AUTO_INCREMENT PRIMARY KEY ,
conversation_id VARCHAR (255 ) NOT NULL ,
message_type VARCHAR (50 ) NOT NULL ,
content LONGTEXT NOT NULL ,
timestamp BIGINT NOT NULL ,
INDEX idx_conversation_id (conversation_id),
INDEX idx_timestamp (timestamp )
) ENGINE= InnoDB DEFAULT CHARSET= utf8mb4 COLLATE = utf8mb4_unicode_ci;
InnoDB 引擎 :提供 ACID 事务支持,确保数据一致性
utf8mb4 字符集 :完全支持 Unicode,包括表情符号等扩展字符
自增主键 :保证消息的全局唯一性和顺序性
索引策略 :在高频查询字段上建立索引,提高查询性能
6.2 表结构字段详解 字段名 类型 说明 索引 idBIGINT AUTO_INCREMENT PRIMARY KEY 消息全局唯一标识,自增主键 是 conversation_idVARCHAR(255) NOT NULL 对话会话 ID,用于隔离不同用户/会话的对话记录 是 message_typeVARCHAR(50) NOT NULL 消息类型:USER(用户消息)或 ASSISTANT(模型回复) 否 contentLONGTEXT NOT NULL 消息内容,支持长文本 否 timestampBIGINT NOT NULL 消息时间戳,毫秒级精度 是
id 字段 :使用 BIGINT 而非 INT,预留充足空间。假设每秒 1000 条消息,10 年可容纳约 3.1 亿条记录,远未达到 BIGINT 上限
conversation_id :VARCHAR(255) 足够存储 UUID 或其他常见的会话 ID 格式
message_type :枚举类型,只有两个值 USER 和 ASSISTANT,但使用 VARCHAR 便于 future 扩展(如 SYSTEM 消息)
content :使用 LONGTEXT 支持长对话内容,理论上可存储 4GB 的文本
timestamp :BIGINT 毫秒级精度,足以应对高频事件和时间排序需求
6.3 索引策略分析 idx_conversation_id 索引的作用:
加速按 conversation_id 的查询
业务逻辑中最频繁的查询就是'获取某个会话的所有消息'
通过该索引,数据库可以快速定位到属于某个会话的所有行
支持按时间范围查询
便于实现消息老化和清理策略
支持分析最近 N 小时/天的对话趋势
content 字段类型是 LONGTEXT,建立索引会占用大量存储空间
业务逻辑中不会按 content 内容进行查询,只是读取展示
建立索引带来的收益远小于成本
6.4 表结构的存储特性 // 数据流动过程
用户消息 → MessageWindowChatMemory → JdbcChatMemoryRepository
↓ INSERT INTO message_store (conversation_id, message_type, content, timestamp) VALUES (?,?,?,?)
// 读取过程
按 conversation_id 查询 → SELECT * FROM message_store WHERE conversation_id = ? ORDER BY timestamp DESC LIMIT ?
Spring AI 的 JdbcChatMemoryRepository 会自动处理以下逻辑:
消息保存 :每条消息(用户消息和 AI 回复都是独立的消息)都会 INSERT 一次
消息查询 :按 conversation_id 和时间戳排序获取消息
消息去重 :Spring AI 内部会处理重复消息的情况
批量操作 :在高并发情况下,可能会进行批量插入优化
6.5 真实数据示例 假设用户 chatId 为"user123"进行对话,表中的数据可能是这样的:
id conversation_id message_type content timestamp 1 user123 USER 你好,请介绍一下 Spring AI 1700000000000 2 user123 ASSISTANT 你好!Spring AI 是 Spring 框架的 AI 扩展…(完整回复) 1700000001000 3 user123 USER 如何使用 ChatMemory 实现多轮对话? 1700000002000 4 user123 ASSISTANT 在 Spring AI 中,ChatMemory 用于存储…(完整回复) 1700000003000
通过按 conversation_id 分组,可以轻松实现多个独立的对话会话。
表中数据量可能快速增长,随着时间推移会达到百万级甚至千万级
需要定期执行 ANALYZE TABLE 以更新统计信息
考虑实现数据分区策略(按日期分区)以提高查询性能
考虑定期归档或删除超过保留期的消息
七、工作流程详解
7.1 消息存储流程 用户请求 → ChatClient.prompt()
↓ MessageChatMemoryAdvisor 检测到使用 ChatMemory
↓ 加载 conversation_id 对应的历史消息
↓ 组装系统消息 + 历史消息 + 用户新消息
↓ 调用大模型获取回复
↓ 回复内容返回给用户
↓ MessageChatMemoryAdvisor 保存用户消息和 AI 回复到数据库
↓ JdbcChatMemoryRepository.add() 执行 INSERT 操作
消息加载阶段 :MessageChatMemoryAdvisor 会检测是否指定了 conversation_id,如果有则从数据库加载该会话的历史消息
消息组装阶段 :将系统提示词、历史消息和新的用户消息组装成完整的消息列表
大模型调用 :将组装后的消息列表传递给 OpenAI/Qwen 等大模型 API
结果处理 :获取大模型的回复文本
持久化阶段 :将用户消息和 AI 回复分别作为两条独立的记录存储到数据库
如下时序图描绘了 Spring AI 结合 JdbcChatMemoryRepository 实现的 持久化记忆管理 全链路。它展示了消息如何从内存流转到关系型数据库(如 MySQL/PostgreSQL)。
大模型 (Qwen) 数据库 (JDBC) JdbcChatMemoryRepository MessageChatMemoryAdvisor ChatClient
1. 对话开始
2. 读取持久化记忆
3. 上下文增强与推理
4. 响应分发
5. 记忆持久化 (Post-Process)
记忆保存完成
prompt(question)
1 getMessages(conversationId)
2 SELECT * FROM chat_memory WHERE ...
3 返回历史消息记录
4 转换为 List<Message>
5 组装 [System + History + User]
6 发送完整 Context
7 返回 AI 回复内容
8 返回结果给用户
9 add(conversationId, userMsg)
10 add(conversationId, aiMsg)
11 INSERT INTO chat_memory ... (用户消息)
12 INSERT INTO chat_memory ... (AI 回复)
13
关键环节深度解析
JDBC 抽象层 :JdbcChatMemoryRepository 是 Spring AI 提供的一个标准实现。它利用 JdbcTemplate 将对话对象序列化为数据库行。默认情况下,它通常包含 chat_id、message_type(USER/ASSISTANT)和 content 等字段。
读取时机 (Step 2-5) :这是典型的'惰性加载'。只有当 ChatClient 被触发时,Advisor 才会去数据库捞取历史。这保证了即使应用重启,用户的对话上下文依然存在。
写入时机 (Step 9-12) :注意保存操作发生在 AI 回复之后 。这是一个严谨的设计:如果 AI 调用失败(例如网络超时),则这一轮错误的对话不会被记入数据库,从而避免了'污染'历史记忆。
会话隔离 :通过 conversation_id(通常由前端传入或从 Session 获取),系统可以同时处理成千上万个并发用户的独立记忆,互不干扰。
进阶优化方案 在高性能场景下,频繁的 SELECT 和 INSERT 可能会成为瓶颈。你是否考虑过:
增加二级缓存 :在 JDBC 之上叠加一个本地缓存(如 Caffeine),减少对数据库的轮询。
异步写入 :将 add() 操作放入异步线程池,不阻塞用户的响应时间。
7.2 消息读取流程 应用启动时或新会话开始
↓ MessageChatMemoryAdvisor 收到请求
↓ 调用 ChatMemory.getMessages(conversationId)
↓ JdbcChatMemoryRepository.query()
↓ 执行 SELECT * FROM message_store WHERE conversation_id = ? ORDER BY timestamp
↓ 将结果转换为 Message 对象列表
↓ MessageWindowChatMemory 根据滑动窗口策略筛选消息
↓ 返回最近 N 条消息供模型使用
窗口策略 :MessageWindowChatMemory 默认返回最近 N 条消息(通常是 10 条),而不是全部历史消息
消息转换 :数据库中的行记录被转换为 Spring AI 的 Message 对象,包括 UserMessage 和 AssistantMessage 两种类型
性能优化 :由于建立了 idx_conversation_id 索引,数据库查询非常高效,即使有数百万条消息也能快速检索
下面的时序图展示了 Spring AI 中持久化存储 与滑动窗口策略 相结合的精细化记忆加载流程。它解释了系统如何在海量历史数据中,既保证'记得住'(JDBC 持久化),又保证'不超限'(滑动窗口筛选)。
数据库 (MySQL/PG) JdbcChatMemoryRepository MessageWindowChatMemory (装饰器) MessageChatMemoryAdvisor
1. 触发记忆检索
2. 全量/增量从库读取
3. 执行滑动窗口策略 (Memory Pruning)
丢弃过旧的 Context,防止 Token 溢出
4. 返回精简后的上下文
5. 注入 Prompt 并发送给模型
getMessages(conversationId)
1 getMessages(conversationId)
2 SELECT * FROM message_store WHERE id = ? ORDER BY ts
3 返回所有历史行 (ResultSet)
4 转换为 List<Message> (全量历史)
5 筛选最近 N 条消息 (e.g., Last 10)
6 返回 List<Message> (Size <= N)
7
核心机制分析
1. 职责分层 (Layered Responsibility)
JdbcChatMemoryRepository :只负责'搬运'。它不关心消息有多少,只负责把数据库里的数据变成 Java 对象。
**MessageWindowChatMemory**:负责'剪裁'。它作为包装层,根据配置的 capacity(容量)对原始数据进行切片。
2. 滑动窗口的必要性 LLM 的上下文窗口(Context Window)是有限的(如 128k tokens)。如果不做筛选:
成本剧增 :每次对话都会带上从第一天开始的所有记录,Token 消耗呈指数级增长。
模型幻觉 :过长且无关的旧背景会干扰模型对当前问题的判断。
3. 性能小贴士 在第 5 步的 SELECT 语句中,如果对话历史达到数万条,全量加载到内存再进行 Window 筛选会变得非常缓慢。
优化建议 :在生产环境中,通常会直接在 SQL 层面通过 LIMIT 和 ORDER BY DESC 来实现物理层面的窗口筛选,例如:
SELECT * FROM message_store WHERE conversation_id = ? ORDER BY timestamp DESC LIMIT 20
下一步建议 这种结构非常稳健。当对话非常长 ,但又不能简单丢弃旧信息时,如何通过 Vector Database (RAG) 来实现'语义搜索式'的记忆检索,而不是简单的'最近 N 条'
7.3 核心代码调用链
chatClient.prompt()
.advisors(MessageChatMemoryAdvisor.builder(chatMemory).build())
.advisors(a -> a.param(ChatMemory.CONVERSATION_ID, "user123" ))
.user(question)
.call()
List<Message> messages = chatMemory.getMessages("user123" );
messages.add(newUserMessage(question));
{
"model" : "qwen3-max" ,
"messages" : [
{"role" : "user" , "content" : "...上一轮问题..." },
{"role" : "assistant" , "content" : "...上一轮回复..." },
{"role" : "user" , "content" : "...新问题..." }
]
}
chatMemory.add("user123" , response);
7.4 并发场景下的消息处理 在高并发环境中,多个用户同时发送消息时,Spring AI 的处理方式:
chatClient.prompt()
.advisors(a -> a.param(ChatMemory.CONVERSATION_ID, "userA" ))
.user("问题 A" )
.call()
chatClient.prompt()
.advisors(a -> a.param(ChatMemory.CONVERSATION_ID, "userB" ))
.user("问题 B" )
.call()
两个 INSERT 操作分别向表中插入两条不同 conversation_id 的记录
由于有 idx_conversation_id 索引,即使表很大也能快速定位
InnoDB 的行级锁确保操作的原子性和一致性
八、关键实现细节
8.1 MessageWindowChatMemory 的窗口机制 MessageWindowChatMemory 并非简单地返回所有历史消息,而是通过滑动窗口策略来控制消息数量:
MessageWindowChatMemory.builder()
.chatMemoryRepository(chatMemoryRepository)
.windowSize(10 )
.build();
成本优化 :减少发送给 LLM 的 tokens,降低 API 调用成本
上下文相关性 :只保留最近的对话,避免很久以前的消息影响当前对话
8.2 Conversation ID 隔离 通过 conversation_id 实现会话隔离:
chatClient.prompt()
.advisors(a -> a.param(ChatMemory.CONVERSATION_ID, "userA" ))
.user("你好" )
.call();
chatClient.prompt()
.advisors(a -> a.param(ChatMemory.CONVERSATION_ID, "userB" ))
.user("你好" )
.call();
两个用户的消息存储在同一个表中,但通过 conversation_id 完全隔离,不会相互干扰。
8.3 自动初始化表结构 initialize-schema: always 的背后:
@Bean
public DatabasePopulator databasePopulator () {
ResourceDatabasePopulator populator = new ResourceDatabasePopulator ();
populator.addScript(new ClassPathResource ("schema-h2.sql" ));
return populator;
}
Spring AI 根据配置的数据库类型加载对应的初始化脚本(如 schema-mysql.sql),在应用启动时自动执行。
九、生产环境最佳实践
9.1 数据源配置 spring:
datasource:
url: jdbc:mysql://db-server:3306/my_db?useUnicode=true&characterEncoding=utf-8&serverTimezone=Asia/Shanghai
username: ${DB_USERNAME}
password: ${DB_PASSWORD}
hikari:
maximum-pool-size: 20
minimum-idle: 5
connection-timeout: 30000
idle-timeout: 600000
max-lifetime: 1800000
9.2 initialize-schema 策略
spring:
ai:
chat:
memory:
repository:
jdbc:
initialize-schema: always
spring:
ai:
chat:
memory:
repository:
jdbc:
initialize-schema: never
9.3 定期备份和清理
BACKUP TABLE message_store TO '/backup/message_store_backup.sql' ;
DELETE FROM message_store WHERE timestamp < UNIX_TIMESTAMP() * 1000 - 7 * 24 * 60 * 60 * 1000 ;
ANALYZE TABLE message_store;
9.4 监控和告警
SELECT table_name, ROUND(((data_length + index_length) / 1024 / 1024 ), 2 ) AS size_mb
FROM information_schema.tables
WHERE table_schema = 'my_db' AND table_name = 'message_store' ;
SELECT * FROM mysql.general_log
WHERE command_type = 'Query' AND execution_time > 1000 ;
十、troubleshooting 常见问题
问题 1:应用启动时表已存在异常 现象 :启动时报错'Table already exists'
原因 :通常是因为多个应用实例同时启动,都尝试创建表
initialize-schema: create-if-missing
问题 2:连接超时 现象 :SQLException: Connection timeout
检查数据库是否启动:mysql -h 127.0.0.1 -u root -p
检查数据库名是否存在:CREATE DATABASE my_db;
检查 URL 配置是否正确
问题 3:字符编码问题 spring:
datasource:
url: jdbc:mysql://...?useUnicode=true&characterEncoding=utf-8
jpa:
properties:
hibernate:
connection:
CharSet: utf8mb4
collation: utf8mb4_unicode_ci
问题 4:消息丢失 原因 :initialize-schema 设置为 always,导致每次启动都清空表
十一、总结 本文详细阐述了 Spring AI MySQL ChatMemory 的完整实现方案:
核心要点回顾:
为什么持久化 :解决内存存储的局限性,支持数据持久化、跨实例共享和数据分析
如何配置 :只需两步——添加依赖和修改配置文件,Spring Boot 自动完成初始化
平滑迁移 :只需改一个 Bean 定义,体现了 Spring AI 优秀的架构设计和依赖注入的威力
自动配置机制 :Spring Boot 通过条件装配、classpath 扫描等机制,自动发现和初始化 JdbcChatMemoryRepository
表结构设计 :合理的字段设计和索引策略,确保查询性能和数据隔离
生产就绪 :提供了最佳实践、troubleshooting 指南和监控方案
实践建议:
开发阶段 :使用 initialize-schema: always,快速迭代
测试阶段 :改为 create-if-missing,验证数据持久化
生产环境 :设置为 never,由 DBA 负责数据库初始化和维护
从 InMemory 到 JDBC 的演进,不仅是存储介质的改变,更是架构思想的升级——从单机应用到分布式系统的支持,从临时数据到永久化存储的转变。这正是 Spring AI 框架设计的精妙之处。
希望这篇文章能帮助你充分理解 Spring AI ChatMemory 的持久化机制,在实战中灵活运用,构建更健壮、更可靠的 AI 对话应用。
相关免费在线工具 Keycode 信息 查找任何按下的键的javascript键代码、代码、位置和修饰符。 在线工具,Keycode 信息在线工具,online
Escape 与 Native 编解码 JavaScript 字符串转义/反转义;Java 风格 \uXXXX(Native2Ascii)编码与解码。 在线工具,Escape 与 Native 编解码在线工具,online
JavaScript / HTML 格式化 使用 Prettier 在浏览器内格式化 JavaScript 或 HTML 片段。 在线工具,JavaScript / HTML 格式化在线工具,online
JavaScript 压缩与混淆 Terser 压缩、变量名混淆,或 javascript-obfuscator 高强度混淆(体积会增大)。 在线工具,JavaScript 压缩与混淆在线工具,online
RSA密钥对生成器 生成新的随机RSA私钥和公钥pem证书。 在线工具,RSA密钥对生成器在线工具,online
Mermaid 预览与可视化编辑 基于 Mermaid.js 实时预览流程图、时序图等图表,支持源码编辑与即时渲染。 在线工具,Mermaid 预览与可视化编辑在线工具,online