在前文当中,我们已经介绍了 Agent 的记忆能力,并基于 Spring AI 的聊天记忆组件,实现了具备记忆功能的智能行程规划 Agent。通过为 Agent 赋予记忆能力,使其能够存储用户偏好、自动复用历史交互信息,从而让行程规划更具个性化与高效性。本文将在此基础上,进一步介绍聊天记忆如何持久化存储至数据库,满足实际部署需求。
在实际使用过程中,必须对记忆能力进行扩展并实现持久化存储,以保证数据可靠性与系统稳定性。
一、存储介质替换方案
在测试验证的情况下通常采用内存存储(InMemoryChatMemoryRepository),服务重启后记忆会丢失;实际使用时建议替换为持久化存储方案:
二、记忆策略设计
在完成持久化改造的基础上,还需配套建立记忆数据的管理、更新与失效机制:
三、【案例】实现Agent 记忆持久化
3.1 核心开发流程
step 1:引入依赖:添加 Spring AI JDBC 记忆存储和 MySQL 驱动依赖;
step 2:配置数据库:在application.properties 中配置 MySQL 连接信息和 JDBC 记忆参数;
step 3:初始化记忆组件:绑定JdbcChatMemoryRepository 与 ChatMemory,配置双记忆类型;
step 4:开发业务服务:封装带记忆的行程规划逻辑,支持记忆类型动态切换
step 5:提供接口:设计标准化 HTTP 接口,支持规划、清除、查询功能。
Weiz-SpringAI-Agent-Memory-DB<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>com.example</groupId>
<artifactId>Weiz-SpringAI-Agent</artifactId>
<version>0.0.1-SNAPSHOT</version>
</parent>
<artifactId>Weiz-SpringAI-Agent-Memory-DB</artifactId>
<name>Weiz-SpringAI-Agent-Memory-DB</name>
<description>Weiz-SpringAI-Agent-Memory-DB</description>
<properties>
<java.version>17</java.version>
<maven.compiler.source>17</maven.compiler.source>
<maven.compiler.target>17</maven.compiler.target>
</properties>
<dependencies>
<!-- Spring Web 依赖 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!-- 智普 AI 依赖 -->
<dependency>
<groupId>org.springframework.ai</groupId>
<artifactId>spring-ai-starter-model-zhipuai</artifactId>
</dependency>
<!-- Spring AI JDBC 记忆存储依赖(内置 JdbcChatMemoryRepository) -->
<dependency>
<groupId>org.springframework.ai</groupId>
<artifactId>spring-ai-starter-model-chat-memory-repository-jdbc</artifactId>
</dependency>
<!-- MySQL 驱动依赖 -->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>8.0.33</version>
<scope>runtime</scope>
</dependency>
<!-- Lombok 依赖 -->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>3. 配置 application.properties
spring.application.name=Weiz-SpringAI-Agent-Memory
server.port=8080
#
spring.ai.zhipuai.api-key=你的智谱API Key
spring.ai.zhipuai.base-url=https://open.bigmodel.cn/api/paas
spring.ai.zhipuai.chat.options.model=GLM-4-Flash
# MySQL
spring.datasource.url=jdbc:mysql://127.0.0.1:3306/spring_ai_agent?useSSL=false&serverTimezone=Asia/Shanghai&allowPublicKeyRetrieval=true
spring.datasource.username=root
spring.datasource.password=12345678
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
#
spring.datasource.hikari.maximum-pool-size=10
spring.datasource.hikari.minimum-idle=5
spring.datasource.hikari.connection-timeout=30000
spring.datasource.hikari.idle-timeout=600000
# Spring AI JDBC
# MySQL mariadb
spring.ai.chat.memory.repository.jdbc.platform=mariadb
spring.ai.chat.memory.repository.jdbc.initialize-schema=always
# spring_ai_chat_memory
# spring.ai.chat.memory.repository.jdbc.table-name=custom_chat_memory
#
logging.level.org.springframework.ai=INFO
logging.level.com.example=DEBUG
logging.level.org.springframework.jdbc=WARN
logging.level.com.mysql.cj=WARN
创建 com.example.weizspringai.config.MysqlMemoryConfig 类,绑定 JDBC 存储与记忆类型:
import lombok.RequiredArgsConstructor;
import org.springframework.ai.chat.client.advisor.MessageChatMemoryAdvisor;
import org.springframework.ai.chat.client.advisor.PromptChatMemoryAdvisor;
import org.springframework.ai.chat.memory.ChatMemory;
import org.springframework.ai.chat.memory.MessageWindowChatMemory;
import org.springframework.ai.chat.memory.repository.jdbc.JdbcChatMemoryRepository;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
@RequiredArgsConstructor
public class MysqlMemoryConfig {
// 注入 Spring AI 内置的 JdbcChatMemoryRepository(自动适配 MySQL)
private final JdbcChatMemoryRepository jdbcChatMemoryRepository;
/**
* 配置 ChatMemory:基于 MySQL 存储+消息窗口限制
* 最大记忆 30 条,避免表数据膨胀
*/
@Bean
public ChatMemory chatMemory() {
return MessageWindowChatMemory.builder()
.chatMemoryRepository(jdbcChatMemoryRepository) // 绑定 MySQL 存储
.maxMessages(30) // 限制最大记忆条数
.build();
}
/**
* 记忆类型 1:MessageChatMemoryAdvisor(角色对话模式)
* 适用于支持角色对话的大模型(如 GLM-4、GPT 系列)
*/
@Bean
public MessageChatMemoryAdvisor messageChatMemoryAdvisor(ChatMemory chatMemory) {
return MessageChatMemoryAdvisor.builder(chatMemory)
.build();
}
/**
* 记忆类型 2:PromptChatMemoryAdvisor(Prompt 封装模式)
* 适用于不支持角色对话的大模型(记忆封装到 System Prompt)
*/
@Bean
public PromptChatMemoryAdvisor promptChatMemoryAdvisor(ChatMemory chatMemory) {
return PromptChatMemoryAdvisor.builder(chatMemory)
.build();
}
}创建 com.example.weizspringai.service.MysqlMemoryTripAgentService 类,封装带记忆的行程规划能力:
import lombok.RequiredArgsConstructor;
import org.springframework.ai.chat.client.ChatClient;
import org.springframework.ai.chat.client.advisor.MessageChatMemoryAdvisor;
import org.springframework.ai.chat.client.advisor.PromptChatMemoryAdvisor;
import org.springframework.ai.chat.client.advisor.api.BaseChatMemoryAdvisor;
import org.springframework.ai.chat.memory.ChatMemory;
import org.springframework.ai.chat.memory.repository.jdbc.JdbcChatMemoryRepository;
import org.springframework.stereotype.Service;
import java.util.List;
@Service
@RequiredArgsConstructor
public class MysqlMemoryTripAgentService {
private final ChatClient chatClient;
private final ChatMemory chatMemory;
private final JdbcChatMemoryRepository jdbcChatMemoryRepository;
// 注入两种记忆类型,支持动态切换
private final MessageChatMemoryAdvisor messageChatMemoryAdvisor;
private final PromptChatMemoryAdvisor promptChatMemoryAdvisor;
/**
* 生产级带 MySQL 记忆的行程规划
*
* @param userId 用户唯一标识(即 conversationId,隔离多用户)
* @param demand 出行需求
* @param memoryType 记忆类型:message/prompt
* @return 个性化行程规划
*/
public String planTripWithMysqlMemory(String userId, String demand, String memoryType) {
// 动态选择记忆类型
BaseChatMemoryAdvisor selectedAdvisor = "prompt".equals(memoryType)
? promptChatMemoryAdvisor
: messageChatMemoryAdvisor;
// 生产级 Agent 行为规则:强化记忆复用、输出准确性
String systemPrompt = """
你是生产级智能行程规划 Agent,严格遵守以下规则:
1. 优先从 MySQL 存储的历史记忆中提取用户偏好(景点类型、饮食禁忌、交通方式、出行人数);
2. 无需用户重复说明已存储的偏好,新需求可覆盖旧记忆;
3. 行程按天/时段拆分,包含景点、交通、餐饮、实用提示(开放时间、预约要求),信息准确可执行;
4. 语言简洁专业,适配移动端阅读,避免冗余表述;
5. 未查询到记忆时,按当前需求正常规划,不提示记忆相关信息。
""";
// 调用大模型:关联用户 MySQL 记忆
return chatClient.prompt()
.system(systemPrompt)
.user(demand)
.advisors(selectedAdvisor)
.advisors(advisorSpec -> advisorSpec.param(ChatMemory.CONVERSATION_ID, userId))
.call()
.content();
}
/**
* 清除用户 MySQL 记忆(生产级必备功能)
*/
public void clearUserMemory(String userId) {
chatMemory.clear(userId);
}
/**
* 查询所有用户对话 ID(管理端使用)
*/
public List<String> listAllConversationIds() {
// 注入 JdbcChatMemoryRepository 以查询所有对话 ID
return jdbcChatMemoryRepository.findConversationIds();
}
}创建com.example.weizspringai.controller.MysqlAgentController类,提供标准化 HTTP 接口:
import com.example.weizspringai.service.MysqlMemoryTripAgentService;
import lombok.RequiredArgsConstructor;
import org.springframework.web.bind.annotation.*;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
@RestController
@RequestMapping("/agent/mysql/trip")
@RequiredArgsConstructor
public class MysqlAgentController {
private final MysqlMemoryTripAgentService mysqlMemoryTripAgentService;
/**
* MySQL 记忆行程规划接口
*
* @param userId 用户唯一标识(必填,如 1001、user_999)
* @param demand 出行需求(必填)
* @param memoryType 记忆类型(可选,默认 message)
*/
@GetMapping("/plan")
public Map<String, Object> planTrip(
@RequestParam("userId") String userId,
@RequestParam("demand") String demand,
@RequestParam("memoryType") String memoryType) {
String tripPlan = mysqlMemoryTripAgentService.planTripWithMysqlMemory(userId, demand, memoryType);
Map<String, Object> result = new HashMap<>();
result.put("code", 200);
result.put("msg", "success");
result.put("data", Map.of(
"userId", userId,
"memoryType", memoryType,
"userDemand", demand,
"tripPlan", tripPlan,
"storageType", "MySQL 持久化"
));
return result;
}
/**
* 清除用户 MySQL 记忆接口(生产环境需添加权限认证)
*/
@GetMapping("/clear-memory")
public Map<String, Object> clearMemory(@RequestParam("userId") String userId) {
mysqlMemoryTripAgentService.clearUserMemory(userId);
return Map.of(
"code", 200,
"msg", "用户 MySQL 记忆清除成功",
"data", Map.of("userId", userId)
);
}
/**
* 查询所有对话 ID 接口(管理端使用)
*/
@GetMapping("/list-conversations")
public Map<String, Object> listConversations() {
List<String> conversationIds = mysqlMemoryTripAgentService.listAllConversationIds();
return Map.of(
"code", 200,
"msg", "success",
"data", Map.of(
"conversationCount", conversationIds.size(),
"conversationIds", conversationIds
)
);
}
}测试 1:首次规划(MySQL 存储记忆)
访问接口(用户 1001 首次查询,说明偏好):http://localhost:8080/agent/mysql/trip/plan?userId=1001&memoryType=message&demand=周末两天厦门游,2人,偏好海滨景点,不吃海鲜,交通以地铁和网约车为主。
响应结果(正常返回行程,记忆存入 MySQL):
{
"msg": "success",
"code": 200,
"data": {
"userDemand": "周末两天厦门游,2人,偏好海滨景点,不吃海鲜,交通以地铁和网约车为主",
"storageType": "MySQL 持久化",
"tripPlan": "根据您提供的信息,以下是为您规划的厦门周末两日游行程:\n\n### 第一天\n\n**上午**\n- **8:00-10:00** 乘坐地铁1号线至【鼓浪屿站】出口,步行至鼓浪屿码头。\n- **10:00-12:00** 游览【日光岩】,欣赏海景,参观【菽庄花园】。\n- **12:00-13:30** 选择一家不提供海鲜的餐厅,享用午餐。\n\n**下午**\n- **13:30-15:30** 前往【厦门大学】,参观校园,体验校园文化。\n- **15:30-17:00** 漫步【南普陀寺】周边,感受佛教文化。\n- **17:00-18:30** 返回市区,乘坐网约车至【曾厝垵】。\n\n**晚上**\n- **18:30-20:00** 在曾厝垵品尝当地特色小吃,避免海鲜选择。\n- **20:00-21:30** 乘坐网约车返回酒店休息。\n\n### 第二天\n\n**上午**\n- **8:00-10:00** 乘坐地铁2号线至【胡里山站】出口,游览【胡里山炮台】。\n- **10:00-12:00** 乘坐地铁2号线至【海沧大桥站】出口,前往【环岛路】,骑行或步行欣赏海滨风光。\n\n**中午**\n- **12:00-13:30** 在【环岛路】附近的餐厅享用午餐。\n\n**下午**\n- **13:30-15:30** 前往【白城沙滩】享受海滩时光。\n- **15:30-17:00** 乘坐地铁1号线返回市区,前往【厦门科技馆】参观。\n\n**晚上**\n- **17:00-18:30** 返回酒店休息。\n- **18:30-20:00** 根据个人喜好选择市区内的餐厅享用晚餐。\n\n**实用提示**:\n- 以上行程仅供参考,具体安排可根据实际情况调整。\n- 请提前了解景点开放时间和预约要求。\n- 出行时注意个人安全,保管好个人物品。\n\n祝您厦门之旅愉快!",
"memoryType": "message",
"userId": "1001"
}
}查询 spring_ai_chat_memory 表,可见聊天记录已存储

重新启动,访问接口(用户 1001 二次查询,不重复偏好):http://localhost:8080/agent/mysql/trip/plan?userId=1001&memoryType=message&demand=下周工作日三天,规划一次泉州游。
响应结果(自动复用 MySQL 中存储的偏好,无需重复输入):
{
"msg": "success",
"code": 200,
"data": {
"userDemand": "下周工作日三天,规划一次泉州游",
"storageType": "MySQL 持久化",
"tripPlan": "根据您的要求,以下是为您规划的泉州三天游行程:\n\n### 第一天:泉州古城游\n\n**上午**\n- **8:00-9:30** 从酒店出发,乘坐网约车前往【开元寺】。\n- **9:30-11:30** 参观开元寺,欣赏宋代建筑风格,了解泉州佛教文化。\n- **11:30-13:00** 在开元寺附近享用午餐。\n\n**下午**\n- **13:00-14:30** 前往【清净寺】,参观阿拉伯风格的古寺。\n- **14:30-16:00** 漫步【西街】,体验泉州古城的风情,品尝当地小吃。\n- **16:00-17:30** 乘坐网约车前往【泉州府文庙】,了解泉州的历史文化。\n\n**晚上**\n- **18:00-19:30** 在西街品尝泉州特色美食。\n- **19:30-21:00** 返回酒店休息。\n\n### 第二天:泉州自然风光游\n\n**上午**\n- **8:00-10:00** 乘坐网约车前往【清源山】。\n- **10:00-12:00** 游览清源山,参观【老君岩】等自然景观。\n- **12:00-13:30** 在清源山附近享用午餐。\n\n**下午**\n- **13:30-15:30** 前往【惠安女风情园】,了解惠安女的生活习俗。\n- **15:30-17:00** 乘坐网约车前往【洛阳桥】,参观这座历史悠久的古桥。\n- **17:00-18:30** 返回市区。\n\n**晚上**\n- **18:30-20:00** 在市区内享用晚餐。\n- **20:00-21:30** 返回酒店休息。\n\n### 第三天:泉州文化体验游\n\n**上午**\n- **8:00-10:00** 乘坐网约车前往【泉州海外交通史博物馆】。\n- **10:00-12:00** 参观博物馆,了解泉州的海外交通历史。\n- **12:00-13:30** 在博物馆附近享用午餐。\n\n**下午**\n- **13:30-15:30** 前往【德化县】,参观德化陶瓷博物馆,了解德化陶瓷文化。\n- **15:30-17:00** 乘坐网约车返回市区,前往【南音馆】,欣赏南音表演。\n- **17:00-18:30** 返回酒店,准备返程。\n\n**实用提示**:\n- 请根据实际情况调整行程,注意景点开放时间和预约要求。\n- 出行时注意个人安全,保管好个人物品。\n- 根据个人喜好选择餐厅,泉州美食丰富多样。\n\n祝您泉州之旅愉快!",
"memoryType": "message",
"userId": "1001"
}
}访问接口(使用 prompt 记忆类型,适配不支持角色对话的模型):http://localhost:8080/agent/mysql/trip/plan?userId=1001&demand=再规划一次漳州游&memoryType=prompt
响应结果(正常复用 MySQL 记忆,记忆封装到 System Prompt):
{
"msg": "success",
"code": 200,
"data": {
"userDemand": "再规划一次漳州游",
"storageType": "MySQL 持久化",
"tripPlan": "根据您之前提供的偏好信息,以下是为您规划的漳州两天游行程:\n\n### 第一天:漳州古城游\n\n**上午**\n- **8:00-9:30** 从酒店出发,乘坐网约车前往【漳州古城】。\n- **9:30-11:30** 参观漳州古城,漫步于古街巷,感受古城的历史韵味。\n- **11:30-13:00** 在古城内选择一家不提供海鲜的餐厅,享用午餐。\n\n**下午**\n- **13:00-14:30** 前往【漳州文庙】,了解漳州的历史文化。\n- **14:30-16:00** 参观【三平寺】,体验佛教文化,欣赏寺庙建筑。\n- **16:00-17:30** 乘坐网约车前往【云洞岩】,游览岩石景观,感受自然之美。\n\n**晚上**\n- **18:30-20:00** 在古城内品尝漳州特色小吃,避免海鲜选择。\n- **20:00-21:30** 返回酒店休息。\n\n### 第二天:漳州自然风光游\n\n**上午**\n- **8:00-10:00** 乘坐网约车前往【火山岛国家地质公园】。\n- **10:00-12:00** 游览火山岛,探索地质奇观,欣赏自然风光。\n- **12:00-13:30** 在火山岛附近享用午餐。\n\n**下午**\n- **13:30-15:30** 前往【南靖土楼群】,参观世界文化遗产,了解客家文化。\n- **15:30-17:00** 乘坐网约车前往【泰宁大金湖】,欣赏湖光山色,体验水上活动。\n\n**晚上**\n- **17:30-19:00** 在泰宁享用晚餐。\n- **19:00-20:30** 返回酒店休息。\n\n**实用提示**:\n- 以上行程仅供参考,具体安排可根据实际情况调整。\n- 请提前了解景点开放时间和预约要求。\n- 出行时注意个人安全,保管好个人物品。\n\n祝您漳州之旅愉快!",
"memoryType": "prompt",
"userId": "1001"
}
}访问接口(清除用户 1001 的 MySQL 记忆):http://localhost:8080/agent/mysql/trip/clear-memory?userId=1001
响应结果:
{
"data": {
"userId": "1001"
},
"code": 200,
"msg": "用户 MySQL 记忆清除成功"
}同时,再次查询 spring_ai_chat_memory 表,用户 1001 的记录已删除。

四、测试结果分析
本文通过 Spring AI 的聊天记忆功能,为智能行程规划 Agent 新增了记忆能力,解决了 “重复输入偏好” 的痛点。Spring AI 封装了成熟的记忆组件,开发者无需关注底层存储细节,即可快速实现 Agent 的记忆功能,极大降低了开发门槛。
后续文章将继续深入 Agent 的高级特性,讲解如何集成工具调用(如调用天气 API 调整行程、调用预约 API 预订景点门票),实现 “规划 + 执行” 一体化的智能 Agent。
如果本文对你有帮助,欢迎点赞、在看、转发,关注我们获取更多 Spring AI 实战干货!