iT邦幫忙

2026 iThome 鐵人賽

DAY 27
1
Software Development

30天打造一套企業PLM系列 第 27

Day 27:一套程式支援兩種資料庫——PostgreSQL 與 Oracle 並存的血淚

  • 分享至 

  • xImage
  •  

https://ithelp.ithome.com.tw/upload/images/20260913/20161290z828MCPPMS.jpg

系列:30 天打造企業級 PLM|面向:後端|素材:Flyway 雙目錄、自訂 Dialect、真實錯誤碼

問題場景

客戶機房躺著現成的 Oracle 授權與 DBA 團隊,新部署想走 PostgreSQL 省授權費。「兩個都支援」四個字,是本系列踩坑密度最高的承諾。今天把跨庫並存的地雷一次掃完,每一顆都有真實的 ORA- 錯誤碼或炸掉的 migration 當證據。

商業邏輯設計

  • 為什麼要撐兩套:既有 Oracle 資產(授權、DBA、備份體系)不能浪費,新環境走開源省成本。這是財務與組織現實,跟技術偏好無關
  • 隱形成本要先講清楚。每個 schema 變更兩份 migration、每個非平凡查詢兩邊驗、CI 多一道 parity 檢查。支援第二種資料庫的成本不是 +20%,接近 +60%,答應之前要讓決策者看到這筆帳

技術選型與取捨

架構演進:從 Agile 深度綁死 Oracle DB 到雙資料庫自由選擇

在商用 PLM 的歷史中,資料庫鎖定(Vendor Lock-in)是企業最沉重的負擔之一:

  1. Agile 的 Oracle DB 專屬枷鎖:以前 Oracle Agile PLM 深度綁死在 Oracle Database 上,內部充斥著專屬的 PL/SQL Packages、Sequence、CONNECT BY 語法、Oracle Text 索引與 ROWID 機制。企業每年被迫向原廠繳納高昂的 DB 授權與維護費,即使想換到開源資料庫也毫無可能;
  2. 缺乏資料庫中立性:舊架構完全沒有考慮過跨資料庫相容性,資料存取層與 Oracle 專屬語法高度糾纏。

Mini-PLM 決定從根本上打破這道枷鎖:同一套 Java 程式碼,透過 Spring Data JPA、自訂 Dialect 與 Flyway 雙版本管理,同時支援開源的 PostgreSQL 與企業既有的 Oracle Database

Schema 管理:Flyway 雙版本對齊 + CI parity gate

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 一致性的持續檢查器。

型別對映:自訂 Dialect 收斂差異

差異點 症狀 解法
大文字 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),另一家就會在某天爆炸。

踩坑記錄:錯誤碼博物館

ORA-00932:CLOB 欄位遇上 SELECT DISTINCT

實體加了 @Lob 欄位後,某支既有查詢在 Oracle 上炸 ORA-00932: inconsistent datatypes。那支查詢用了 SELECT DISTINCT,而 Oracle 不允許對 CLOB 做 DISTINCT 比較。PostgreSQL 沒這限制、H2 單元測試也不攔,只有 Oracle 生產環境會炸。修法是拿掉冗餘的 DISTINCT(那支查詢的 join 形狀其實不會產生重複列)。沉澱下來的紀律:幫實體加 @Lob 前,先 grep 它有沒有被 DISTINCT 查詢用到。

ORA-30556:DESC 複合索引擋住 ALTER

migration 要 MODIFY 某欄位長度,Oracle 回 ORA-30556: functional index is defined on the column。欄位上明明只有一個普通的複合索引,哪來的函數式索引?答案是 Oracle 的 DESC 複合索引在內部就是函數式索引,索引裡任何一欄都不能 MODIFY。migration 寫法從此固定:guard 內先 DROP index、MODIFY、再重建 index。

ORA-02275:防禦性 guard 要做到欄位級

Oracle migration 的防禦性寫法常見「先查約束存在才加」,但比對固定約束名攔不住 Hibernate 自動命名的 FK(名字是雜湊,每個環境不同),於是重複加約束炸 ORA-02275。正解是 guard 用 USER_CONS_COLUMNS 做欄位級檢查,「這兩欄之間已有 FK 就跳過」,不看約束叫什麼名字。

保留字:mode / type / user / group

SQL 別名用了 modetype 這類單字,某一家資料庫當保留字直接語法錯誤,另一家沒事。紀律:別名一律用業務語義全名(trigger_modefield_type),順手還提升了可讀性。

Java 8 也算「第三種方言」

目標環境 Tomcat 9 加 Java 8,開發機 JDK 較新。isBlank()(Java 11)兩度溜進程式碼,本機綠、目標環境 NoSuchMethodError。同構的教訓:目標執行環境的版本約束要用 CI 編譯目標鎖住(maven release flag),不能靠人記得。

小結

跨庫並存的生存法則:版本 parity 交給 CI,已 applied migration 不可改,差異收斂在 Dialect 層,guard 做到欄位級,加 @Lob 先查 DISTINCT,別名避保留字。每一條的學費都是一個生產事故或一次深夜除錯。明日 Day 28:資安強化實戰——帳號鎖定、授權補洞與攻擊面盤點。


上一篇
Day 26:批次匯入——背景任務、進度回報與稽核設計
下一篇
Day 28:資安強化實戰——帳號鎖定、授權補洞與攻擊面盤點
系列文
30天打造一套企業PLM28
圖片
  熱門推薦
圖片
{{ item.channelVendor }} | {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言