Stories

Detail Return Return

Java ORM 哪家強?10個ORM框架測試對比與選型建議 - Stories Detail

前言:

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個測試用例分別測試)
ORM查詢性能測試結果

ORM插入性能測試結果

總結:

  • 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 後期也可能會適配)。

Add a new Comments

Some HTML is okay.