【langchain4j实战-06】Spring Boot + MyBatis 持久化会话记忆,打造企业级AI对话系统
2026/4/15 15:22:03 网站建设 项目流程

1. 为什么需要会话持久化?

想象一下你和朋友聊天的场景。如果每次重启手机,之前的聊天记录都消失,你还能记得昨天聊到哪吗?AI对话系统同样面临这个问题。传统的内存存储方式就像用便利贴记东西——断电就没了。这对于企业级应用简直是灾难,特别是当用户问"上次我们聊到哪?"时,系统却回答"我们第一次见面吧"。

我做过一个客服系统项目,最初没做持久化,结果服务器每周维护时客户投诉量直接翻倍。后来用MySQL做持久化存储后,不仅投诉归零,还能做历史对话分析。这就是为什么LangChain4j的持久化功能如此重要——它让AI真正记住了"你是谁"。

2. 环境搭建与依赖配置

2.1 必备依赖清单

先看pom.xml关键配置,这里有个坑我踩过:Spring Boot 3.x必须用MyBatis 3.0+版本,否则启动会报错:

<!-- LangChain4j核心 --> <dependency> <groupId>dev.langchain4j</groupId> <artifactId>langchain4j</artifactId> <version>0.25.0</version> </dependency> <!-- Spring Boot + MyBatis全家桶 --> <dependency> <groupId>org.mybatis.spring.boot</groupId> <artifactId>mybatis-spring-boot-starter</artifactId> <version>3.0.3</version> </dependency> <!-- 数据库连接池选型建议 --> <dependency> <groupId>com.alibaba</groupId> <artifactId>druid</artifactId> <version>1.2.20</version> </dependency>

实测发现HikariCP在高并发下性能更好,但Druid的监控功能更完善。如果你们团队需要SQL监控,选Druid准没错。

2.2 数据库连接配置

application.yml这样配最稳妥:

spring: datasource: url: jdbc:mysql://localhost:3306/ai_chat?useSSL=false&serverTimezone=Asia/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`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; 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`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;

关键改进:

  • 使用JSON类型存储结构化消息
  • 增加message_seq保证对话顺序
  • DATETIME(3)记录精确时间
  • 枚举类型限制角色取值

3.2 MyBatis映射技巧

在MessageMapper.xml里这样处理JSON字段:

<resultMap id="messageMap" type="com.example.AiMessage"> <result property="content" column="content" typeHandler="org.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, List<ChatMessage> 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. 转换并存储消息 List<AiMessage> 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事务的这两个坑要注意:

  1. 默认只对RuntimeException回滚
  2. 同类内方法调用不生效

建议这样配置:

@Transactional(rollbackFor = Exception.class) public void saveConversation(String sessionId, List<ChatMessage> messages) { // 业务逻辑 }

5. 性能优化实践

5.1 批量插入优化

MyBatis批量插入有3种方式,实测结果:

方式1万条耗时内存占用
循环单条插入12.8s
BatchExecutor1.4s
批量SQL拼接0.9s

推荐使用第三种:

@Insert({ "<script>", "INSERT INTO ai_message (...) VALUES ", "<foreach collection='list' item='item' separator=','>", "(#{item.sessionId}, #{item.role}, ...)", "</foreach>", "</script>" }) void batchInsert(@Param("list") List<AiMessage> messages);

5.2 缓存策略

二级缓存这样配置最合理:

mybatis: configuration: cache-enabled: true local-cache-scope: statement

在Mapper接口上添加:

@CacheNamespace(eviction = LruCache.class, size = 1000) public interface MessageMapper { // 方法定义 }

6. 异常处理经验

这些异常你肯定会遇到:

  1. 序列化异常:当ChatMessage包含特殊字符时
// 解决方案:自定义序列化器 public class SafeMessageSerializer { public static String serialize(ChatMessage message) { try { return objectMapper.writeValueAsString(message); } catch (JsonProcessingException e) { return "{\"error\":\"serialize_failed\"}"; } } }
  1. 事务失效场景
  • 方法非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
  • 历史对话自动作为上下文
  • 支持多轮对话管理

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询