Spring AI JDBC 持久化聊天记忆报错 No value specified for parameter 2 问题修复

技术栈版本

技术栈版本
Spring AI1.0.0-RC1
MySQL8.0
Spring Boot3.5.4
JDK17

前言

在开发AI商城系统时,我需要为AI对话添加持久化记忆功能。按照Spring AI官方文档,我引入了以下依赖:

<dependency>
    <groupId>org.springframework.ai</groupId>
    <artifactId>spring-ai-starter-model-chat-memory-repository-jdbc</artifactId>
</dependency>

数据库表SPRING_AI_CHAT_MEMORY也严格按照文档创建完成,一切看起来都很正常。然而启动应用测试时,控制台报出以下错误:

Caused by: java.sql.SQLException: No value specified for parameter 2
	at com.mysql.cj.jdbc.exceptions.SQLError.createSQLException(SQLError.java:130) ~[mysql-connector-j-8.0.33.jar:8.0.33]
	at com.mysql.cj.jdbc.exceptions.SQLExceptionsMapping.translateException(SQLExceptionsMapping.java:122) ~[mysql-connector-j-8.0.33.jar:8.0.33]
	at com.mysql.cj.jdbc.ClientPreparedStatement.executeQuery(ClientPreparedStatement.java:989) ~[mysql-connector-j-8.0.33.jar:8.0.33]
	... 155 common frames omitted

控制台异常截图

问题分析:为什么参数2会缺失?

错误信息很明确 - SQL语句需要两个参数但只提供了一个。问题在于,我根本没有手动编写任何SQL!这表明问题可能出现在Spring AI框架的源代码中。

深入排查:追踪Bug的起源

通过调试模式,定位到 JdbcChatMemoryRepository类中的findByConversationId方法:

// 问题代码:只传入了一个参数
@Override
public List<Message> findByConversationId(String conversationId) {
    return this.jdbcTemplate.query(
        dialect.getSelectMessagesSql(), // 需要2个参数的SQL
        new MessageRowMapper(), 
        conversationId                  // 但只传了1个参数
    );
}

缺失参数2

查看MySQL方言实现,发现了问题根源:

public class MysqlChatMemoryRepositoryDialect {
    @Override
    public String getSelectMessagesSql() {
        // 这条SQL需要2个参数!
        return "SELECT content, type FROM SPRING_AI_CHAT_MEMORY 
                WHERE conversation_id = ? ORDER BY `timestamp` DESC LIMIT ?";
    }
}

具体的SQL代码

而顶层接口却只定义了一个参数:

public interface ChatMemoryRepository {
    // 接口只定义了一个参数!
    List<Message> findByConversationId(String conversationId);
}

ChatMemoryRepository 类

这就导致了实现与接口契约不一致 - MySQL方言需要两个参数(conversationId和LIMIT值),但接口只提供了一个参数。

验证猜想

通过IDEA的"Evaluate Expression"功能执行SQL,添加LIMIT值后,可以正确执行:

表达式求值验证

这证实了我的猜想:问题在于参数数量不匹配。

解决方案:自定义MySQL方言

Spring AI文档提供了自定义数据库语法的能力:

ChatMemoryRepository chatMemoryRepository = JdbcChatMemoryRepository.builder()
    .jdbcTemplate(jdbcTemplate)
    .dialect(new PostgresChatMemoryDialect())
    .build();

创建自定义方言类

public class IMysqlChatMemoryRepositoryDialect implements JdbcChatMemoryRepositoryDialect {

    private static final int MAX_MESSAGES_LIMIT = 100;

    @Override
    public String getSelectMessagesSql() {
        // 将LIMIT ?改为固定值,避免参数不匹配
        return "SELECT content, type, conversation_id, `timestamp` " +
               "FROM SPRING_AI_CHAT_MEMORY " +
               "WHERE conversation_id = ? " +
               "ORDER BY `timestamp` DESC LIMIT " + MAX_MESSAGES_LIMIT;
    }
}

自定义方言类实现

配置修复后的聊天记忆存储

@Configuration
public class ChatMemoryConfig {

       @Bean(name = "mySqlChatMemory")
    public ChatMemory mySqlChatMemory(JdbcTemplate jdbcTemplate) {
        ChatMemoryRepository chatMemoryRepository = JdbcChatMemoryRepository.builder()
                .jdbcTemplate(jdbcTemplate)
                .dialect(new IMysqlChatMemoryRepositoryDialect())
                .build();

        return MessageWindowChatMemory.builder()
                .chatMemoryRepository(chatMemoryRepository)
                .maxMessages(10)
                .build();
    }
    
}

配置修复方案

在ChatClient中应用修复

    @Bean
    public ChatClient deepSeekChatClient(
            DeepSeekChatModel model,
            ChatMemory mySqlChatMemory,
            GuideTool guideTool) {

        return ChatClient.builder(model)
                .defaultTools(guideTool)  // 添加工具
                .defaultAdvisors(
                        new SimpleLoggerAdvisor(), // 添加advisor增强环绕通知:日志记录器
                        MessageChatMemoryAdvisor.builder(mySqlChatMemory).build() // 添加聊天记录记忆的环绕增强器
                )
                .build();
    }

配置ChatClient

修复效果验证

完成上述配置后,重新启动应用:

  1. ✅ 之前的SQLException异常消失
  2. ✅ 应用正常启动且运行稳定
  3. ✅ 聊天消息正确持久化到数据库
  4. ✅ AI能够正确回忆对话历史

修复后正常运行

多轮对话记忆

数据持久化

总结与最佳实践

问题本质

这个Bug的本质是实现层与接口层的契约不一致

  1. 接口层只约定一个参数
  2. 实现层需要两个参数
  3. 框架没有做参数数量校验

核心解决方案

// 将动态LIMIT参数改为固定值
return "SELECT ... LIMIT " + FIXED_VALUE;

适用范围

此解决方案适用于:

  • ✅ Spring AI 1.0.0-RC1 版本

技术提示:该方案是临时解决方案,建议在官方修复后及时更新。同时关注Spring AI项目的GitHub issue,了解官方修复进度。


欢迎交流!
如果你在实际项目中也遇到了Spring AI的相关问题,或者有更好的解决方案,欢迎在评论区分享你的经验!

关注我,获取更多Spring AI实战技巧和源码解析!

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值