I think the code is not correct. A parallel running thread finishing the update later, but having read the same counter before, will overwrite the counter and so one count is lost.
It also depends on the transaction isolation level, if you are using SERIALIZATION
or 'REPEATABLE_READ' you are safe, but usually READ COMMITTED
is used, which would show this problem usually with database like Oracle or PostgreSQL.
Also notable is that at least Hibernate is saving the complete entity not only modified columns by default. So changing a different column does not work. You can change this with the @DynamicUpdate
but maybe this hard behavior change has some side effects at least regarding the performance for the dirty checking of the field to flush to the DB.
Solutions:
Correct solutions are:
- Pessimistic locking: Lock the row with an
SELECT ... FOR UPDATE
- bad this can be bad for performance because all writers have to wait and also all readers if on writer is active
- Atomic Updates: Better because it does no use pessimistic locking:
UPDATE photo SET likecount = likecount + 1 WHERE id = :id
- Optimistic locking approaches. But then you have to handle
OptimisticLockingExceptions
and repeat the transaction