Java - JPA - @Version annotation
How does @Version
annotation work in JPA?
I found various answers whose extract is as follows:
JPA uses a version field in your entities to detect concurrent modifications to the same datastore record. When the JPA runtime detects an attempt to concurrently modify the same record, it throws an exception to the transaction attempting to commit last.
But I am still not sure how it works.
Also as from the following lines:
You should consider version fields immutable. Changing the field value has undefined results.
Does it mean that we should declare our version field as final
?
But still I am not sure how it works?
Let's say an entity MyEntity
has an annotated version
property:
@Entity
public class MyEntity implements Serializable {
@Id
@GeneratedValue
private Long id;
private String name;
@Version
private Long version;
//...
}
On update, the field annotated with @Version
will be incremented and added to the WHERE
clause, something like this:
UPDATE MYENTITY SET ..., VERSION = VERSION + 1 WHERE ((ID = ?) AND (VERSION = ?))
If the WHERE
clause fails to match a record (because the same entity has already been updated by another thread), then the persistence provider will throw an OptimisticLockException
.
Does it mean that we should declare our version field as final
No but you could consider making the setter protected as you're not supposed to call it.
Although @Pascal answer is perfectly valid, from my experience I find the code below helpful to accomplish optimistic locking:
@Entity
public class MyEntity implements Serializable {
// ...
@Version
@Column(name = "optlock", columnDefinition = "integer DEFAULT 0", nullable = false)
private long version = 0L;
// ...
}
Why? Because:
- Optimistic locking won't work if field annotated with
@Version
is accidentally set tonull
. - As this special field isn't necessarily a business version of the object, to avoid a misleading, I prefer to name such field to something like
optlock
rather thanversion
.
First point doesn't matter if application uses only JPA for inserting data into the database, as JPA vendor will enforce 0
for @version
field at creation time. But almost always plain SQL statements are also in use (at least during unit and integration testing).
Every time an entity is updated in the database the version field will be increased by one. Every operation that updates the entity in the database will have appended WHERE version = VERSION_THAT_WAS_LOADED_FROM_DATABASE
to its query.
In checking affected rows of your operation the jpa framework can make sure there was no concurrent modification between loading and persisting your entity because the query would not find your entity in the database when it's version number has been increased between load and persist.
Version used to ensure that only one update in a time. JPA provider will check the version, if the expected version already increase then someone else already update the entity so an exception will be thrown.
So updating entity value would be more secure, more optimist.
If the value changes frequent, then you might consider not to use version field. For an example "an entity that has counter field, that will increased everytime a web page accessed"