前言:
Java 領域的ORM(Object-Relational Mapping)框架有很多,各家的性能和使用體驗如何?本文將對比體驗以下的Java ORM框架,包括Spring JDBC、Spring Data JPA + Hibernate、QueryDSL、jOOQ、GraphQL、MyBatis、MyBatis-dynamic-sql、MyBatis-plus、Fluent-mybatis、MyBatis-flex,以幫助開發者選型。
一、性能測試對比
測試背景:
我們以diboot的操作日誌表為基準,字段十幾個,測試表中數據量約2萬條。分別測試對比以下ORM框架:Spring JdbcTemplate、Spring JdbcClient、Spring data JPA、Mybatis、Mybatis-plus、Mybatis-flex。(其他框架會在使用體驗章節介紹)
測試以下步驟:
- 循環執行 1000 次讀LIKE 查詢,重複多次
- 循環執行 1000 次寫插入,重複多次測試
測試結果:
(注:因Mybatis-plus 和 Mybatis-flex 存在衝突無法共存,所以我們拆分為2個測試用例分別測試)
總結:
- Spring JdbcTemplate 是最接近原生JDBC的性能,以此為基準,Spring 最新的JdbcClient是在JdbcTemplate的基礎上在對象映射上做了輕量封裝,二者的讀寫性能都非常優秀。
- Mybatis 的讀寫性能僅次於Spring JdbcClient,非常優秀。
- Mybatis-plus 的讀性能較好,在Mybatis的基礎上損耗較小,優於Mybatis-flex(二者都是通過Lambda構建查詢)。寫性能上二者相差不大。
- JPA的讀寫性能不如Mybatis-plus(與Mybatis-flex混合測試還會導致無法寫入)。
二、使用體驗對比
Spring JdbcTemplate、JdbcClient
優點: 接近原生JDBC的性能
缺點: 僅提供了基礎的對象映射轉換處理,需要在Java中寫大量的SQL語句,表多的話很難維護
示例:
// 查詢用法示例
List<IamOperationLog> dataList = jdbcClient
.sql("select * from iam_operation_log where business_obj LIKE ?")
.param("%"+keyword+"%")
.query(IamOperationLog.class)
.list();
Spring Data JPA + Hibernate
優點: 有Spring體系的支持,關注對象模型不太關注SQL的簡單場景用起來比較容易
缺點: 過度抽象,隱藏了SQL實現,背後的Hibernate駕馭起來也比較困難,複雜SQL條件構建與擴展都不方便
示例:
// 查詢用法示例
IamOperationLog iamOperationLog = new IamOperationLog().setBusinessObj(keyword);
List<IamOperationLog> dataList = jpaRepository.findAll(Example.of(iamOperationLog));
QueryDSL
優點: 支持APT自動生成構建SQL所需的DSL類;可以作為JPA方案的補充,擴展完善其查詢條件構建等能力
缺點: 國內比較小眾,複雜SQL實現繁瑣
示例:
// 查詢用法示例
List<IamOperationLog> dataList = queryFactory.selectFrom(iamOperationLog)
.where(iamOperationLog.businessObj.like('%'+keyword+'%'))
.fetch();
jOOQ
優點: SQL構建方式相對優雅,寫法接近原生SQL
缺點: 數據庫支持少(開源版僅支持MySQL、PostgreSQL、SQLite等),國內比較小眾
示例:
// 查詢用法示例
// 不使用APT生成DSL輔助類,寫起來還是挺繁瑣的
Query query = create.select(field("BOOK.TITLE"),field("AUTHOR.FIRST_NAME"),field("AUTHOR.LAST_NAME"))
.from(table("BOOK")).join(table("AUTHOR")).on(field("BOOK.AUTHOR_ID").eq(field("AUTHOR.ID")))
.where(field("BOOK.PUBLISHED_IN").eq(2008));
// 使用APT生成DSL輔助類,寫起來相對順暢
Query query = create.select(BOOK.TITLE, AUTHOR.FIRST_NAME, AUTHOR.LAST_NAME)
.from(BOOK).join(AUTHOR)
.on(BOOK.AUTHOR_ID.eq(AUTHOR.ID))
.where(BOOK.PUBLISHED_IN.eq(2008));
List<Object> bindValues = query.getBindValues();
GraphQL
優點: 設計思路新穎
缺點: 只熱過一陣子,把查詢構建(業務邏輯)交給前端註定過於挑戰開發者習慣,尤其是前後端分離場景下
// GraphQL 查詢,注意是:前端構建查詢請求
author(id: "7") {
id
name
avatarUrl
articles(limit: 2) {
name
urlSlug
}
}
MyBatis
優點: 性能優秀,使用簡單,複雜SQL可以寫在XML中方便統一維護,複雜項目更可控
缺點: 手寫SQL過多,缺失通用Mapper、聯表SQL手寫複雜
<!-- 查詢用法示例 -->
<select id="getMatchedLog" resultType="com.diboot.ormpk.entity.IamOperationLog">
select *
from iam_operation_log
where business_obj LIKE #{keyword,jdbcType=VARCHAR}
</select>
MyBatis-dynamic-sql
優點: Mybatis官方出品的支持多表查詢的動態SQL構建解決方案
缺點: 缺少APT自動生成方案,手寫代碼過多,使用起來缺失一點優雅
// 查詢用法示例
SelectStatementProvider selectStatement = select(id, animalName, bodyWeight, brainWeight)
.from(animalData)
.where(id, isIn(1, 5, 7))
.and(bodyWeight, isBetween(1.0).and(3.0))
.orderBy(id.descending(), bodyWeight)
.build().render(RenderingStrategies.MYBATIS3);
List<AnimalData> animals = mapper.selectMany(selectStatement);
MyBatis-plus
優點: Mybatis的擴展框架,性能較好,支持通用Mapper等,單表CRUD、查詢條件構建寫起來比較優雅
缺點: 缺少聯表查詢方案(可使用 Diboot core 內核實現)
// 查詢用法示例
LambdaQueryWrapper<IamOperationLog> queryWrapper = new LambdaQueryWrapper<IamOperationLog>()
.like(IamOperationLog::getBusinessObj, keyword);
List<IamOperationLog> logList = iamOperationLogMPMapper.selectList(queryWrapper);
Fluent-mybatis
優點: 借鑑了jOOQ的實現思路
缺點: 已停更
// 查詢用法示例
StudentQuery query = new StudentQuery().where.userName().eq("u2").end();
List<StudentEntity> users = mapper.listEntity(query);
MyBatis-flex
優點: Mybatis的擴展框架,支持通用Mapper,支持APT生成DSL輔助類,支持多表,動態SQL構建也相對優雅。像是借鑑了眾多ORM的優勢實現的一個既要又要的解決方案。
缺點: 查詢性能還有優化空間(v1.8.x版本),穩定性還有待驗證
// 查詢用法示例
// 1. 不使用APT生成DSL輔助類,Lambda寫起來類似Mybatis-plus
QueryWrapper queryWrapper = QueryWrapper.create().like(IamOperationLog::getBusinessObj, keyword);
List<IamOperationLog> logList = iamOperationLogMFMapper.selectListByQuery(queryWrapper);
// 2. 使用APT生成DSL輔助類,寫起來類似jOOQ
queryWrapper = QueryWrapper.create().from(IAM_OPERATION_LOG).and(IAM_OPERATION_LOG.BUSINESS_OBJ.like(keyword));
logList = iamOperationLogMFMapper.selectListByQuery(queryWrapper);
三、總結與選型建議
Java ORM雖然很多,綜合下來目前主流的還是JPA(Hibernate)和Mybatis兩大陣營。
- Mybatis擁有良好的性能和應對複雜場景的能力,國內使用更廣泛是有理由的,個人建議首選站隊Mybatis,除非你的項目非常簡單。
- 針對Mybatis的不足,如果你追求穩健,可以使用 MyBatis-plus + Diboot 。如果你的開發場景能夠接受嚐鮮,可以使用 Mybatis-flex(Diboot 後期也可能會適配)。