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的对象映射性能如此高的最重要的原因。
-