最近詳細了解了一下ORM,這里記錄一下。
當下我們使用的ORM("對象-關系 映射"(Object Relational Mapping))框架中,JPA/Hibernate/Mybatis占了半邊天,它們都有各自的優勢和使用場景。
最近發現了一個之前從來沒用的ORM框架jOOQ,非常有意思,為數據處理提供了一種全新的方式
一、ORM
1.1、ORM概述
面向對象編程和關系型數據庫,都是目前最流行的技術,但是它們的模型是不一樣的。
面向對象編程把所有實體看成對象(object),關系型數據庫則是采用實體之間的關系(relation)連接數據。很早就有人提出,關系也可以用對象表達,這樣的話,就能使用面向對象編程,來操作關系型數據庫。
簡單說,ORM 就是通過實例對象的語法,完成關系型數據庫的操作的技術,是"對象-關系映射"(Object/Relational Mapping) 的縮寫。
ORM 把數據庫映射成對象。
- 數據庫的表(table) --> 類(class)
- 記錄(record,行數據)--> 對象(object)
- 字段(field)--> 對象的屬性(attribute)
舉例來說,下面是一行 SQL 語句。
SELECT id, first_name, last_name, phone, birth_date, sex FROM persons WHERE id = 10
程序直接運行 SQL,操作數據庫的寫法如下。
1 res = db.execSql(sql); 2 name = res[0]["FIRST_NAME"];
改成 ORM 的寫法如下。
p = Person.get(10); name = p.first_name;
一比較就可以發現,ORM 使用對象,封裝了數據庫操作,因此可以不碰 SQL 語言。開發者只使用面向對象編程,與數據對象直接交互,不用關心底層數據庫。
總結起來,ORM 有下面這些優點。
- 數據模型都在一個地方定義,更容易更新和維護,也利於重用代碼。
- ORM 有現成的工具,很多功能都可以自動完成,比如數據消毒、預處理、事務等等。
- 它迫使你使用 MVC 架構,ORM 就是天然的 Model,最終使代碼更清晰。
- 基於 ORM 的業務代碼比較簡單,代碼量少,語義性好,容易理解。
- 你不必編寫性能不佳的 SQL。
但是,ORM 也有很突出的缺點。
- ORM 庫不是輕量級工具,需要花很多精力學習和設置。
- 對於復雜的查詢,ORM 要么是無法表達,要么是性能不如原生的 SQL。
- ORM 抽象掉了數據庫層,開發者無法了解底層的數據庫操作,也無法定制一些特殊的 SQL。
1.2、命名規定
許多語言都有自己的 ORM 庫,最典型、最規范的實現公認是 Ruby 語言的 Active Record。Active Record 對於對象和數據庫表的映射,有一些命名限制。
(1)一個類對應一張表。類名是單數,且首字母大寫;表名是復數,且全部是小寫。比如,表books
對應類Book
。
(2)如果名字是不規則復數,則類名依照英語習慣命名,比如,表mice
對應類Mouse
,表people
對應類Person
。
(3)如果名字包含多個單詞,那么類名使用首字母全部大寫的駱駝拼寫法,而表名使用下划線分隔的小寫單詞。比如,表book_clubs
對應類BookClub
,表line_items
對應類LineItem
。
(4)每個表都必須有一個主鍵字段,通常是叫做id
的整數字段。外鍵字段名約定為單數的表名 + 下划線 + id,比如item_id
表示該字段對應items
表的id
字段。
二、MyBatis
查看我的博客:
https://blog.csdn.net/yangyangye/category_9227808.html
最大的體會是:MyBatis能讓我控制SQL
對於任何使用關系數據庫的嚴重項目,您無法逃避學習SQL。大多數ORM試圖通過提供更高級別的抽象來使您與SQL隔離。但是作為交換,他們強迫你學習一個新的API或一個抽象的查詢語言。這些API /查詢語言無論如何都會生成SQL,所以唯一的區別是你不知道它們是什么,直到你打開引擎蓋。在很多情況下,我可以用單個語句編寫的查詢將以兩個或更多語句生成,從而降低性能。
現在我突然回到ORM並調整它以產生更高效的查詢。有時候,API不夠豐富,無法滿足所需要的任何事情,無論如何,我都必須逃避SQL語言。為什么要這么麻煩?如果我必須知道SQL,為什么不直接自己寫,直接優化呢?教學框架將結果映射到對象中要容易得多。
猜猜看,這正是MyBatis所做的 - 在這里看到結果圖。它教MyBatis如何將查詢結果映射到一個Transaction對象,並引用一個Account和一個Category。我最終得到更清晰和可理解的代碼。
所以:我希望你能明白為什么我喜歡MyBatis。這將是我未來Java的默認ORM
三、JOOQ
3.1 介紹
JOOQ(Java Object Oriented Query)是一個開源框架,它可以把數據庫模型的基本信息,比如表名,字段名自動生成相應的Java類;並在此基礎上提供了一整套數據處理的API。
jOOQ(Java Object Oriented Querying,即面向Java對象查詢)是一個高效地合並了復雜SQL、類型安全、源碼生成、ActiveRecord、存儲過程以及高級數據類型的Java API的類庫。
Hibernate致力於以面向對象的方式處理數據,隱藏了所有SQL相關處理;
Mybatis則是在XML文件中寫SQL。
jOOQ與它們都不同,它致力於通過java語言以最簡單的形式寫SQL。使用jOOQ DSL(Domain-Specific Language), SQL看起來幾乎是由Java本地支持的。
對於寫Java的碼農來說ORMS再也熟悉不過了,不管是Hibernate或者Mybatis,都能簡單的使用實體映射來訪問數據庫。但有時候這些 ‘智能’的對象關系映射又顯得笨拙,沒有直接使用原生sql來的靈活和簡單,而且對於一些如:joins,union, nested selects等復雜的操作支持的不友好。JOOQ 既吸取了傳統ORM操作數據的簡單性和安全性,又保留了原生sql的靈活性,它更像是介於 ORMS和JDBC的中間層。對於喜歡寫sql的碼農來說,JOOQ可以完全滿足你控制欲,可以是用Java代碼寫出sql的感覺來。就像官網說的那樣 :
get back in control of your sql
SELECT TITLE FROM BOOK WHERE BOOK.PUBLISHED_IN = 2011 ORDER BY BOOK.TITLE
1 create.select(BOOK.TITLE) 2 3 .from(BOOK) 4 5 6 .where(BOOK.PUBLISHED_IN.eq(2011)) 7 8 9 .orderBy(BOOK.TITLE)
從mvnrepository上查詢jOOQ,發現它的第一個版本早在2011年,到現在已經有9個年頭了,社區依然活躍。
jOOQ之所以誕生,大概是人們厭倦了直接寫SQL,用java以流式的方式寫SQL,上手成本並不算高,熟練以后應該很舒服。
3.2. jOOQ解決了什么問題
jOOQ是將SQL語言集成到Java中的一種簡單方法,它使開發人員可以直接用Java快速,安全地編寫高質量的SQL,從而使他們可以專注於自己的業務。
絕大部分數據庫函數,都轉化為了java方法,使用起來自然方便,它還能進行必要的類型檢查,規避了大多數語法錯誤。
它有下面這幾個優勢:
- 數據庫優先,它不提倡隱藏SQL;與Mybatis一樣,以SQL優先,同時可以快速安全的編寫SQL。
- DSL(Domain Specific Language )風格,代碼夠簡單和清晰。遇到不會寫的sql可以充分利用IDEA代碼提示功能輕松完成。
- 類型安全的SQL,它支持列類型檢查、行值表達式檢查、SQL語法檢查。保留了傳統ORM 的優點,簡單操作性,安全性,類型安全等。不需要復雜的配置,並且可以利用Java 8 Stream API 做更加復雜的數據轉換。
- 代碼自動生成,自動生成一份Model類,不需要再手動維護它們。
- SQL標准化,各個數據庫方言存在很多細微差別,jOOQ可以自動進行轉換
- 支持區分不同環境,可以動態切換開發數據庫、測試數據庫等不同環境的數據庫
- 查詢生命周期,jOOQ不嘗試隱藏SQL,圍繞整個生命周期開放了接口,我們可以做日志自定義,事件觸發,SQL轉換等處理。
- 支持編寫存儲過程
- 支持主流的RDMS和更多的特性,如self-joins,union,存儲過程,復雜的子查詢等等。
- 豐富的Fluent API和完善文檔。
- runtime schema mapping 可以支持多個數據庫schema訪問。簡單來說使用一個連接池可以訪問N個DB schema,使用比較多的就是SaaS應用的多租戶場景。
3.3. 更多的例子
java
1 2 3 4 5 6 SELECT AUTHOR.FIRST_NAME, AUTHOR.LAST_NAME, COUNT(*) 7 8 FROM AUTHOR 9 10 JOIN BOOK ON AUTHOR.ID = BOOK.AUTHOR_ID 11 12 13 WHERE BOOK.LANGUAGE = 'DE' 14 15 16 AND BOOK.PUBLISHED > DATE '2008-01-01' 17 18 19 GROUP BY AUTHOR.FIRST_NAME, AUTHOR.LAST_NAME 20 21 22 HAVING COUNT(*) > 5 23 24 25 ORDER BY AUTHOR.LAST_NAME ASC NULLS FIRST 26 27 28 LIMIT 2 29 30 31 OFFSET 1 32 33 34
c
1 reate.select(AUTHOR.FIRST_NAME, AUTHOR.LAST_NAME, count()) 2 3 .from(AUTHOR) 4 5 .join(BOOK).on(AUTHOR.ID.equal(BOOK.AUTHOR_ID)) 6 7 .where(BOOK.LANGUAGE.eq("DE")) 8 9 .and(BOOK.PUBLISHED.gt(date("2008-01-01"))) 10 11 .groupBy(AUTHOR.FIRST_NAME, AUTHOR.LAST_NAME) 12 13 .having(count().gt(5)) 14 15 .orderBy(AUTHOR.LAST_NAME.asc().nullsFirst()) 16 17 .limit(2) 18 19 .offset(1)
//類型檢查 select().from(t).where(t.a.eq(select(t2.x).from(t2)); // Type-check here: ---------------> ^^^^ select().from(t).where(t.a.eq(any(select(t2.x).from(t2))); // Type-check here: -------------------> ^^^^ select().from(t).where(t.a.in(select(t2.x).from(t2)); // Type-check here: ---------------> ^^^^ //表達式類型檢查 select().from(t).where(row(t.a, t.b).eq(1, 2)); // Type-check here: -----------------> ^^^^ select().from(t).where(row(t.a, t.b).overlaps(date1, date2)); // Type-check here: ------------------------> ^^^^^^^^^^^^ select().from(t).where(row(t.a, t.b).in(select(t2.x, t2.y))); // Type-check here: -------------------------> ^^^^^^^^^^ update(t).set(row(t.a, t.b), select(t2.x, t2.y).where(...)); // Type-check here: --------------> ^^^^^^^^^^ insertInto(t, t.a, t.b).values(1, 2); // Type-check here: ---------> ^^^^
- 這里簡單介紹了JOOQ以及為什么要使用它,作為一個強力的ORM框架,其從一個新的方向嘗試更快更好的編寫SQL,很值得我們學習。
參考項目:jOOQ-spring-boot-example,
4、總結
以前項目用過JPA,后面為了控制SQL使用MyBatis(強大組件,包含緩存事務等處理),等以后項目再試試JOOQ吧。
5、參考:
http://www.ruanyifeng.com/blog/2019/02/orm-tutorial.html
https://segmentfault.com/a/1190000006748584?utm_source=tuicool&utm_medium=referral
https://segmentfault.com/a/1190000020490982