一重启 AI 就失忆?用 Spring AI + PostgreSQL 3步搞定“长期记忆”持久化(附完整代码)

上篇文章我们把 spring_ai_chat 跑通了,一个基于 Spring AI 和 DeepSeek 的聊天项目。跑起来之后有个问题:一重启,AI 就什么都不记得了。

内存存储,重启即失。开发时无所谓,真要拿出去用就不太行了。

这篇就是解决这个事:把聊天记录从内存挪到 PostgreSQL 里,让 AI 重启之后还能认出你。


一、先搞清楚:为什么重启会"失忆"?

大模型本身是无状态的------它压根不记得你三秒前问了什么。

所谓的"多轮对话",其实是每次把历史消息打包塞进请求里,让模型看起来"记得"。启动时我们用的是 InMemoryChatMemory,存内存里,一重启自然什么都没了。

Spring AI 对这个事有抽象:ChatMemory 负责"记忆"的读写,ChatMemoryRepository 负责存储。JdbcChatMemoryRepository 就是 JDBC 的实现,支持 PostgreSQL、MySQL 这些数据库。换掉它,问题就解决了。

看懂下面这张工作原理图,你就明白 Spring AI 是怎么实现记忆存储的了:

sequenceDiagram autonumber actor User as 👤 用户 participant Client as 🚀 Spring AI (ChatClient) participant Advisor as 🧠 MemoryAdvisor (拦截器) participant DB as 🐘 PostgreSQL (数据库) participant LLM as 🤖 DeepSeek API (大模型) User->>Client: 发送新消息 (例如: "我刚才说了什么?") Client->>Advisor: 触发请求前置拦截 Advisor->>DB: 1. 查数据库:读取该用户历史聊天记录 DB-->>Advisor: 返回历史消息列表 Advisor->>LLM: 2. 组装 Prompt (历史消息 + 新消息) 发送大模型 LLM-->>Advisor: 3. 返回大模型生成的回答 Advisor->>DB: 4. 存数据库:将本次问答追加写入数据库 Advisor-->>Client: 返回处理后的回答 Client-->>User: 展示回答给用户

图解原理 :Spring AI 的 MessageChatMemoryAdvisor 就像一个贴心的秘书:

  1. 提问前:先去数据库翻出之前的聊天记录,打包一起交给 DeepSeek(大模型才知道上下文)。
  2. 回答后:把你刚才的提问和 DeepSeek 的回答整理好,自动存回 PostgreSQL(保证重启后记录不丢失)。

二、三步改完,跑起来

第一步:加依赖

xml 复制代码
<!-- Spring AI JDBC 记忆存储 Starter -->
<dependency>
    <groupId>org.springframework.ai</groupId>
    <artifactId>spring-ai-starter-model-chat-memory-repository-jdbc</artifactId>
</dependency>

<!-- PostgreSQL 驱动 -->
<dependency>
    <groupId>org.postgresql</groupId>
    <artifactId>postgresql</artifactId>
    <scope>runtime</scope>
</dependency>

第二步:改配置

application.yml 里加数据源和记忆存储配置:

yaml 复制代码
spring:
  datasource:
    url: jdbc:postgresql://localhost:5432/springai
    username: your_username
    password: your_password
    driver-class-name: org.postgresql.Driver

  ai:
    chat:
      memory:
        repository:
          jdbc:
            initialize-schema: always   # 第一次启动时自动建表
            platform: postgresql        # 指定数据库类型

initialize-schema: always 第一次跑会自动建表,后面可以改成 never。PostgreSQL 方言一般能自动识别,显式指定一下更稳妥。

第三步:改代码

原来用的是内存存储,现在换成 JDBC:

java 复制代码
@Configuration
public class ChatConfig {

    private final JdbcChatMemoryRepository repository;

    public ChatConfig(JdbcChatMemoryRepository repository) {
        this.repository = repository;
    }

    @Bean
    public ChatMemory chatMemory() {
        return MessageWindowChatMemory.builder()
                .chatMemoryRepository(repository)  // 换成 JDBC 存储
                .maxMessages(50)                    // 最多保留最近 50 条消息
                .build();
    }

    @Bean
    public ChatClient chatClient(ChatClient.Builder builder, ChatMemory chatMemory) {
        return builder
                .defaultAdvisors(
                        new MessageChatMemoryAdvisor(chatMemory) // 挂载记忆拦截器
                )
                .build();
    }
}

MessageChatMemoryAdvisor 会在每次请求前自动把历史消息塞进上下文,响应后再把新消息存进去。你只需要按 conversationId 区分不同用户就行。


三、顺手加两个小功能:会话列表与删除

存进数据库之后,会话列表和删除记录就是顺手的事了。

JdbcChatMemoryRepository 里本来就有这两个方法:

java 复制代码
void clearByChatId(String chatId);
List<ChatInfo> getChats();

写个 Controller 暴露出去:

java 复制代码
@RestController
@RequestMapping("/api/conversation")
public class ConversationController {

    private final JdbcChatMemoryRepository repository;

    public ConversationController(JdbcChatMemoryRepository repository) {
        this.repository = repository;
    }
   
    @RequestMapping("/getChatHistory")
    public List<MessageVO> getChatHistory(String chatId){
        List<Message> messages = chatMemory.get(chatId);
        return messages.stream().map(MessageVO::new).collect(Collectors.toList());
    }

   
    @RequestMapping("/deleteChat")
    public Boolean deleteChat(String chatId){
        try {
            chatHistoryTitleRepository.clearByChatId(chatId);
            chatMemory.clear(chatId);
        }catch (Exception e){
            return false;
        }
        return true;
    }
}

这样前端就能拿到所有历史会话列表,用户也可以删掉某次聊天记录。能存、能查、能删------一个聊天产品该有的基本能力就有了。


四、我踩过的两个坑

1. 自动建表在 MySQL 下可能不生效

PostgreSQL 一般没问题。MySQL 的话,initialize-schema: always 可能不会自动建表,需要手动执行建表 SQL。表结构大概是这样的:

sql 复制代码
CREATE TABLE spring_ai_chat_memory (
    id BIGINT AUTO_INCREMENT PRIMARY KEY,
    conversation_id VARCHAR(36) NOT NULL,
    content TEXT NOT NULL,
    type VARCHAR(10) NOT NULL,
    timestamp TIMESTAMP NOT NULL,
    INDEX idx_conversation (conversation_id, timestamp)
);

2. 别把窗口设太大

maxMessages 建议设为 10-50 条。设太多,每次请求 Token 消耗会爆炸,大模型也容易"迷失"在过长的上下文里。


五、后面还能怎么玩?

持久化搞定了,后面可以接着做两件事:

  • 会话摘要:会话太长时,用一个小模型把历史消息压缩成摘要,塞进 System Prompt,省 Token。
  • RAG 知识库:Spring AI 支持 PGVector,把文档向量化存进 PostgreSQL,让 AI 根据你的私有文档回答问题。

Spring AI 这套东西,核心就是三个角色:ChatMemory 存、ChatMemoryRepository 存和取、MessageChatMemoryAdvisor 把存取的逻辑自动串起来。搞懂这个分工,后面换 Redis 还是换别的数据库,思路都一样。


📍 关于我

我是麻雀 ,6年央国企实战派,专注 Java后端工程踩坑 + AI原生开发实战

🎁 粉丝专属开源代码与福利

  • 关注公众号【麻雀聊技术 】,回复【SpringAI】:获取已对接 PostgreSQL持久化记忆与历史会话列表的开箱即用工程源码!
  • 回复【路线图】:免费获取《Java后端 + Spring 全景学习路线图 (α/β/γ高清图)》

👉 如果觉得本文对你有帮助,欢迎【点赞 + 收藏 + 转发】,有问题评论区随时交流!

相关推荐
码农进化录1 小时前
Java 程序员的 AI 进化论 | LangChain4j 入门,Java 开发者的 AI 应用框架
java·spring boot·openai
番茄炒鸡蛋加糖2 小时前
中级核心技术1--MySQL/Java 并发
java·数据库·mysql
he___H2 小时前
Spring-Configur注解
java·spring
SunnyDays10112 小时前
Java 如何为 PDF 添加、修改和删除书签
java·pdf
铃木之影3 小时前
Java 版本 RAG 示例(Spring AI + Milvus)
java·人工智能·spring
C++、Java和Python的菜鸟3 小时前
第14章 项目部署(Linux)
java
智海深蓝3 小时前
智慧渔业海上养殖数字孪生实践方向与难点拆解分析
java·前端·网络
2601_955760074 小时前
Claude API 多人协作中的版本管理方法
java·ai编程
梦想的旅途24 小时前
企业微信API二次开发:外部群模块功能清单与全场景对接
java·python·企业微信