StackTraces
Spring AI已驗證

Spring AI JDBC 記憶報 Table doesn't exist

@debug_master13 天前更新閱讀 6 min00

本地 H2 跑得通,換成 MySQL 或 PostgreSQL 就報表不存在。根因是 JdbcChatMemoryRepository 的 schema 自動初始化只認嵌入式數據庫,外部庫不會自動建表。另外 Spring AI 2.0.0 起表結構多了 sequence_id 列,老表不加列會報 Unknown column。

Issue & Env(案發現場與環境)

  • 業務場景:用 JdbcChatMemoryRepository 存多輪對話記憶,本地開發連 H2,測試或生產連 MySQL、PostgreSQL。
  • 現象一:換庫後啟動或首次對話就報表不存在。
  • 現象二:升級 Spring AI 2.0 後,老表報列不存在。
  • 環境:Spring AI 1.1.x / 2.0.0,Spring Boot 3.x,H2 / MySQL / PostgreSQL。

同一份程式碼、同一份設定,只換了 JDBC 連接串,結果就不一樣。問題不在業務程式碼,在表的初始化策略。

兩現象經常被當成兩個獨立問題,其實是同一套記憶表在不同階段暴露出來的。表都沒建,先撞表不存在;表建了但結構舊,就撞列不存在。

StackTrace(堆疊日誌)

MySQL 上表沒建,SQL 直接失敗:

org.springframework.jdbc.BadSqlGrammarException: PreparedStatementCallback; bad SQL grammar [SELECT conversation_id, content, type, timestamp FROM SPRING_AI_CHAT_MEMORY WHERE conversation_id = ? ORDER BY timestamp]
Caused by: java.sql.SQLSyntaxErrorException: Table 'demo.SPRING_AI_CHAT_MEMORY' doesn't exist

升級 Spring AI 2.0 之後,老表缺列:

Caused by: java.sql.SQLSyntaxErrorException: Unknown column 'sequence_id' in 'field list'

這兩類報錯指向同一個根:表狀態和框架預期對不上。前一個是表根本沒有,後一個是表在但結構舊。

Reproduce(復現程式碼)

依賴裡帶上 OpenAI 與兩個數據庫驅動:

<dependency>
    <groupId>org.springframework.ai</groupId>
    <artifactId>spring-ai-starter-model-openai</artifactId>
</dependency>
<dependency>
    <groupId>com.h2database</groupId>
    <artifactId>h2</artifactId>
    <scope>runtime</scope>
</dependency>
<dependency>
    <groupId>com.mysql</groupId>
    <artifactId>mysql-connector-j</artifactId>
    <scope>runtime</scope>
</dependency>

Bean 定義只聲明記憶倉庫,不手工建表:

@Bean
ChatMemory chatMemory(JdbcTemplate jdbcTemplate) {
    return MessageWindowChatMemory.builder()
        .chatMemoryRepository(new JdbcChatMemoryRepository(jdbcTemplate))
        .maxMessages(20)
        .build();
}

這一段程式碼,連 H2 能自動建表,連 MySQL 就報表不存在。

Root Cause(根因分析)

自動建表只認嵌入式庫

JdbcChatMemoryRepository 的 schema 初始化,預設只對嵌入式數據庫執行,比如 H2、HSQL、Derby。MySQL、PostgreSQL 這些外部庫不會自動建表。

所以本地 H2 一切正常,一換庫就崩。這不是設定寫錯,是初始化策略的預設行為。預設值就是給「本地跑得起來」服務的,不是給生產用的。

為什麼 H2 有表 MySQL 沒有

根源在初始化條件。框架啟動時判斷當前數據庫是不是嵌入式類型,是才執行建表腳本。H2 屬於嵌入式,MySQL、PostgreSQL 不屬於,腳本就被跳過。

同一份程式碼,在兩種庫上走出了兩條分支。表現是「換庫就報錯」,實際是初始化根本沒在外部庫上發生過。

2.0 起表結構多了 sequence_id 列

Spring AI 2.0.0 起,記憶表新增了 sequence_id BIGINT 列,用來做會話內消息排序。舊實現靠 timestamp 排序,同一毫秒內寫入的多條消息順序會亂掉,快問快答時尤其明顯。

老表不加這一列,框架查詢裡卻帶了 sequence_id,於是報 Unknown column。表在、列不在,報錯就從「表不存在」換成了「列不存在」。

截至撰寫時未經核實:sequence_id 具體在 2.0.0 的哪個小版本引入,未逐版核對,升級前建議對照升級說明確認一遍。

The Fix(解決方案)

手工建表(推薦)

外部庫上手工建表,別依賴應用自動建。建表語句:

CREATE TABLE IF NOT EXISTS SPRING_AI_CHAT_MEMORY (
    conversation_id VARCHAR(36)  NOT NULL,
    content         TEXT         NOT NULL,
    type            VARCHAR(10)  NOT NULL,
    timestamp       TIMESTAMP    NOT NULL,
    sequence_id     BIGINT       NOT NULL,
    PRIMARY KEY (conversation_id, sequence_id)
);

字段說明:

  • conversation_id:會話 ID,應用側顯式傳入。
  • content:消息正文。
  • type:消息類型,如 USER、ASSISTANT、SYSTEM。
  • timestamp:寫入時間。
  • sequence_id:會話內排序序號,2.0 起用於穩定排序。

類型名按數據庫微調:PostgreSQL 可用 BIGSERIAL 或配序列,MySQL 可用 BIGINT AUTO_INCREMENT,按主鍵策略選一種。

老表升級加列

已有 1.x 表結構,補一列即可:

ALTER TABLE SPRING_AI_CHAT_MEMORY
    ADD COLUMN sequence_id BIGINT NOT NULL DEFAULT 0;

加完建議回填一次歷史數據的序號,再考慮加唯一約束。否則 sequence_id 全是 0,排序又會退化成原來那樣。

讓應用自動初始化

內置的初始化也可以對非嵌入式庫放開:

spring:
  ai:
    chat:
      memory:
        repository:
          jdbc:
            initialize-schema: always

該屬性的取值是 embedded(預設)與 always。生產上更推薦手工遷移:讓應用自動改表風險不小,尤其是有歷史數據和主鍵約束的時候。自動建表適合本地和測試環境,生產用一次性的遷移腳本更可控。

遷移腳本要納入版本管理,跟應用發佈一起走。別在本地手工建好表就算完,測試和生產環境同樣要跑一遍。漏了一處,報錯會在最不該出現的時候冒出來。

References(參考來源)

最後更新於 2026-09-28

這篇幫到你了嗎?

剛解決了一個棘手的報錯?花兩分鐘記錄下來,幫助下一個遇到同樣問題的開發者。

貢獻一條解法