
系列:30 天打造企業級 PLM|面向:後端|素材:Flyway 雙目錄、自訂 Dialect、真實錯誤碼
客戶機房躺著現成的 Oracle 授權與 DBA 團隊,新部署想走 PostgreSQL 省授權費。「兩個都支援」四個字,是本系列踩坑密度最高的承諾。今天把跨庫並存的地雷一次掃完,每一顆都有真實的 ORA- 錯誤碼或炸掉的 migration 當證據。
在商用 PLM 的歷史中,資料庫鎖定(Vendor Lock-in)是企業最沉重的負擔之一:
CONNECT BY 語法、Oracle Text 索引與 ROWID 機制。企業每年被迫向原廠繳納高昂的 DB 授權與維護費,即使想換到開源資料庫也毫無可能;Mini-PLM 決定從根本上打破這道枷鎖:同一套 Java 程式碼,透過 Spring Data JPA、自訂 Dialect 與 Flyway 雙版本管理,同時支援開源的 PostgreSQL 與企業既有的 Oracle Database。
PostgreSQL Migration 序列: V1__init.sql ... V29__...sql
Oracle Migration 序列: V1__init.sql ... V29__...sql
規矩三條。第一,兩套 migration 版本必須對齊,不得只補單一資料庫。人會忘,所以 CI 用 Migration 對齊檢查腳本自動比對兩套資料庫的版本序列,缺一版直接紅燈。第二,絕不修改已 applied 的 V{n}:改了 checksum 不符,所有既有環境的 Flyway 全面卡死,要改就開新版 V{n+1}。這條是用一次生產環境 migration 全面卡死的驚魂夜換來的。第三,ddl-auto 策略分家:postgres 開發期 update 讓迭代快,oracle 一律 validate,schema 完全由 migration 控制。validate 同時是 entity 與兩套 schema 一致性的持續檢查器。
| 差異點 | 症狀 | 解法 |
|---|---|---|
| 大文字 | Oracle CLOB vs PG text | 一律 @Lob + 自訂 PG Dialect 對映 text;禁用 columnDefinition="TEXT"(Oracle validate 直接炸) |
| 二進位密碼欄 | Oracle RAW(16) vs PG bytea | AttributeConverter<String,byte[]> + 自訂 Oracle Dialect(讓 LONGVARBINARY≤2000 對映 raw(len)) |
| 布林 | Oracle 無 boolean(number(1)) | NumericBooleanType 統一對映(Day 10 matrix 的 enabled 欄位) |
原則只有一條:差異收斂在 Dialect 與 Converter 層,entity 保持中立。entity 上出現任何一家資料庫的方言(columnDefinition),另一家就會在某天爆炸。
實體加了 @Lob 欄位後,某支既有查詢在 Oracle 上炸 ORA-00932: inconsistent datatypes。那支查詢用了 SELECT DISTINCT,而 Oracle 不允許對 CLOB 做 DISTINCT 比較。PostgreSQL 沒這限制、H2 單元測試也不攔,只有 Oracle 生產環境會炸。修法是拿掉冗餘的 DISTINCT(那支查詢的 join 形狀其實不會產生重複列)。沉澱下來的紀律:幫實體加 @Lob 前,先 grep 它有沒有被 DISTINCT 查詢用到。
migration 要 MODIFY 某欄位長度,Oracle 回 ORA-30556: functional index is defined on the column。欄位上明明只有一個普通的複合索引,哪來的函數式索引?答案是 Oracle 的 DESC 複合索引在內部就是函數式索引,索引裡任何一欄都不能 MODIFY。migration 寫法從此固定:guard 內先 DROP index、MODIFY、再重建 index。
Oracle migration 的防禦性寫法常見「先查約束存在才加」,但比對固定約束名攔不住 Hibernate 自動命名的 FK(名字是雜湊,每個環境不同),於是重複加約束炸 ORA-02275。正解是 guard 用 USER_CONS_COLUMNS 做欄位級檢查,「這兩欄之間已有 FK 就跳過」,不看約束叫什麼名字。
SQL 別名用了 mode、type 這類單字,某一家資料庫當保留字直接語法錯誤,另一家沒事。紀律:別名一律用業務語義全名(trigger_mode、field_type),順手還提升了可讀性。
目標環境 Tomcat 9 加 Java 8,開發機 JDK 較新。isBlank()(Java 11)兩度溜進程式碼,本機綠、目標環境 NoSuchMethodError。同構的教訓:目標執行環境的版本約束要用 CI 編譯目標鎖住(maven release flag),不能靠人記得。
跨庫並存的生存法則:版本 parity 交給 CI,已 applied migration 不可改,差異收斂在 Dialect 層,guard 做到欄位級,加 @Lob 先查 DISTINCT,別名避保留字。每一條的學費都是一個生產事故或一次深夜除錯。明日 Day 28:資安強化實戰——帳號鎖定、授權補洞與攻擊面盤點。