Hibernate implements optimistic locking by updating a version column (either an incrementing number or a timestamp) and adding the expected current value of that column to the WHERE clause of the statement.
Something equivalent to the following:
UPDATE student
SET name = 'rakesh updated',
version = version + 1,
... other columns omitted
WHERE roll_no = 101
AND version = 1;
Hibernate expects 1 row to be updated in this case. If the number of updated rows is not 1, it will infer that the row (specifically the version) has already been updated by another transaction and throw a StaleObjectStateException.
In the first example you gave, the Student is loaded from the database so the version column will be populated with the current value in the database. That value will be added to the WHERE
clause of the update statement.
However, in the second example a new instance of Student is created but the version column is not explicitly set, so it will be whatever the default is for that field, presumably 0 or null (depending on how you have implemented it).
If, for example, the current value of the version column in the database is 1, but the default value in your newly created Student instance is 0, the generated sql will effectively be
UPDATE student
SET name = 'rakesh updated',
version = version + 1,
... other columns omitted
WHERE roll_no = 101
AND version = 0; -- <-- doesn't match the current version in the db!!
So no rows will be updated and the StaleObjectStateException is thrown.
In order to fix this, you need to either
- load the entity from the database as you done in the first example, and apply the changes to it; or
- load the entity from the database (the managed entity) and copy the current value of the version field from the managed entity to the new Student instance (the detached entity).
For option 2, you will need to use session.evict()
to remove the managed entity from the session before you try to do the update on the new instance, otherwise you could get a NonUniqueObjectException.