news 2026/6/9 19:57:14

SpringAi基于PgSQL数据库存储扩展ChatMemory

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
SpringAi基于PgSQL数据库存储扩展ChatMemory

一、环境准备

SpringAI入门学习

<!-- SpringAI--> <dependency> <groupId>com.alibaba.cloud.ai</groupId> <artifactId>spring-ai-alibaba-starter</artifactId> <version>1.0.0-M6.1</version> </dependency> <dependency> <groupId>org.postgresql</groupId> <artifactId>postgresql</artifactId> <version>42.3.1</version> </dependency> <dependency> <groupId>org.springframework.ai</groupId> <artifactId>spring-ai-pgvector-store-spring-boot-starter</artifactId> <version>1.0.0-M6</version> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-jdbc</artifactId> </dependency>

上文中实现了基于内存的会话保持功能,现在要基于数据库扩展实现

JdbcTemplate+PgSQL数据库实现扩展ChatMemory实现。

二、扩展实现

package org.springframework.ai.chat.memory; import java.util.List; import org.springframework.ai.chat.messages.Message; public interface ChatMemory { default void add(String conversationId, Message message) { this.add(conversationId, List.of(message)); } void add(String conversationId, List<Message> messages); List<Message> get(String conversationId, int lastN); void clear(String conversationId); }
  • 表结构准备
CREATE TABLE chat_messages ( id BIGSERIAL PRIMARY KEY, conversation_id VARCHAR(255) NOT NULL, message_type VARCHAR(50) NOT NULL, content TEXT NOT NULL, created_at TIMESTAMP NOT NULL ); -- 创建索引 CREATE INDEX idx_conversation_id ON chat_messages (conversation_id);
  • ChatDao接口定义
package org.spring.springaiprojet.dao; import org.spring.springaiprojet.entity.ChatMessageEntity; import java.util.List; public interface ChatDao { /** * 保存表 * @param messages */ void insertMessages(List<ChatMessageEntity> messages); /** * 查询最近的N条消息 * @param conversationId * @param lastN * @return */ List<ChatMessageEntity> findLastNMessages(String conversationId, int lastN); /** * 删除会话 * @param conversationId */ void deleteByConversationId(String conversationId); }
  • ChatImpl接口实现
import org.spring.springaiprojet.dao.ChatDao; import org.spring.springaiprojet.entity.ChatMessageEntity; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.jdbc.core.JdbcTemplate; import org.springframework.stereotype.Repository; import java.time.LocalDateTime; import java.util.List; @Repository public class ChatDaoImpl implements ChatDao { @Autowired private JdbcTemplate jdbcTemplate; @Override public void insertMessages(List<ChatMessageEntity> messages) { jdbcTemplate.batchUpdate("insert into chat_messages (conversation_id, message_type, content, created_at) values (?, ?, ?, ?)", messages, messages.size(), (ps, message) -> { ps.setString(1, message.getConversationId()); ps.setString(2, message.getMessageType()); ps.setString(3, message.getContent()); ps.setObject(4, message.getCreatedAt()); }); } @Override public List<ChatMessageEntity> findLastNMessages(String conversationId, int lastN) { return jdbcTemplate.query( "select * from chat_messages where conversation_id = ? order by created_at desc limit ?", (rs, rowNum) -> { ChatMessageEntity message = new ChatMessageEntity(); message.setConversationId(rs.getString("conversation_id")); message.setMessageType(rs.getString("message_type")); message.setContent(rs.getString("content")); message.setCreatedAt(rs.getObject("created_at", LocalDateTime.class)); return message; }, conversationId, lastN ); } @Override public void deleteByConversationId(String conversationId) { jdbcTemplate.update("delete from chat_messages where conversation_id = ?", conversationId); } }
  • ChatMessageEntity
package org.spring.springaiprojet.entity; import java.time.LocalDateTime; public class ChatMessageEntity { private Long id; private String conversationId; private String messageType; private String content; private LocalDateTime createdAt; public Long getId() { return id; } public void setId(Long id) { this.id = id; } public String getConversationId() { return conversationId; } public void setConversationId(String conversationId) { this.conversationId = conversationId; } public String getMessageType() { return messageType; } public void setMessageType(String messageType) { this.messageType = messageType; } public String getContent() { return content; } public void setContent(String content) { this.content = content; } public LocalDateTime getCreatedAt() { return createdAt; } public void setCreatedAt(LocalDateTime createdAt) { this.createdAt = createdAt; } }
  • PgSQLChatMemory
package org.spring.springaiprojet.config.chat; import org.spring.springaiprojet.dao.ChatDao; import org.spring.springaiprojet.entity.ChatMessageEntity; import org.spring.springaiprojet.entity.MessageEnum; import org.springframework.ai.chat.memory.ChatMemory; import org.springframework.ai.chat.messages.AssistantMessage; import org.springframework.ai.chat.messages.Message; import org.springframework.ai.chat.messages.UserMessage; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Component; import java.time.LocalDateTime; import java.util.Collections; import java.util.List; import java.util.stream.Collectors; @Component public class PgSQLChatMemory implements ChatMemory { @Autowired private ChatDao chatDao; @Override public void add(String conversationId, List<Message> messages) { if (messages == null || messages.isEmpty()) { return; } List<ChatMessageEntity> entities = messages.stream() .map(msg -> { ChatMessageEntity entity = new ChatMessageEntity(); entity.setConversationId(conversationId); entity.setContent(msg.getText()); if (msg instanceof UserMessage) { entity.setMessageType(MessageEnum.USER.getValue()); } else if (msg instanceof AssistantMessage) { entity.setMessageType(MessageEnum.ASSISTANT.getValue()); } entity.setCreatedAt(LocalDateTime.now()); return entity; }) .collect(Collectors.toList()); chatDao.insertMessages(entities); } @Override public List<Message> get(String conversationId, int lastN) { List<ChatMessageEntity> entities = chatDao.findLastNMessages(conversationId, lastN); if (entities == null || entities.isEmpty()) { return Collections.emptyList(); } // 倒序 Collections.reverse(entities); return entities.stream() .map(entity -> { switch (entity.getMessageType()) { case "user": return new UserMessage(entity.getContent()); case "assistant": return new AssistantMessage(entity.getContent()); default: throw new IllegalArgumentException("未知的消息类型!"); } }) .collect(Collectors.toList()); } @Override public void clear(String conversationId) { if (conversationId == null){ return; } chatDao.deleteByConversationId(conversationId); } }
  • AiConfig实现
/** * 基于PgSQL实现会话记忆 */ @Autowired private PgSQLChatMemory pgSQLChatMemory; @Bean public ChatClient chatClient(ChatClient.Builder builder) { // defaultSystem,默认系统角色,带有对话身份 return builder // .defaultSystem("请以通俗开发者角度介绍") // 增加会话记忆,基于 .defaultAdvisors(new PromptChatMemoryAdvisor(pgSQLChatMemory)).build(); // .defaultAdvisors(new SimpleLoggerAdvisor()).build(); }
  • 控制器实现
/** * 普通对话模式 * @param question * @return */ @RequestMapping("/qwen/chat/api") public String chat(String question) { return chatClient.prompt().user(question).call().content(); }

三、测试调用

  • 第一次调用如下接口
GET http://localhost:8088/boot/ai/qwen/chat/api?question=SpringBoot框架各个模块作用

已经写入数据表中了

  • 第二次基于上文会话内容,写入表中
GET http://localhost:8088/boot/ai/qwen/chat/api?question=基于上述回答内容,SpringBoot哪个模块学习难度较大,评估一下

再次查看数据库表,发现已经基于会话保存回答问题了

  • 第三次提问,会话记忆
GET http://localhost:8088/boot/ai/qwen/chat/api?question=基于上述回答内容,SpringBoot哪个模块学习难度相对较小,比较容易上手,评估一下

再次查看数据库,以及基本保存进来,基于数据库内容实现上下文会话回答了。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/6/9 18:19:45

存储空间只剩10MB怎么办?边缘Agent极限优化的3个秘密方法

第一章&#xff1a;存储空间只剩10MB的挑战与应对当系统提示存储空间仅剩10MB时&#xff0c;设备往往会出现运行迟缓、应用崩溃甚至无法安装更新等问题。这种情况在嵌入式设备、老旧服务器或云实例中尤为常见。及时识别并清理冗余数据是保障系统稳定运行的关键。快速诊断磁盘使…

作者头像 李华
网站建设 2026/6/8 20:06:32

基于BiLSTM的多输入多输出拟合预测建模(Matlab实现)

基于BiLSTM做多输入多输出的拟合预测建模&#xff0c;输入个输出的个数没有限制。 程序内注释详细直接替换数据就可以使用。 程序语言为matlab。 程序直接运行可以出拟合预测图&#xff0c;线性拟合预测图&#xff0c;多个预测评价指标。在数据分析和预测领域&#xff0c;BiLST…

作者头像 李华
网站建设 2026/6/9 18:41:08

安装conda

1. 软件下载 https://repo.anaconda.com/archive/index.html wget -c https://repo.anaconda.com/archive/Anaconda3-2025.06-1-Linux-x86_64.sh -P /tmp/package 2. 安装conda 在conda文件的目录下输入命令安装&#xff0c;一路回车&#xff0c;直到他要求输入yes bash A…

作者头像 李华
网站建设 2026/6/8 19:22:22

实时异常检测是如何实现的?工业Agent数据分析中的5个核心算法揭秘

第一章&#xff1a;实时异常检测是如何实现的&#xff1f;工业Agent数据分析中的5个核心算法揭秘在现代工业物联网&#xff08;IIoT&#xff09;系统中&#xff0c;实时异常检测是保障生产连续性与设备健康的关键能力。通过部署在边缘设备或云端的智能Agent&#xff0c;系统能够…

作者头像 李华
网站建设 2026/6/9 18:38:09

学生轻薄本电脑推荐/哪家好:适合学生使用的轻薄型笔记本电脑全面解析

在当今数字化学习环境中&#xff0c;一款适合学生使用的轻薄型笔记本电脑已成为不可或缺的学习工具。面对市场上琳琅满目的产品&#xff0c;如何选择一款真正适合自己的轻薄本&#xff1f;本文将为您提供客观、实用的选购指南&#xff0c;帮助您找到最适合的轻薄本。选择学生轻…

作者头像 李华
网站建设 2026/6/8 17:34:12

2025各大品牌学生游戏本电脑推荐及其散热性能对比

引言随着2025年游戏产业的蓬勃发展&#xff0c;高性能游戏本已成为学生群体的刚需装备。在兼顾学习、娱乐与创作的多重需求下&#xff0c;散热性能成为影响用户体验的关键指标。本文基于2025年12月的市场数据与实际测试&#xff0c;对主流品牌学生游戏本进行客观推荐与散热性能…

作者头像 李华