大家好,我是直奔標杆,欢迎各位Java同仁来到《Spring AI 零基础到实战》专栏的第七课,咱们继续并肩前行,一步步搞定Spring AI实战技能,共同实现AI转型!

在上一节《Java开发者AI转型第六课!Spring AI 灵魂架构 Advisor 切面拦截与自定义实战》中,咱们一起吃透了现代Spring AI的核心——Advisor(顾问/切面拦截器)。掌握了这个高阶技能,咱们终于能直面AI应用开发中最头疼的难题:大模型的“7秒钟失忆症”,今天就带大家彻底解决它!

相信很多同仁在实际开发中都遇到过这样的问题,用代码调用大模型聊天时:

你问:“我叫张三,今年 6 岁。”

AI 答:“你好,张三小朋友。”

你紧接着问:“我几岁了?”

AI 答:“抱歉,我不知道你的年龄。”

是不是很困惑?网页版的Deepseek/ChatGPT能轻松记住上下文,为啥咱们通过API调用的大模型就成了“失忆症患者”?还有更实际的问题:如果同时有100个用户使用你的AI客服,怎么确保张三的聊天记忆不串到李四那里?服务器重启后,之前的聊天记录还能找回来吗?

这节课,咱们就借助上一节学到的Advisor机制,结合Spring AI强大的ChatMemory组件,手把手教大家给大模型装上“长久记忆”,同时实现多租户隔离和数据库持久化,直接适配生产环境,干货拉满,建议收藏跟着实操!

本节学习目标(共勉)

  • 认知破局:搞懂大语言模型(LLM)的无状态(Stateless)本质,避开初学者常见误区;

  • 自动化记忆:掌握MessageWindowChatMemory与MessageChatMemoryAdvisor的用法,实现零代码侵入的上下文自动拼接;

  • 多租户隔离:巧用ChatMemory.CONVERSATION_ID,实现多用户会话的物理隔离,避免记忆串线;

  • 记忆持久化:告别内存丢失问题,实战JdbcChatMemoryRepository,将对话记录永久存入MySQL数据库,适配生产级需求。

多租户记忆检索与装配引擎(核心原理拆解)

在动手写代码之前,咱们先通过一张逻辑图,搞清楚多用户场景下,Spring AI底层的记忆切面是如何工作的:

这张图的核心关键的就是ChatMemoryRepository,咱们可以把它类比成一个大型文件柜,方便大家记忆:

  • 抽屉(Key):就是咱们传入的conversationId(会话ID,比如user_001,可对应用户唯一标识);

  • 内容(Value):就是该用户与AI之前所有的聊天记录(List<Message>)。

每次收到用户的提问时,MemoryAdvisor会自动根据请求中的会话ID,从这个“文件柜”里取出该用户的历史聊天数据,自动拼接上下文,彻底省去咱们手动处理的繁琐操作,这就是Spring AI的强大之处!

深入理解:大模型为什么会“失忆”?

在动手写代码之前,咱们先纠正一个很多初学者都会踩的误区:不少同仁觉得,大模型是一个能随时记住数据的“智能伙伴”,其实不然。

无论是ChatGPT、DeepSeek还是通义千问,咱们调用它们的API时,其本质就是一个无状态(Stateless)的数学函数:f(prompt) = response。简单说,就是你的HTTP请求发过去,它只识别本次发送的文本内容;一旦请求结束,你的聊天数据就会被彻底清除,不会在它的内存中留存。

那网页版的Deepseek为什么能记住咱们之前说的话?答案很简单:它的前端或后端会悄悄帮咱们保存所有聊天记录,每次发送新消息时,系统会自动把“历史聊天记录+本次新问题”拼接成一个完整的Prompt,再打包发给大模型——说白了,就是手动帮大模型“记笔记”。

在Spring AI中,这些聊天记录被抽象成了两个核心接口,咱们重点掌握即可,不用死记原理,结合实操理解更高效:

  • ChatMemory:负责记忆管理策略(比如只保留最近10条消息,防止记忆过长撑爆Token,避免不必要的损耗);

  • ChatMemoryRepository:负责物理存储(决定聊天记录存在内存里,还是存入MySQL等数据库)。

实战上手:引入ChatMemory与Advisor(零代码侵入实现自动记忆)

咱们先回顾一下,不使用Spring AI框架时,AI开发中处理聊天记忆有多繁琐:需要手动创建List<Message>,每次对话后还要手动add()用户提问和AI回答,重复工作多,还容易出错。

而Spring AI框架已经帮咱们封装好了一切,借助ChatClient的流式API和内置Advisor,就能省去这些繁琐操作,咱们直接上手实操,步骤清晰,跟着做就能搞定!

第一步:注册全局记忆组件(Spring自动装配,无需手动写大量代码)

要让系统拥有记忆能力,首先需要在Spring容器中注册一个ChatMemory。最简洁的方式是使用MessageWindowChatMemory(滑动窗口记忆法),它会维护一个消息窗口,窗口大小不超过指定上限(默认20条消息),当消息数量超出上限时,会自动删除较旧的消息,但会保留系统消息。

重点来了:这一步咱们完全不用手动实现!当咱们引入模型相关的starter(比如spring-ai-starter-model-deepseek)时,Spring会自动向容器中注册ChatMemoryRepository和MessageWindowChatMemory,底层默认使用InMemoryChatMemoryRepository(本质就是ConcurrentHashMap),将数据存在JVM内存中,核心自动装配代码如下(大家可以参考理解,不用手动编写):

/**
 * 自动装配逻辑(Spring默认实现,供大家参考学习)
 * 
 * 账号:直奔標杆(CSDN),专注Java AI转型实战分享
 */
@AutoConfiguration
@ConditionalOnClass({ChatMemory.class, ChatMemoryRepository.class})
public class ChatMemoryAutoConfiguration {
    @Bean
    @ConditionalOnMissingBean
    ChatMemoryRepository chatMemoryRepository() {
        return new InMemoryChatMemoryRepository();
    }

    @Bean
    @ConditionalOnMissingBean
    ChatMemory chatMemory(ChatMemoryRepository chatMemoryRepository) {
        return MessageWindowChatMemory.builder().chatMemoryRepository(chatMemoryRepository).build();
    }
}

这里重点提醒一下:@ConditionalOnMissingBean注解的作用是“当容器中没有该Bean时才自动注册”,这意味着咱们可以根据自己的需求,在项目配置中自行替换组件,灵活性拉满。

第二步:挂载Memory Advisor(将记忆组件绑定到ChatClient)

注册好全局记忆组件后,咱们需要将它通过拦截器挂载到ChatClient上,这样ChatClient就能自动调用记忆功能了,直接上Controller代码,注释详细,大家可以直接复制实操:

/**
 * 自动记忆功能实战(可直接复制到项目中测试)
 *
 * @author 直奔標杆(CSDN),专注Java AI转型实战,与大家共同进步
 */
@RestController
public class AutoMemoryController {

    private final ChatClient chatClient;

    // 构造器注入 Builder 和 ChatMemory(推荐构造器注入,更规范)
    public AutoMemoryController(ChatClient.Builder builder, ChatMemory chatMemory) {
        this.chatClient = builder
                // 为ChatClient挂载全局记忆拦截器,实现自动记忆
                .defaultAdvisors(MessageChatMemoryAdvisor.builder(chatMemory).build())
                .defaultSystem("你是一个贴心的私人助理,你需要努力记住用户的偏好。")
                .build();
    }

    @GetMapping("/api/chat")
    public String chatWithMemory(@RequestParam String msg) {
        // 重点:配置Advisor后,每次call()前后都会自动读写chatMemory,无需手动处理
        return chatClient.prompt()
                .user(msg)
                .call()
                .content();
    }
}

测试验证(必做!加深理解)

咱们分两步测试,就能看到效果,非常直观:

  1. 先访问接口:/api/chat?msg=我叫张三,我最喜欢吃火锅。

  2. 再访问接口:/api/chat?msg=我是谁,我喜欢吃什么?

大家会发现,AI终于不再失忆,能准确回答出你的名字和喜好——这就是Advisor和ChatMemory的作用,自动帮咱们拼接了上下文!

关键优化:解决记忆“串线”问题(多租户隔离,生产级必备)

上面的代码虽然能实现自动记忆,但在真实服务器环境中会出现致命问题:咱们只注册了一个全局ChatMemory,如果张三和李四同时访问/api/chat接口,就会出现“串记忆”的情况——张三能看到李四的聊天记录,李四也能看到张三的,直接导致业务异常,这是生产环境绝对不能容忍的。

要实现多租户(多用户)的物理隔离,核心就是给每个用户分配一个专属的“记忆抽屉”,关键操作就是:每次请求时,通过ChatMemory.CONVERSATION_ID告诉Advisor,当前对话属于哪个用户。

咱们稍微改造一下Controller,就能完美解决这个问题,代码如下(重点修改部分已标注,可直接替换测试):

    /**
     * 多租户隔离版聊天接口(生产级可用)
     * @param userId 用户唯一标识(比如登录用户的token、uuid等,确保唯一)
     * @param msg 用户的新问题
     */
    @GetMapping("/api/chat/isolated")
    public String chatWithIsolatedMemory(
            @RequestParam String userId,
            @RequestParam String msg) {

        return chatClient.prompt()
                .user(msg)
                // 重点:动态传入用户专属会话ID,实现记忆物理隔离(取值参考BaseChatMemoryAdvisor#getConversationId)
                .advisors(a -> a.param(ChatMemory.CONVERSATION_ID, userId))
             // 备注:参数设置在call()时生效,可参考DefaultChatClientUtils#toChatClientRequest源码
                .call()
                .content();
    }

测试验证(多租户隔离效果)

按以下步骤测试,就能确认隔离效果,建议大家实际操作一遍:

  1. 访问:/api/chat/isolated?userId=zhangsan&msg=我叫张三

  2. 访问:/api/chat/isolated?userId=lisi&msg=我叫李四

  3. 交叉访问:/api/chat/isolated?userId=lisi&msg=你知道张三吗?

测试后会发现,李四的对话中无法获取张三的信息,多用户记忆物理隔离完美生效,再也不用担心串线问题!

进阶实战:从内存到JDBC数据库持久化(生产级落地关键)

咱们前面用到的InMemoryChatMemory,是将数据存在JVM的ConcurrentHashMap中,这就意味着:只要Spring Boot服务重启,所有的聊天记录都会丢失——这在开发测试环境没问题,但在生产环境中是绝对不可接受的。

Spring AI提供了官方JDBC存储方案,咱们只需几行代码,就能将聊天记录永久存入MySQL或PostgreSQL数据库,彻底解决内存丢失问题,直接适配生产环境,咱们一步步实操:

1. 引入JDBC相关依赖(pom.xml中添加)

首先引入JDBC记忆库的官方Starter和MySQL驱动,版本可根据自己的项目需求调整,代码如下:

<!-- JDBC 记忆库官方 Starter(Spring AI官方提供,直接引入即可) -->
<dependency>
    <groupId>org.springframework.ai</groupId>
    <artifactId>spring-ai-starter-model-chat-memory-repository-jdbc</artifactId>
</dependency>
<dependency>
    <groupId>com.mysql</groupId>
    <artifactId>mysql-connector-j</artifactId>
    <scope>runtime</scope>
    <version>${mysql.version}</version>
</dependency>

2. 配置数据库连接(application.yml中配置)

在配置文件中填写MySQL连接信息,同时配置memory的初始化策略,重点是initialize-schema的设置,代码如下(注释详细,可直接复制修改):

spring:
  ai:
    chat:
      memory:
        repository:
          jdbc:
#           初始化数据库:默认值为embedded(仅适配H2、HSQL等嵌入式数据库)
#           改为always,Spring AI启动时会自动创建表,无需手动建表
            initialize-schema: always 
  datasource:
    driver-class-name: com.mysql.cj.jdbc.Driver
    url: jdbc:mysql://localhost:3306/springai?serverTimezone=Asia/Shanghai&useUnicode=true&characterEncoding=utf-8&allowPublicKeyRetrieval=true&useSSL=false&allowMultiQueries=true
    username: root  # 替换为自己的MySQL用户名
    password: 123456 # 替换为自己的MySQL密码

重点提醒:配置initialize-schema: always后,Spring AI启动时会自动在MySQL中创建名为spring_ai_chat_memory的表,无需咱们手动建表,省去繁琐操作。

3. 将Memory库切换为JDBC驱动(手动配置,可选)

咱们可以手动构建ChatMemory,将存储方式切换为JDBC,代码如下(可直接复制到项目中,注释详细,方便理解):

package com.uka.springai.demo;

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.ai.chat.memory.repository.jdbc.MysqlChatMemoryRepositoryDialect;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.jdbc.core.JdbcTemplate;
/**
 * JDBC 存储记忆配置(生产级实战,可直接复用)
 *
 * @author 直奔標杆(CSDN),分享Java AI转型实战经验,与大家共同成长
 */
@Configuration
public class AiMemoryConfig {

    @Bean
    public ChatMemory chatMemory(JdbcTemplate jdbcTemplate) {
        // 1. 构建基于MySQL语法的JDBC记忆库,也可通过@Bean覆盖默认的内存存储
        JdbcChatMemoryRepository repository = JdbcChatMemoryRepository.builder()
                .jdbcTemplate(jdbcTemplate)
                // 重点:设置MySQL方言,适配MySQL数据库
                .dialect(new MysqlChatMemoryRepositoryDialect())
                .build();

        // 2. 将JDBC记忆库注入到滑动窗口策略中
        return MessageWindowChatMemory.builder()
                .chatMemoryRepository(repository)
                .build();
    }
}

配置完成后,再调用之前的/api/chat/isolated接口,所有的对话记录都会被永久存入MySQL数据库——即使服务器断电、重启,只要传入相同的userId,大模型就能精准回忆起之前的所有对话,彻底解决记忆丢失问题!

补充知识点:JDBC自动装配(无需手动配置,更便捷)

上面咱们演示的是手动构建ChatMemory,其实还有更便捷的方式:由于咱们引入了spring-ai-starter-model-chat-memory-repository-jdbc,Spring会自动帮咱们装配JdbcChatMemoryRepository,因此上面第3步的手动替换操作,咱们可以选择忽略,Spring会自动覆盖默认的内存存储。

给大家贴出Spring的自动装配代码,方便大家理解底层逻辑(不用手动编写,参考学习即可):

@AutoConfiguration(
    after = {JdbcTemplateAutoConfiguration.class},
  // 关键:在ChatMemoryAutoConfiguration之前执行,实现对内存存储的覆盖
    before = {ChatMemoryAutoConfiguration.class}
)
@ConditionalOnClass({JdbcChatMemoryRepository.class, DataSource.class, JdbcTemplate.class})
@EnableConfigurationProperties({JdbcChatMemoryRepositoryProperties.class})
public class JdbcChatMemoryRepositoryAutoConfiguration {
    @Bean
    @ConditionalOnMissingBean
    JdbcChatMemoryRepository jdbcChatMemoryRepository(JdbcTemplate jdbcTemplate, DataSource dataSource) {
       // 根据数据库配置,自动获取对应的数据库方言
        JdbcChatMemoryRepositoryDialect dialect = JdbcChatMemoryRepositoryDialect.from(dataSource);
        return JdbcChatMemoryRepository.builder().jdbcTemplate(jdbcTemplate).dialect(dialect).build();
    }
  // 其余自动装配逻辑省略,大家可自行查看Spring AI源码学习
}

本节课总结(共勉复盘)

本节课咱们重点攻克了大模型“失忆”的核心难题,结合Spring AI的Advisor机制和ChatMemory组件,实现了AI的长久记忆,同时完成了多租户隔离和数据库持久化,直接达到生产级标准,咱们一起复盘一下核心要点:

  1. 依托MessageChatMemoryAdvisor切面机制,彻底省去了手动拼接聊天历史的繁琐代码,实现零代码侵入的自动记忆;

  2. 通过ChatMemory.CONVERSATION_ID,轻松实现多用户会话的物理隔离,解决了记忆串线的生产级痛点;

  3. 引入JdbcChatMemoryRepository,无需修改核心业务代码,就能将记忆从内存平滑迁移到MySQL数据库,彻底解决记忆丢失问题。

建议大家课后多动手实操一遍,把代码跑起来,感受Spring AI的便捷性,遇到问题可以在评论区留言,咱们一起交流解决,共同进步!

下节预告(精彩继续)

随着用户与AI的聊天次数增多,记忆中的上下文会越来越多,这会导致传递给大模型的Prompt越来越大——不仅会让API计费暴涨(Token成本太高),还可能超出大模型的窗口限制,直接报错OOM,这也是生产环境中必须解决的问题。

下一节课,咱们将带来《Java开发者AI转型第八课!避开Token陷阱!Spring AI记忆裁剪源码解析与Token级防溢出核心技巧》,深入剖析Token的本质,教大家如何在Spring AI中精确计算和动态裁剪长文本,守住钱包和服务器的底线!

干货持续输出,咱们下节不见不散!

往期实战内容(连贯学习,效果更佳)

  • Java开发者AI转型第四课!告别硬编码!Spring AI 提示词模板 (Prompt) 注入与多模态识图全攻略

  • Java开发者AI转型第五课!让AI懂规矩!Spring AI 结构化输出 (DTO) 映射与 Flux 流式打字机极速响应

  • Java开发者AI转型第六课!Spring AI 灵魂架构 Advisor 切面拦截与自定义实战

我是直奔標杆,专注Java开发者AI转型实战分享,每天进步一点点,一起向标杆靠拢!觉得本文有用的话,欢迎点赞、收藏、关注,评论区交流实操心得~

Logo

AtomGit 是由开放原子开源基金会联合 CSDN 等生态伙伴共同推出的新一代开源与人工智能协作平台。平台坚持“开放、中立、公益”的理念,把代码托管、模型共享、数据集托管、智能体开发体验和算力服务整合在一起,为开发者提供从开发、训练到部署的一站式体验。

更多推荐