Benchmark Report
Not only can Jimmer bring you powerful features, but it can also bring you ultimate performance.
The source code for benchmarking is here, using H2 in-memory database, no environment preparation needed to run directly.
Report
Operations Per Second
- The x-axis indicates the number of data objects queried from the database each time.
- The y-axis indicates the number of operations per second.
- Chart
- Data
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 |
Operation Time
- The x-axis indicates the number of data objects queried from the database each time.
- The y-axis indicates the operation time (microseconds) for each operation.
- Chart
- Data
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 |
Since Spring removed support for OpenJPA, this benchmark test does not include JPA(OpenJPA)
.
Implementation Principles
-
Disable cache for all frameworks
-
Turn off logs for all frameworks
-
For all frameworks, open and close connections/sessions each time, no sharing; rely on connection pools to ensure performance.
-
Use Spring's connection management mechanism. The implementation is slightly different due to the different APIs of different frameworks.
- Some use DataSourceUtils's getConnection and releaseConnection
- Some use TransactionAwareDataSourceProxy
But the end result is the same
-
Do not use transactions
Exposed
is special in that its API can only work with transactions, so there is a fake transaction implementation for it. -
Use embedded H2 database to compress database overhead as much as possible, highlighting the performance of the ORM itself, i.e. the performance of mapping.
Value
A common view: the performance of the ORM itself is not important. In actual projects, the database is not an in-memory database, so the time consumed by the ORM itself is negligible compared to the time consumed by the database.
Rebuttal: After the release of Java 19, virtual threads are supported. The faster the ORM completes the mapping, the more virtual threads the JVM can schedule, which can increase system throughput.
Why so fast?
There are two programming styles in JDBC for reading values from java.sql.ResultSet
-
JDBC(ColIndex)
, i.e. reading by column index,rs.getString(1)
-
JDBC(ColName)
, reading by column name:rs.getString("VALUE_1")
JDBC(ColIndex)
is faster than JDBC(ColName)
, so Jimmer itself also uses JDBC(ColIndex)
as the underlying means of accessing ResultSet
, providing the possibility of exceeding the performance metrics of JDBC(ColName)
.
Now let's focus on discussing the two cases in the charts (select native JDBC metrics):
-
Querying 10 rows of data each time, the OPS of
JDBC(ColName)
andJimmer(Java)
are close.In this scenario, the performance of Jimmer and
JDBC(ColName)
is similar. The test code ofJDBC(ColName)
directly hardcodes the SQL, while Jimmer needs to dynamically construct the SQL using the DSL mechanism, from which perspective Jimmer should be slower. However, the process of Jimmer convertingResultSet
to objects is faster thanJDBC(ColName)
, canceling each other out. So the performance is similar.Of course, the SQL statement used in Benchmark is not complex. If more complex SQL is used, the hardcoded SQL
JDBC(ColName)
will be faster, which is also an area Jimmer can optimize in subsequent versions. -
Querying 1000 rows of data each time, Jimmer's ops are significantly higher than
JDBC(ColName)
's OPS.In this case, the performance advantage of Jimmer converting
ResultSet
to objects is fully demonstrated. Even if Jimmer slowed down due to dynamically generating SQL using DSL, the overall result is still faster thanJDBC(ColName)
.The main reasons why Jimmer converts
ResultSet
to objects so fast are:-
Use
JDBC(ColIndex)
at the bottom layer -
Do not use Java reflection to dynamically set properties for objects.
At compile time, Jimmer generates a modifiable
DraftImpl
class for each immutable entity type, which provides a generic__set(PropId propId, Object value)
method to achieve dynamic setting of object properties similar to Java reflection.At the same time, the compiler assigns each property an integer as id. The
__set
method in theDraftImpl
class uses aswitch
statement internally to identify the passed property id. For example, the generated Java code is:@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;
...Omitted...
}
}Modern compilers have sufficient optimization for such integer-based
switch
statements, so it can be considered that Jimmer's dynamic assignment of entity objects is not much slower than the hardcoded JDBC test code.
-
These two reasons are the most important reasons why Jimmer's object mapping performance is so high.