Benchmark报告
Jimmer不仅可以为你带来强大的功能,还可以为你带来极致的性能。
基准测试的源代码在此,使用H2的内存数据库,无需任何环境准备即可直接运行。
报告
每秒操作次数
- 横坐标表示每次从数据库中查询到的数据对象的数量。
- 纵坐标表示每秒操作次数。
- 图表
- 数据
Framework | Data count | Ops/s |
---|---|---|
JDBC(ColIndex) | 10 | 662071 |
JDBC(ColName) | 10 | 345169 |
Jimmer(Java) | 10 | 315312 |
Jimmer(Kotlin) | 10 | 309029 |
EasyQuery | 10 | 206634 |
MyBatis | 10 | 68743 |
Exposed | 10 | 92494 |
JPA(Hibernate) | 10 | 90542 |
JPA(EclipseLink) | 10 | 64230 |
JOOQ | 10 | 69408 |
Nutz | 10 | 76904 |
ObjectiveSQL | 10 | 56245 |
Spring Data JDBC | 10 | 20494 |
Ktorm | 10 | 19152 |
JDBC(ColIndex) | 20 | 453226 |
JDBC(ColName) | 20 | 238107 |
Jimmer(Java) | 20 | 224179 |
Jimmer(Kotlin) | 20 | 230622 |
EasyQuery | 20 | 135063 |
MyBatis | 20 | 43940 |
Exposed | 20 | 66934 |
JPA(Hibernate) | 20 | 59926 |
JPA(EclipseLink) | 20 | 33421 |
JOOQ | 20 | 39737 |
Nutz | 20 | 39501 |
ObjectiveSQL | 20 | 29975 |
Spring Data JDBC | 20 | 10704 |
Ktorm | 20 | 9520 |
JDBC(ColIndex) | 50 | 259484 |
JDBC(ColName) | 50 | 125589 |
Jimmer(Java) | 50 | 132673 |
Jimmer(Kotlin) | 50 | 128315 |
EasyQuery | 50 | 92230 |
MyBatis | 50 | 20050 |
Exposed | 50 | 26693 |
JPA(Hibernate) | 50 | 25893 |
JPA(EclipseLink) | 50 | 13634 |
JOOQ | 50 | 18373 |
Nutz | 50 | 16639 |
ObjectiveSQL | 50 | 12455 |
Spring Data JDBC | 50 | 4146 |
Ktorm | 50 | 4385 |
JDBC(ColIndex) | 100 | 130991 |
JDBC(ColName) | 100 | 72424 |
Jimmer(Java) | 100 | 77044 |
Jimmer(Kotlin) | 100 | 74499 |
EasyQuery | 100 | 45502 |
MyBatis | 100 | 10541 |
Exposed | 100 | 19483 |
JPA(Hibernate) | 100 | 13096 |
JPA(EclipseLink) | 100 | 6802 |
JOOQ | 100 | 8145 |
Nutz | 100 | 8903 |
ObjectiveSQL | 100 | 6251 |
Spring Data JDBC | 100 | 2229 |
Ktorm | 100 | 2091 |
JDBC(ColIndex) | 200 | 77725 |
JDBC(ColName) | 200 | 33068 |
Jimmer(Java) | 200 | 41474 |
Jimmer(Kotlin) | 200 | 36656 |
EasyQuery | 200 | 28085 |
MyBatis | 200 | 5310 |
Exposed | 200 | 10008 |
JPA(Hibernate) | 200 | 6900 |
JPA(EclipseLink) | 200 | 3238 |
JOOQ | 200 | 4186 |
Nutz | 200 | 4374 |
ObjectiveSQL | 200 | 3470 |
Spring Data JDBC | 200 | 1025 |
Ktorm | 200 | 1063 |
JDBC(ColIndex) | 500 | 32109 |
JDBC(ColName) | 500 | 16234 |
Jimmer(Java) | 500 | 16371 |
Jimmer(Kotlin) | 500 | 16870 |
EasyQuery | 500 | 11666 |
MyBatis | 500 | 2137 |
Exposed | 500 | 3894 |
JPA(Hibernate) | 500 | 2491 |
JPA(EclipseLink) | 500 | 1361 |
JOOQ | 500 | 1707 |
Nutz | 500 | 2040 |
ObjectiveSQL | 500 | 1259 |
Spring Data JDBC | 500 | 447 |
Ktorm | 500 | 427 |
JDBC(ColIndex) | 1000 | 16188 |
JDBC(ColName) | 1000 | 8346 |
Jimmer(Java) | 1000 | 8831 |
Jimmer(Kotlin) | 1000 | 7932 |
EasyQuery | 1000 | 4427 |
MyBatis | 1000 | 1048 |
Exposed | 1000 | 2082 |
JPA(Hibernate) | 1000 | 1207 |
JPA(EclipseLink) | 1000 | 650 |
JOOQ | 1000 | 832 |
Nutz | 1000 | 957 |
ObjectiveSQL | 1000 | 559 |
Spring Data JDBC | 1000 | 197 |
Ktorm | 1000 | 168 |
每次操作耗时
- 横坐标表示每次从数据库中查询到的数据对象的数量。
- 纵坐标表示每次操作耗时(微秒)。
- 图表
- 数据
Framework | Data count | Time(μs) |
---|---|---|
JDBC(ColIndex) | 10 | 2 |
JDBC(ColName) | 10 | 3 |
Jimmer(Java) | 10 | 3 |
Jimmer(Kotlin) | 10 | 3 |
EasyQuery | 10 | 5 |
MyBatis | 10 | 15 |
Exposed | 10 | 11 |
JPA(Hibernate) | 10 | 11 |
JPA(EclipseLink) | 10 | 16 |
JOOQ | 10 | 14 |
Nutz | 10 | 13 |
ObjectiveSQL | 10 | 18 |
Spring Data JDBC | 10 | 49 |
Ktorm | 10 | 52 |
JDBC(ColIndex) | 20 | 2 |
JDBC(ColName) | 20 | 4 |
Jimmer(Java) | 20 | 4 |
Jimmer(Kotlin) | 20 | 4 |
EasyQuery | 20 | 7 |
MyBatis | 20 | 23 |
Exposed | 20 | 15 |
JPA(Hibernate) | 20 | 17 |
JPA(EclipseLink) | 20 | 30 |
JOOQ | 20 | 25 |
Nutz | 20 | 25 |
ObjectiveSQL | 20 | 33 |
Spring Data JDBC | 20 | 93 |
Ktorm | 20 | 105 |
JDBC(ColIndex) | 50 | 4 |
JDBC(ColName) | 50 | 8 |
Jimmer(Java) | 50 | 8 |
Jimmer(Kotlin) | 50 | 8 |
EasyQuery | 50 | 11 |
MyBatis | 50 | 50 |
Exposed | 50 | 37 |
JPA(Hibernate) | 50 | 39 |
JPA(EclipseLink) | 50 | 73 |
JOOQ | 50 | 54 |
Nutz | 50 | 60 |
ObjectiveSQL | 50 | 80 |
Spring Data JDBC | 50 | 241 |
Ktorm | 50 | 228 |
JDBC(ColIndex) | 100 | 8 |
JDBC(ColName) | 100 | 14 |
Jimmer(Java) | 100 | 13 |
Jimmer(Kotlin) | 100 | 13 |
EasyQuery | 100 | 22 |
MyBatis | 100 | 95 |
Exposed | 100 | 51 |
JPA(Hibernate) | 100 | 76 |
JPA(EclipseLink) | 100 | 147 |
JOOQ | 100 | 123 |
Nutz | 100 | 112 |
ObjectiveSQL | 100 | 160 |
Spring Data JDBC | 100 | 449 |
Ktorm | 100 | 478 |
JDBC(ColIndex) | 200 | 13 |
JDBC(ColName) | 200 | 30 |
Jimmer(Java) | 200 | 24 |
Jimmer(Kotlin) | 200 | 27 |
EasyQuery | 200 | 36 |
MyBatis | 200 | 188 |
Exposed | 200 | 100 |
JPA(Hibernate) | 200 | 145 |
JPA(EclipseLink) | 200 | 309 |
JOOQ | 200 | 239 |
Nutz | 200 | 229 |
ObjectiveSQL | 200 | 288 |
Spring Data JDBC | 200 | 976 |
Ktorm | 200 | 941 |
JDBC(ColIndex) | 500 | 31 |
JDBC(ColName) | 500 | 62 |
Jimmer(Java) | 500 | 61 |
Jimmer(Kotlin) | 500 | 59 |
EasyQuery | 500 | 86 |
MyBatis | 500 | 468 |
Exposed | 500 | 257 |
JPA(Hibernate) | 500 | 401 |
JPA(EclipseLink) | 500 | 735 |
JOOQ | 500 | 586 |
Nutz | 500 | 490 |
ObjectiveSQL | 500 | 794 |
Spring Data JDBC | 500 | 2237 |
Ktorm | 500 | 2342 |
JDBC(ColIndex) | 1000 | 62 |
JDBC(ColName) | 1000 | 120 |
Jimmer(Java) | 1000 | 113 |
Jimmer(Kotlin) | 1000 | 126 |
EasyQuery | 1000 | 226 |
MyBatis | 1000 | 954 |
Exposed | 1000 | 480 |
JPA(Hibernate) | 1000 | 829 |
JPA(EclipseLink) | 1000 | 1538 |
JOOQ | 1000 | 1202 |
Nutz | 1000 | 1045 |
ObjectiveSQL | 1000 | 1789 |
Spring Data JDBC | 1000 | 5076 |
Ktorm | 1000 | 5952 |
由于Spring移除了对OpenJPA的支持,本基准测试不包含JPA(OpenJPA)
实现原则
-
所有框架禁用缓存
-
所有框架关闭日志
-
所有框架每次都打开和关闭连接/会话,不做共享;靠连接池保证性能。
-
接入Spring的连接管理机制。因不同框架API不同,实现方法略有不同。
- 有的使用DataSourceUtils的getConnection和releaseConnection
- 有的使用TransactionAwareDataSourceProxy
但最终效果都一样
-
不使用事务
Exposed
比较特殊,其API强制要求事务,给予假的实现。 -
使用嵌入的H2内数据库,尽可能压缩数据库的消耗,凸显ORM本身的性能,即映射的性能。
价值
一种常见的观点:ORM本身的性能不重要,实际项目中,数据库并非有内嵌内存数据库,所以ORM本身耗时相对于数据库耗时可忽略不计。
反驳:Java19发布后,支持虚拟线程。ORM能尽快完成映射,可以让JVM 去调度更多的虚拟线程,可以提高系统的吞吐量。
为什么如此快?
JDBC有两种编程风格用于从java.sql.ResultSet
中读取值
-
JDBC(ColIndex)
,即按照列索引读取,rs.getString(1)
-
JDBC(ColName)
,按照列名称读取:rs.getString("VALUE_1")
JDBC(ColIndex)
比JDBC(ColName)
性能高,因此,Jimmer本身也将JDBC(ColIndex)
作为底层的访问ResultSet
的手段,为性能指标超越JDBC(ColName)
提供了可能性。
现在要我们重点讨论图表 (选中JDBC原生JDBC指标
) 中的两个案例
-
每次查询10条数据,
JDBC(ColName)
和Jimmer(Java)
的OPS接近。这种场景下,Jimmer和
JDBC(ColName)
性能相似。JDBC(ColName)
的测试代码直接硬编码SQL,而Jimmer采用DSL机制需要动态构建SQL,从这个角度讲Jimmer应该更慢。然而Jimmer把ResultSet
转化为对象的的过程比JDBC(ColName)
快,二者抵消。所以性能相近。当然Benchmark中采用的SQL语句不复杂,如果换用更复杂的SQL,直接硬编码SQL的
JDBC(ColName)
会更快,这也是Jimmer后续版本可以优化的空间。 -
每次查询1000条数据,Jimmer的ops明显高于
JDBC(ColName)
的OPS。在这种情况下,Jimmer把
ResultSet
转化为对象的性能优势得到了充分体现,即便Jimmer使用DSL临时生产SQL拖慢了速度,但整体结果仍然比JDBC(ColName)
快。Jimmer把
ResultSet
转化为对象的 性能非高,主要是因为如下两个原因-
底层使用
JDBC(ColIndex)
-
不使用Java的反射机制为对象动态设置各属性。
在编译时,Jimmer为每一个不可变的实体类型生称一个可修改的
DraftImpl
类,提供了一个通用的__set(PropId propId, Object value)
方法完成和Java反射类似的动态设置对象属性的功能。同时,与编译器每个属性分配一个整数作为id,
DraftImpl
类的的__set
方法内部使用switch
语句实现了对传入的属性id进行鉴别。以生成的Java代码为例@Override
public void __set(PropId prop, Object value) {
int __propIndex = prop.asIndex();
switch (__propIndex) {
case -1:
__set(prop.asName(), value);
return;
case 0:
setId((Long)value);
break;
case 1:
setName((String)value);
break;
case 2:
setEdition((Integer)value);
break;
...略...
}现代编译器对这种基于整数的
switch
语句有充分优化,可以认为Jimmer对实体对象的动态赋值不会比JDBC测试代码中的硬编码慢多少。
以上两个原因,是Jimmer的对象映射性能如此高的最重要的原因。
-