【langchain4j实战-06】Spring Boot + MyBatis 持久化会话记忆,打造企业级AI对话系统

张开发
2026/4/15 15:22:05 15 分钟阅读

分享文章

【langchain4j实战-06】Spring Boot + MyBatis 持久化会话记忆,打造企业级AI对话系统
1. 为什么需要会话持久化想象一下你和朋友聊天的场景。如果每次重启手机之前的聊天记录都消失你还能记得昨天聊到哪吗AI对话系统同样面临这个问题。传统的内存存储方式就像用便利贴记东西——断电就没了。这对于企业级应用简直是灾难特别是当用户问上次我们聊到哪时系统却回答我们第一次见面吧。我做过一个客服系统项目最初没做持久化结果服务器每周维护时客户投诉量直接翻倍。后来用MySQL做持久化存储后不仅投诉归零还能做历史对话分析。这就是为什么LangChain4j的持久化功能如此重要——它让AI真正记住了你是谁。2. 环境搭建与依赖配置2.1 必备依赖清单先看pom.xml关键配置这里有个坑我踩过Spring Boot 3.x必须用MyBatis 3.0版本否则启动会报错!-- LangChain4j核心 -- dependency groupIddev.langchain4j/groupId artifactIdlangchain4j/artifactId version0.25.0/version /dependency !-- Spring Boot MyBatis全家桶 -- dependency groupIdorg.mybatis.spring.boot/groupId artifactIdmybatis-spring-boot-starter/artifactId version3.0.3/version /dependency !-- 数据库连接池选型建议 -- dependency groupIdcom.alibaba/groupId artifactIddruid/artifactId version1.2.20/version /dependency实测发现HikariCP在高并发下性能更好但Druid的监控功能更完善。如果你们团队需要SQL监控选Druid准没错。2.2 数据库连接配置application.yml这样配最稳妥spring: datasource: url: jdbc:mysql://localhost:3306/ai_chat?useSSLfalseserverTimezoneAsia/Shanghai username: root password: 123456 driver-class-name: com.mysql.cj.jdbc.Driver druid: initial-size: 5 max-active: 20 validation-query: SELECT 1记得在MySQL8.0必须加时区参数否则会报serverTimezone错误。这个坑我凌晨3点debug过...3. 数据库设计实战3.1 表结构优化方案原始设计有两个问题1消息内容用TEXT类型影响查询性能 2缺少对话状态字段。这是我的优化版CREATE TABLE ai_conversation ( id BIGINT UNSIGNED NOT NULL AUTO_INCREMENT, session_id VARCHAR(64) NOT NULL COMMENT UUID格式, user_id VARCHAR(64) NOT NULL COMMENT 用户标识, status TINYINT DEFAULT 1 COMMENT 0-结束 1-进行中, created_at DATETIME(3) NOT NULL COMMENT 精确到毫秒, PRIMARY KEY (id), UNIQUE KEY uk_session (session_id), KEY idx_user (user_id) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4; CREATE TABLE ai_message ( id BIGINT UNSIGNED NOT NULL AUTO_INCREMENT, session_id VARCHAR(64) NOT NULL, message_seq INT NOT NULL COMMENT 对话序号, role ENUM(USER,AI,SYSTEM) NOT NULL, content JSON NOT NULL COMMENT 结构化存储, tokens INT DEFAULT 0, created_at DATETIME(3) NOT NULL, PRIMARY KEY (id), KEY idx_session (session_id) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4;关键改进使用JSON类型存储结构化消息增加message_seq保证对话顺序DATETIME(3)记录精确时间枚举类型限制角色取值3.2 MyBatis映射技巧在MessageMapper.xml里这样处理JSON字段resultMap idmessageMap typecom.example.AiMessage result propertycontent columncontent typeHandlerorg.apache.ibatis.type.JsonTypeHandler/ /resultMap记得在实体类上加注解public class AiMessage { TableField(typeHandler JsonTypeHandler.class) private MessageContent content; }4. 核心实现逻辑4.1 自定义ChatMemoryStore重点看updateMessages方法这里采用了UPSERT策略Override Transactional public void updateMessages(Object memoryId, ListChatMessage messages) { // 1. 检查并创建会话 String sessionId (String) memoryId; Conversation session sessionMapper.selectBySessionId(sessionId); if (session null) { session new Conversation(); session.setSessionId(sessionId); session.setStatus(1); sessionMapper.insert(session); } // 2. 转换并存储消息 ListAiMessage dbMessages messages.stream() .map(msg - { AiMessage entity new AiMessage(); entity.setSessionId(sessionId); entity.setRole(msg.type().name()); entity.setContent(convertContent(msg)); return entity; }).collect(Collectors.toList()); messageMapper.batchInsert(dbMessages); }4.2 事务管理要点Spring事务的这两个坑要注意默认只对RuntimeException回滚同类内方法调用不生效建议这样配置Transactional(rollbackFor Exception.class) public void saveConversation(String sessionId, ListChatMessage messages) { // 业务逻辑 }5. 性能优化实践5.1 批量插入优化MyBatis批量插入有3种方式实测结果方式1万条耗时内存占用循环单条插入12.8s高BatchExecutor1.4s中批量SQL拼接0.9s低推荐使用第三种Insert({ script, INSERT INTO ai_message (...) VALUES , foreach collectionlist itemitem separator,, (#{item.sessionId}, #{item.role}, ...), /foreach, /script }) void batchInsert(Param(list) ListAiMessage messages);5.2 缓存策略二级缓存这样配置最合理mybatis: configuration: cache-enabled: true local-cache-scope: statement在Mapper接口上添加CacheNamespace(eviction LruCache.class, size 1000) public interface MessageMapper { // 方法定义 }6. 异常处理经验这些异常你肯定会遇到序列化异常当ChatMessage包含特殊字符时// 解决方案自定义序列化器 public class SafeMessageSerializer { public static String serialize(ChatMessage message) { try { return objectMapper.writeValueAsString(message); } catch (JsonProcessingException e) { return {\error\:\serialize_failed\}; } } }事务失效场景方法非public自调用问题异常被捕获未抛出7. 完整调用示例最后看一个带用户上下文的完整流程// 1. 配置AI服务 Bean public Assistant assistant(ChatLanguageModel model, ChatMemoryStore memoryStore) { return AiServices.builder(Assistant.class) .chatLanguageModel(model) .chatMemoryProvider(memoryId - MessageWindowChatMemory.builder() .id(memoryId) .maxMessages(20) .chatMemoryStore(memoryStore) .build()) .build(); } // 2. 业务调用 public String handleUserQuery(String userId, String question) { String sessionId user_ userId; return assistant.chat(sessionId, question); }我在金融项目中使用这种方案用户满意度提升了40%。关键点是每个用户有独立sessionId历史对话自动作为上下文支持多轮对话管理

更多文章