一、功能概述

Spring AI 的 Chat Memory 用于在多轮对话中保持上下文:每次请求可带上「会话 ID」,框架会按会话加载历史消息、拼入本次请求,并在收到模型回复后把本轮消息写回存储。
若存储使用 JDBC,历史会持久化到关系库,重启或扩容后仍可复用同一会话。

  • 业务价值:多轮对话有记忆、支持按会话隔离、可做审计与分析。
  • 技术要点:无 JPA 实体,表名与列名由框架约定;建表脚本可自定义,运行时 SQL 由 Dialect 固定。

二、快速使用

2.1 依赖

<dependency>
    <groupId>org.springframework.ai</groupId>
    <artifactId>spring-ai-starter-model-chat-memory-repository-jdbc</artifactId>
</dependency>

<!-- 按数据库选驱动,如 MySQL -->
<dependency>
    <groupId>com.mysql</groupId>
    <artifactId>mysql-connector-j</artifactId>
    <scope>runtime</scope>
</dependency>

2.2 配置

数据源(示例使用占位符,请替换为实际环境):

spring:
  datasource:
    driver-class-name: com.mysql.cj.jdbc.Driver
    url: jdbc:mysql://<YOUR_HOST>:3306/<YOUR_DB>?serverTimezone=Asia/Shanghai&useUnicode=true&characterEncoding=utf-8
    username: <YOUR_USERNAME>
    password: <YOUR_PASSWORD>

Chat Memory JDBC(建表脚本与是否执行):

spring:
  ai:
    chat:
      memory:
        repository:
          jdbc:
            initialize-schema: always   # 可选: always | never | embedded
            schema: classpath:sql/schema-mysql.sql   # 自定义建表脚本路径

2.3 建表脚本(约定表名与列名)

表名必须为 SPRING_AI_CHAT_MEMORY,列需包含:conversation_idcontenttypetimestamp。示例(MySQL):

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'))
);

2.4 Java 配置

@Configuration
public class SpringAIConfiguration {

    @Bean
    public ChatClient chatClient(DeepSeekChatModel chatModel, ChatMemory chatMemory) {
        return ChatClient.builder(chatModel)
                .defaultAdvisors(
                        MessageChatMemoryAdvisor.builder(chatMemory).build()
                )
                .build();
    }

    @Bean
    public ChatMemory chatMemory(JdbcChatMemoryRepository chatMemoryRepository) {
        return MessageWindowChatMemory.builder()
                .chatMemoryRepository(chatMemoryRepository)
                .maxMessages(20)
                .build();
    }
}
  • JdbcChatMemoryRepository:由 starter 自动配置,无需手写。
  • MessageWindowChatMemory:对单会话保留最近 N 条(如 20),超出则淘汰旧消息。

2.5 控制器中传入会话 ID

@RestController
@RequestMapping("/ai")
public class ChatController {

    private final ChatClient chatClient;

    @GetMapping("/chat")
    public Flux<String> chat(@RequestParam String prompt,
                              @RequestParam String chatId) {
        return chatClient
                .prompt(prompt)
                .advisors(as -> as.param(ChatMemory.CONVERSATION_ID, chatId))
                .stream()
                .content();
    }
}
  • 同一 chatId 即同一会话,历史从库中按 conversation_id 读取并写回。

三、核心类图与调用链

3.1 类图(简化)

在这里插入图片描述

3.2 自动配置与 DDL 初始化

reads

creates

extends

uses

JdbcChatMemoryRepositoryAutoConfiguration

+jdbcChatMemoryRepository()

+jdbcChatMemoryScriptDatabaseInitializer()

JdbcChatMemoryRepositoryProperties

-CONFIG_PREFIX

-DEFAULT_SCHEMA_LOCATION

+getDefaultSchemaLocation()

JdbcChatMemoryRepositorySchemaInitializer

-extends PropertiesBasedDataSourceScriptDatabaseInitializer

«Spring Boot»

DatabaseInitializationProperties

+schema

+initializeSchema

  • Propertiesspring.ai.chat.memory.repository.jdbc.schema 指定建表脚本路径;未配置时使用默认 schema-@@platform@@.sql
  • SchemaInitializer:启动时根据配置执行该脚本,只负责建表,不参与运行时 SQL。

四、底层实现要点

4.1 无实体类:行 ↔ Message 的映射

  • 框架没有SPRING_AI_CHAT_MEMORY 提供 JPA 实体。
  • ChatMemoryRepository 接口的入参/出参是 List<Message>(Spring AI 的 Message 接口)。
  • JdbcChatMemoryRepository 内部:
    • :用 Dialect 的 getSelectMessagesSql()content, type,再用 MessageRowMapper 根据 type 构造 UserMessage / AssistantMessage / SystemMessage / ToolResponseMessage
    • saveAll 先按 conversation_id 删除,再按 Dialect 的 getInsertMessageSql() 批量插入,参数来自 message.getText()message.getMessageType().name() 和顺序时间戳。

4.2 表名与列名写死在 Dialect 中

  • 各数据库的 Dialect 实现(如 MysqlChatMemoryRepositoryDialect)中,SQL 写死表名 SPRING_AI_CHAT_MEMORY 与列名 conversation_id, content, type, timestamp
  • 因此「任意表结构」仅体现在:你可以自定义建表脚本(索引、约束、字符集等),但表名与这四列必须与 Dialect 一致,否则运行时会查错表或列。

4.3 一次请求的读写流程

  1. 请求进入,带 chatId(即 conversation_id)。
  2. MessageChatMemoryAdvisor 在调用模型前:从 ChatMemory.get(chatId) 取历史 → 实际走到 JdbcChatMemoryRepository.findByConversationId → 执行 SELECT ... FROM SPRING_AI_CHAT_MEMORY WHERE conversation_id = ? ORDER BY timestamp,再映射为 List<Message> 拼入上下文。
  3. 模型返回后,Advisor 将本轮 user/assistant 消息 ChatMemory.add(chatId, messages)MessageWindowChatMemory 先取该会话现有消息,与新课合并并做窗口截断(如保留最近 20 条),再 saveAll(chatId, processedMessages)JdbcChatMemoryRepositoryDELETE ... WHERE conversation_id = ?,再批量 INSERT

五、扩展与自定义

需求 做法
使用自己的表名/列名 实现 JdbcChatMemoryRepositoryDialect,在 SQL 中写自己的表与列;或直接实现 ChatMemoryRepository,用 JdbcTemplate 操作任意表结构。
只自定义建表方式 保留默认 Dialect,仅通过 spring.ai.chat.memory.repository.jdbc.schema 指定自己的 DDL 脚本(表名与四列需一致)。
替换存储介质 实现 ChatMemoryRepository(如 Redis、Mongo),再交给 MessageWindowChatMemory 使用,无需改 ChatClient/Advisor。

六、小结

  • 使用:引入 starter、配置数据源与 JDBC chat memory 的 schema、建好约定表、注入 ChatMemory 并挂上 MessageChatMemoryAdvisor,接口里传入 ChatMemory.CONVERSATION_ID 即可。
  • 实现:表结构由 Dialect 约定(固定表名与四列);无 JPA 实体,读写由 JdbcChatMemoryRepository + MessageRowMapper / 批插完成;建表脚本可自定义,运行时 SQL 不可通过配置修改,只能通过自定义 Dialect 或自定义 Repository 实现「任意表结构」。

以上内容不涉及任何真实服务器 IP、数据库账号密码或 API Key,生产环境请自行替换占位符并做好敏感信息管理。

Logo

有“AI”的1024 = 2048,欢迎大家加入2048 AI社区

更多推荐