上篇文章我们把 spring_ai_chat 跑通了,一个基于 Spring AI 和 DeepSeek 的聊天项目。跑起来之后有个问题:一重启,AI 就什么都不记得了。
内存存储,重启即失。开发时无所谓,真要拿出去用就不太行了。
这篇就是解决这个事:把聊天记录从内存挪到 PostgreSQL 里,让 AI 重启之后还能认出你。
一、先搞清楚:为什么重启会"失忆"?
大模型本身是无状态的------它压根不记得你三秒前问了什么。
所谓的"多轮对话",其实是每次把历史消息打包塞进请求里,让模型看起来"记得"。启动时我们用的是 InMemoryChatMemory,存内存里,一重启自然什么都没了。
Spring AI 对这个事有抽象:ChatMemory 负责"记忆"的读写,ChatMemoryRepository 负责存储。JdbcChatMemoryRepository 就是 JDBC 的实现,支持 PostgreSQL、MySQL 这些数据库。换掉它,问题就解决了。
看懂下面这张工作原理图,你就明白 Spring AI 是怎么实现记忆存储的了:
图解原理 :Spring AI 的
MessageChatMemoryAdvisor就像一个贴心的秘书:
- 提问前:先去数据库翻出之前的聊天记录,打包一起交给 DeepSeek(大模型才知道上下文)。
- 回答后:把你刚才的提问和 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 全景学习路线图 (α/β/γ高清图)》
👉 如果觉得本文对你有帮助,欢迎【点赞 + 收藏 + 转发】,有问题评论区随时交流!