在构建AI对话应用的后端架构时,对话历史(ChatMemory)的持久化是迈向生产环境的关键一步。本文将带你深入Spring AI的JDBC实现,从配置、代码改造到表结构设计,全面解析如何将ChatMemory从内存迁移到MySQL,实现数据不丢失、服务可扩展。

为什么需要持久化ChatMemory?

默认的InMemoryChatMemoryRepository将对话数据存储在JVM内存中,这在微服务和容器化部署场景下存在致命缺陷:

  • 数据随重启丢失:应用重启、Pod重建都会导致所有对话记录消失,用户体验中断。
  • 无法跨实例共享:在负载均衡或Kubernetes集群中,多个后端实例各自维护独立的内存,用户请求路由到不同实例时,对话上下文无法延续。
  • 内存压力与OOM风险:长期运行积累的对话数据会持续占用堆内存,高并发场景下极易触发OutOfMemoryError。
  • 缺乏审计与合规能力:医疗、金融等受监管行业要求完整的审计日志,内存存储无法满足。

采用MySQL数据库持久化后,对话数据永久保存、支持分布式共享,并天然具备数据分析和审计能力,是生产级AI后端架构的必备选择。

三步完成迁移:依赖、配置与代码改造

Spring AI的抽象设计使得从InMemory切换到JDBC异常平滑,只需修改一个Bean定义。

1. 引入Maven依赖

pom.xml中添加以下依赖:

<!-- Spring AI JDBC ChatMemory Repository -->
  <dependency>
  <groupId>org.springframework.ai</groupId>
  <artifactId>spring-ai-starter-model-chat-memory-repository-jdbc</artifactId>
  </dependency>
  <!-- MySQL JDBC驱动 -->
    <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驱动,确保与数据库的连接。

2. 配置数据源与ChatMemory

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...

连接字符串参数含义:

参数说明
使用Unicode编码,确保中文正常存储
字符集为UTF-8,支持中文和其他Unicode字符
MySQL中0000-00-00转换为null,避免异常
MySQL BIT类型映射为Java boolean
允许多条SQL语句一起执行
允许使用公钥检索进行认证
不使用SSL连接(开发环境)
设置时区为上海,避免时间错位

接着配置ChatMemory的JDBC行为:

spring:
ai:
chat:
memory:
repository:
jdbc:
initialize-schema: always

推荐策略:开发环境用always,测试环境用embedded,生产环境用never

3. 代码改造:只需改一个Bean

Before(InMemory):

@Bean
public ChatMemory chatMemory() {
InMemoryChatMemoryRepository inMemoryChatMemoryRepository = new InMemoryChatMemoryRepository();
return MessageWindowChatMemory.builder()
.chatMemoryRepository(inMemoryChatMemoryRepository)
.build();
}

After(JDBC):

@Bean
public ChatMemory chatMemory(JdbcChatMemoryRepository chatMemoryRepository) {
return MessageWindowChatMemory.builder()
.chatMemoryRepository(chatMemoryRepository)
.build();
}

改动仅两处:删除new InMemoryChatMemoryRepository(),在方法参数中添加JdbcChatMemoryRepository repository。业务代码零改动,体现了依赖倒置原则的威力。

自动配置机制:Spring Boot如何发现JdbcChatMemoryRepository?

当添加了spring-ai-starter-model-chat-memory-repository-jdbc依赖后,Spring Boot的自动配置机制自动生效:

  • classpath扫描:扫描spring.factories文件,找到JDBC ChatMemory的自动配置类。
  • 条件判断:自动配置类使用@ConditionalOnClass@ConditionalOnProperty注解,确保只有在classpath存在JdbcChatMemoryRepository且配置了数据源时才生效。
  • Bean创建:自动创建JdbcOperationsJdbcChatMemoryRepositoryProperties等核心Bean。

这套机制实现了零配置、约定优于配置,开发者只需定义业务Bean,Spring自动完成依赖注入。

数据库表结构深度解析

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引擎 + utf8mb4字符集:支持ACID事务和完整Unicode(含表情符号)。
  • 自增主键:保证消息全局唯一性和插入顺序。
  • 双索引策略idx_conversation_id加速按会话查询,idx_timestamp支持时间范围过滤和清理。
  • LONGTEXT字段:content字段可存储高达4GB的对话内容,且不建索引以节省空间。

数据示例:

idconversation_idmessage_typecontenttimestamp
1user123USER你好,请介绍一下Spring AI1700000000000
2user123ASSISTANT你好!Spring AI是Spring框架的AI扩展…(完整回复)1700000001000
3user123USER如何使用ChatMemory实现多轮对话?1700000002000
4user123ASSISTANT在Spring AI中,ChatMemory用于存储…(完整回复)1700000003000

工作流程:消息的存储与读取

存储流程

用户请求 → ChatClient.prompt()MessageChatMemoryAdvisor 检测到使用ChatMemory
↓
加载conversation_id对应的历史消息
↓
组装系统消息 + 历史消息 + 用户新消息
↓
调用大模型获取回复
↓
回复内容返回给用户
↓
MessageChatMemoryAdvisor 保存用户消息和AI回复到数据库
↓
JdbcChatMemoryRepository.add() 执行INSERT操作

核心步骤:

  1. 消息加载:MessageChatMemoryAdvisor根据conversation_id从数据库加载历史消息。
  2. 上下文组装:将系统提示词、历史消息和新用户消息组装成完整列表。
  3. 大模型调用:将组装后的消息传递给API(如OpenAI、Qwen)。
  4. 持久化:将用户消息和AI回复分别作为两条独立记录写入数据库。

注意:保存操作发生在AI回复之后,若AI调用失败则不会写入脏数据。

读取流程

应用启动时或新会话开始
↓
MessageChatMemoryAdvisor 收到请求
↓
调用 ChatMemory.getMessages(conversationId)JdbcChatMemoryRepository.query()
↓
执行 SELECT * FROM message_store
WHERE conversation_id = ? ORDER BY timestamp
↓
将结果转换为Message对象列表
↓
MessageWindowChatMemory 根据滑动窗口策略筛选消息
↓
返回最近N条消息供模型使用

关键机制:

  • 滑动窗口策略:默认返回最近N条消息(通常10条),避免一次性加载海量历史。
  • 消息转换:数据库行记录转换为UserMessage和AssistantMessage对象。
  • 性能优化:借助idx_conversation_id索引,百万级数据也能毫秒级检索。

生产环境最佳实践与常见问题

最佳实践

  • 数据源配置:使用HikariCP连接池,设置合理的maximum-pool-sizeconnection-timeout
  • 表初始化策略:生产环境务必设为never,由DBA通过变更脚本管理表结构。
  • 定期清理:通过定时任务删除超过保留期的历史消息,避免表数据无限膨胀。
  • 监控告警:监控数据库连接池使用率、慢查询和表空间占用。

常见问题

  • ⚠️ 表已存在异常:将initialize-schema设为never,手动执行DDL脚本。
  • 连接超时:检查网络连通性,增加connection-timeout值,使用连接池。
  • 字符编码问题:确保数据库、表、连接字符串均使用utf8mb4
  • 消息丢失:开启事务管理,确保add()操作在事务内执行。

[AFFILIATE_SLOT_1]

总结

本文完整呈现了Spring AI ChatMemory从InMemory迁移到MySQL的实战路径。核心要点:

  • 持久化解决数据丢失、分布式共享、合规审计三大痛点。
  • ✅ 仅需修改一个Bean定义,业务代码零改动。
  • 自动配置机制实现零配置集成,表结构设计遵循数据库最佳实践。
  • ⚠️ 生产环境需关注连接池、表清理和监控告警。

通过将ChatMemory持久化到MySQL,你的AI后端架构将具备企业级的可靠性和可扩展性,从容应对高并发和微服务场景。

[AFFILIATE_SLOT_2]

useUnicode=truecharacterEncoding=utf-8zeroDateTimeBehavior=convertToNulltransformedBitIsBoolean=trueallowMultiQueries=trueallowPublicKeyRetrieval=trueuseSSL=falseserverTimezone=Asia/Shanghai