Skip to main content

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.

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.
note

Since Spring removed support for OpenJPA, this benchmark test does not include JPA(OpenJPA).

Implementation Principles

  1. Disable cache for all frameworks

  2. Turn off logs for all frameworks

  3. For all frameworks, open and close connections/sessions each time, no sharing; rely on connection pools to ensure performance.

  4. Use Spring's connection management mechanism. The implementation is slightly different due to the different APIs of different frameworks.

    But the end result is the same

  5. 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.

  6. 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) and Jimmer(Java) are close.

    In this scenario, the performance of Jimmer and JDBC(ColName) is similar. The test code of JDBC(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 converting ResultSet to objects is faster than JDBC(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 than JDBC(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 the DraftImpl class uses a switch 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.