Imagine having a tool that can automatically detect if you are using JPA and Hibernate properly.
Hypersistence Optimizer is that tool!
In this article, we are going to see what is the best way to map the entity version property with JPA and Hibernate.
Most often, we overlook the basic entity type mappings, focusing more on associations or querying options. However, basic types can also have a significant impact on application performance, especially if the type in question is used in many entity mappings.
Hibernate increments the version column value, so now the next value is -2147483648:
p.id AS id1_0_0_,
p.quantity AS quantity2_0_0_,
p.version AS version3_0_0_
p.id = 1
quantity = 9,
version = -2147483648
id = 1 AND
version = 2147483647
So, the value overflows when reaching the maximum value.
The best way to map the version property with JPA and Hibernate
However, using an integer container for the version property might be too much as the goal of the version property is to ensure that, if the entity state changed, the never version column value is different than the one we have loaded from the database upon fetching the entity.
If you want a detailed explanation of how the entity version property is used by the Hibernate optimistic locking mechanism when the read and write happen either in the scope of the same database transaction or in separate transactions and JPA Persistence Contextx, then you should definitely read this article.
Therefore, the version column effectiveness lies in the probability of not finding the same entity version while the row itself has changed due to concurrent modifications.
So, theoretically, there can be so many changes in between the entity read and write so that, by the time we write the entity back to the database, the values have rotated to the very same value we read from the database.
In the case of an integer column, we can accommodate 4294967294 changes in between the read and the write.
But, that’s way too much.
Even if we allow 100 concurrent database transactions and all these transactions modify the very same database record because only one transaction can modify a record at a time, the probability of reaching the same value due to overflowing is given by the average transaction time.
So, even if the average transaction time is just 10 ms, it will take 42,949,672.96 seconds to reach the same version column value. That’s 497 days.
However, between the read and the write of a database record, there will surely be way less than 497 days. Even if the read and the write happen in separate database transactions like in the following example:
The user’s think-time might be limited by the HTTP session timeout period anyway, and even for batch processing tasks, the time between a read and a successive write will not take very long, even if retries are being employed.
Therefore, we can choose a short or smallint column type for the version property:
private short version;
The short column takes 2 bytes and accommodates 65,536 possible values. So, with an average transaction of just 10 ms, it will take 655 seconds before we hit the very same value we read previously, but which has been changed 65,536 times ever since.
However, in reality, the average transaction time is higher than 10 ms, and not every transaction wants to modify the same record we have previously read before. Therefore, the time it takes to modify an entity 65,536 times will take way more than 655 seconds.
If you enjoyed this article, I bet you are going to love my Book and Video Courses as well.
Using a short primitive type for the entity version property and a smallint column type on the database side is usually a much better choice than opting for an int or a long property.
By using a more compact column type, we can save up space both on disk and in memory. This way, the database server can accommodate more entity records per in-memory page, so more rows can be ultimately stored in the buffer pool.
So, the best way to map an entity version property with JPA and Hibernate is to choose the right column type based on how often the entity is modified. Most often, a smallint type is sufficient. If the entity is rarely changed and the database supports a tinyint column type (e.g. MySQL), then you should choose a byte version type which will still allow you to benefit from the optimistic locking mechanism while only adding a one-byte overhead to the underlying row storage.
Based on my book, High-Performance Java Persistence, this workshop teaches you various data access performance optimizations from JDBC, to JPA, Hibernate and jOOQ for the major rational database systems (e.g. Oracle, SQL Server, MySQL and PostgreSQL).