數據訪問層(DAL)技術已經非常成熟,從最早的 JDBC 到 Hibernate、MyBatis,再到 Spring Data JPA。我們習慣了用這些框架處理數據。然而,當我們把目光投向“數據”本身的變化時,會發現這些經典的框架似乎正逐漸變為“老一代”。
新一代的挑戰不再僅僅是如何優雅地寫 SQL,而是如何用統一的方式訪問那些不再僅僅存儲在關係型數據庫中的數據。
一、老舊的一代
談論 “老一代” 數據訪問庫,並非貶義,而是指它們誕生的時代背景和核心使命。
在過去以及現在,MyBatis、Hibernate、JPA (Hibernate)、Spring JDBC Template 以及 Apache Commons DbUtils 統治了 Java 開發者的工具箱。它們的共同特徵非常明顯:
- 關係型數據庫:它們的設計初衷就是為了更好地操作 Oracle, MySQL, PostgreSQL 等數據庫。核心邏輯無論是 ORM 映射還是 SQL 模板,都緊緊圍繞着 SQL 標準。
- 專有性:當 NoSQL 興起後,這些框架顯得力不從心。於是我們看到了 MongoDB Java Driver、Elasticsearch RestHighLevelClient 等專有 SDK。
這種格局導致了一個現象:要麼專有,要麼偏向純關係型數據庫。如果你的應用既要查 MySQL 也要查 Elasticsearch,你通常需要引入兩套完全不同的技術棧,寫兩套風格迥異的代碼。
二、破舊嘗試
數據庫技術一直在不斷的迭代,文檔型數據庫(MongoDB)、搜索引擎(Elasticsearch/OpenSearch)、鍵值存儲(Redis)、時序數據庫乃至現在的向量數據庫蜂擁而至。
面對這些 “新東西”,我們熟悉的 “老配方” 也在嘗試去解決新問題。於是我們看到了一系列試圖彌合裂痕的動作:
- Easy-ES:試圖用 MyBatis-Plus 的習慣去操作 Elasticsearch,讓開發者像操作數據庫表一樣操作索引。
- Hibernate OGM:試圖將 JPA 標準延續到 NoSQL 領域,用註解映射非關係型數據。
- Spring Data:通過統一的 Repository 接口抽象,試圖掩蓋底層實現的差異(如
JpaRepository與MongoRepository)。
然而,這些努力雖然在一定程度上緩解了問題,但依然難以掩蓋核心的 困境:
-
套用 SQL 思維: SQL 是關係型數據庫的通用語,但對於具有嵌套結構、倒排索引或圖關係的數據,強行套用 SQL 或表格思維。 像 Easy-ES 這樣的工具雖然方便,但在處理 ES 特有的聚合或複雜 DSL 時,往往還是需要回退到原生 QueryDSL。
-
中間件對 JDBC 的態度: JDBC 本是 Java 屆最成功的抽象之一,但它被打上了深深的關係型數據庫烙印。
- Elasticsearch:曾經嘗試提供 JDBC 支持,但限制諸多(不支持嵌套對象複雜查詢),甚至一度計劃廢棄 SQL 插件。
- MongoDB:雖然有商業版的 JDBC 驅動,但社區生態中大家更習慣用 MongoTemplate 或原聲 BSON API。
-
API 接口層面的持續割裂: 儘管有 Spring Data 這樣的封裝,但底層的割裂依然存在。
- 關係型數據庫:
mapper.selectById(id) - MongoDB:
mongoTemplate.findById(id, class) - Elasticsearch:
client.get(new GetRequest(index, id))
這種割裂不僅增加了學習成本,更讓架構設計變得複雜。我們看似有了一堆工具,但依然沒有一個真正的 “One API” 來統一所有數據訪問。
- 關係型數據庫:
三、One API Access Any DataBase
既然已經走向多元化,數據訪問層(DAL)也必須進化。 新一代數據訪問庫的使命,應當是讓數據訪問重新實現標準化和統一化。
我們不再應該問 “這是什麼數據庫?”,而應該問 “我想在這個數據源上做什麼操作?”。
繼承 JDBC 和 SQL 的普世精神,但打破其對關係型數據庫的枷鎖,這就是新一代數據訪問庫的目標。將其概括為一句就是:"One API Access Any DataBase"。
四、 技術選擇與可行路徑
要實現這個宏大的願景,技術上有兩條主要路徑可供選擇:
路徑 1:統一 DSL
這條路試圖定義一種 “萬能語言”,能同時表達關係查詢、文檔檢索、圖遍歷等邏輯。
-
類似 SQL 的統一 DSL: 本質的難點在於 應用場景 的不同,導致很難有一個統一的 DSL 能在所有場景下適用。比如 Oracle、MongoDB、Elasticsearch 甚至是 Redis 在語法層面達成共識。
-
自然語言: 一種更大膽的假設,基於 LLM 大語言模型直接將自然語言解釋為數據庫引擎可執行的 物理執行計劃,也就是:
自然語言 -> AI -> 算子樹 -> 存儲引擎, AI 在這一過程中充當了 Parser 和 Optimizer 的角色,直接驅動數據庫內核運行具體的物理任務。但在當下,AI 在處理 語義精確性、數據訪問安全性以及複雜邏輯推理 時仍存在“幻覺”風險。 將不確定的 AI 推理直接作用於確定性的數據存儲內核,這將會是一場極具冒險的行為。因此,它更多被視為輔助工具(Copilot),而非底層的、確定性的數據訪問標準。
路徑 2:基本範式的抽象
操作(Operation) 是數據訪問的本質。相比發明新語言或依賴 AI,顯得更加務實且可控。無論數據存在 MySQL 的行、Redis 的 Key、Elasticsearch 的 Document、還是 Neo4j 的節點。
應用程序對數據的使用場景絕大多數時候都逃不出 增(Create)、刪(Delete)、改(Update)、查(Read) 這四個基本範式。
-
行為為中心: 不同於 SQL 關注“如何描述數據”,統一 API 關注“應用想對數據做什麼”。“根據 ID 獲取對象”是一個通用的意圖,無論底層是
SELECT * FROM table WHERE id=?還是GET /index/_doc/id,其業務語義是完全一致的。 -
逃生艙(Escape Hatch): 當然,如果僅有簡單的 CRUD 是無法覆蓋真實業務中 20% 的複雜場景(如深度聚合、圖算法分析)。因此,統一 API 方案必須包含一個 逃生艙機制。 當標準 CRUD 無法滿足需求時,開發者可以藉助 JDBC 的
Statement接口,直接下發專有 DSL(如 Elasticsearch JSON Query)或標準化 SQL。 底層的適配器不僅負責翻譯標準 CRUD,也允許透傳原生 API 或 SDK 調用,確保簡單場景統一化,複雜場景如初般強大。 -
適配器模式: 通過定義一套標準的 API(如
insert,update,query),我們可以在底層通過 適配器模式,將這些標準請求動態 “翻譯” 為不同數據源的方言(Dialect)。
五、 新一代數據訪問庫
我認為 “新一代 Java 數據訪問庫” 應該具是以 One API Access Any DataBase 為核心願景,通過標準化的 API 屏蔽底層數據源的差異,為開發者提供統一、簡單、高效的數據操作體驗為目標。
它不應再區分“這是 ORM”還是“這是 Client”,它就是應用通往數據的統一大門。
六、 dbVisitor 的技術嘗試
dbVisitor 正是基於這一理念誕生的技術嘗試。它的架構設計非常獨特,可以概括為:API訪問庫 + JDBC Driver 的雙層適配架構。
1. API訪問庫:提供統一 API
dbVisitor 的數據訪問層不依賴於具體的 SQL 語法,而是提供高度抽象的 API。例如:查詢構造器
// 無論是 MySQL 還是 MongoDB,代碼看起來都是一樣的
lambdaTemplate.lambdaQuery(User.class)
.eq(User::getAge, 18)
.list();
這一層負責屏蔽 Java 對象與數據模型之間的映射差異。
在此過程中,方言(Dialect) 扮演了關鍵的翻譯官角色。它負責根據上層統一 API 的調用行為(如 .list(), .eq()),生成目標數據源能夠理解的 專有 DSL(如 MySQL 的 SQL、MongoDB 的 BSON Command、Elasticsearch 的 JSON DSL)。
這些生成的 DSL 隨後會被下發到 JDBC Driver 適配層,由對應的驅動執行器完成最終的數據交互。這種機制確保了業務代碼的純淨性,同時保留了對底層特性的精確控制。
2. JDBC Driver 適配器
標準下的選擇性實現,這是 dbVisitor 最具創新性的地方。它沒有重新發明輪子去寫一套私有協議的 Driver,而是選擇 複用 JDBC 標準接口,但對其內涵進行了擴展和適配。
dbVisitor 的解法是引入一個輕量級的驅動適配器框架。它將 JDBC 繁瑣的狀態管理和複雜的接口規範,封裝為簡單的 Request/Response 模型。
開發者不需要去實現一個完整的 JDBC 規範,只需要關注核心的數據交互邏輯,並實現 Request/Response 模型 即可。
這種簡化極大降低了適配新數據源的成本,從而能夠快速適配絕大多數的數據訪問需求。
通過這種“舊瓶裝新酒”的方式,dbVisitor 既保留了 JDBC 生態的兼容性(你可以直接用 Druid 連接池管理 ES 連接),又實現了對 NoSQL 的原生級支持。
七、目前的挑戰
儘管 dbVisitor 的雙層適配架構解決了大部分通用問題,但在實現 "One API" 的征途中,我們依然面臨着一些客觀存在的挑戰:
-
封裝與穿透: Request/Response 模型可以極大地簡化了適配器開發,但沒有任何一種抽象能完美覆蓋所有底層特性。當開發者需要使用某個數據源極其特殊的特性時, 目前的解法是允許使用 JDBC 的
unwrap方法直達底層 SDK。雖然這在一定程度上破壞了封裝性,不作為推薦用法,通過這種“開後門”的方式,保證了在極端場景下問題依然可解。 -
DSL 困境: 並非所有 NoSQL 都有完善的查詢語言,對於那些沒有標準 DSL 的數據庫,dbVisitor 不得不採用一種折中方案:用 DSL 語法來模仿 SDK 的 API 調用結構。 這樣做的好處是保留了近似官方的習慣用法,降低了認知門檻。但壞處也很明顯:不同版本的 SDK API 差異甚至是不兼容的 API 結構。 這會削弱了 DSL 本身的標準化程度和穩定性,加重認知負擔。這個問題只能寄希望於數據庫廠商可以有一個屬於它自己的標準的查詢語法出現,例如 Elasticsearch 的 QueryDSL。
最佳實踐總結:
經過大量的適配實踐,我發現實現 "One API" 的最佳路徑,是依賴於數據庫廠商提供的標準 DSL 或 Shell Commands。
- 如果數據庫本身提供了一套穩定的文本協議(如 SQL, MongoDB Shell Command, Elasticsearch DSL),那麼基於這些標準協議構建適配器,對接底層 API,是最穩健、兼容性最好的方式。
- 對於沒有 DSL 的 數據庫,只需要模仿它 API 的調用方式,提供一個 Shell Command,這一點可以借鑑 MongoDB 的思路。
八、 dbVisitor 實戰演示
為了讓大家更直觀地感受 "One API" 的魅力,以最常見的 CRUD 操作為例,展示 dbVisitor 如何在不同數據源間保持統一的編碼體驗。
1. 統一的 CRUD 體驗
無論底層是 MySQL、MongoDB 還是 Elasticsearch,開發者都可以使用完全一致的 API 進行數據操作。
// 初始化 (僅需更改 Connection 創建方式)
// Connection conn = DriverManager.getConnection("jdbc:mysql://...");
// Connection conn = DriverManager.getConnection("jdbc:dbvisitor:mongo://...");
Connection conn = DriverManager.getConnection("jdbc:dbvisitor:elastic://...");
LambdaTemplate template = new LambdaTemplate(conn);
// 1. 插入數據 (Insert)
UserInfo user = new UserInfo();
user.setId("1001");
user.setName("dbVisitor");
template.insert(UserInfo.class)
.applyEntity(user)
.executeSumResult();
// 2. 查詢數據 (Select)
// 自動適配:MySQL WHERE / Mongo Filter / ES BoolQuery
List<userinfo> list = template.query(UserInfo.class)
.eq(UserInfo::getName, "dbVisitor")
.list();
// 3. 更新數據 (Update)
template.update(UserInfo.class)
.eq(UserInfo::getId, "1001")
.updateTo(UserInfo::getName, "Updated Name")
.doUpdate();
// 4. 刪除數據 (Delete)
template.delete(UserInfo.class)
.eq(UserInfo::getId, "1001")
.doDelete();
2. 底層 API 可達 (Escape Hatch)
當統一 API 無法滿足特殊需求時(例如 Redis 的特定原子操作,或 ES 的特殊聚合),dbVisitor 允許通過 unwrap 機制“穿透”到底層驅動,直接使用原生 SDK。
// 場景 A:Redis 原生調用 (穿透到 Jedis)
if (conn.isWrapperFor(Jedis.class)) {
Jedis jedis = conn.unwrap(Jedis.class);
jedis.set("native_key", "value");
// 使用 Jedis 所有原生能力...
}
// 場景 B:Elasticsearch 原生調用 (穿透到 RestClient)
if (conn.isWrapperFor(RestClient.class)) {
RestClient client = conn.unwrap(RestClient.class);
// 構造原生 Request...
Request request = new Request("GET", "/user_index/_search");
request.setJsonEntity("{\"query\":{\"match_all\":{}}}");
client.performRequest(request);
}
九、 dbVisitor 的生態現狀
目前,dbVisitor 已經實現了對多類數據源的統一訪問支持,正在一步步踐行新一代數據訪問庫的承諾:
- 關係型數據庫:
- 支持 MySQL, PostgreSQL, Oracle, SQLServer, H2, SQLite 等主流關係型數據庫。
- NoSQL 支持:
- Elasticsearch:支持複雜的索引查詢和聚合。
- MongoDB:支持文檔的 CRUD 及複雜過濾。
- Redis:將 Redis 抽象為數據表進行操作。
在 dbVisitor 的世界裏,開發者不再需要為了引入一個新的中間件而重構整個數據訪問層代碼。One API, Access Any DataBase,這不僅僅是一句口號。
如果您正在尋找一種能夠統一管理關係型與非關係型數據訪問的方案,不妨嘗試一下 dbVisitor,體驗新一代數據訪問庫帶來的開發效率變革。
- 項目首頁:https://www.dbvisitor.net/
- 項目源碼:https://gitee.com/zycgit/dbvisitor
- 原文:https://www.dbvisitor.net/blog/new-generation-dbvisitor