Optimistic locking with @Version
Stop concurrent updates from silently overwriting each other, without holding row locks.
Open this lesson in the learning hubKey points
- Two users load the same row and both save. Unprotected, the second write erases the first without a sound - a lost update.
- Add a
@Versionfield (long,intorInstant). Hibernate increments it on every flush; never set it yourself. - Every UPDATE becomes
... where id = ? and version = ?. Zero rows affected means somebody else got there first. - You then get an
OptimisticLockException, wrapped by Spring asObjectOptimisticLockingFailureException. Reload, retry, or tell the user. - Optimistic means no lock is held, so it scales. Use
LockModeType.PESSIMISTIC_WRITEonly for short, genuinely hot contention. - Send the version to the browser and back with the form. That also catches edits made while the page sat open.
Example
@Entity
public class Account {
@Id @GeneratedValue private Long id;
private long balanceCents;
@Version
private long version; // Hibernate bumps this on every flush
public void withdraw(long cents) {
if (cents > balanceCents) throw new InsufficientFundsException();
balanceCents -= cents;
}
}
@Service
public class TransferService {
@Retryable(retryFor = ObjectOptimisticLockingFailureException.class, maxAttempts = 3)
@Transactional
public void withdraw(Long accountId, long cents) {
em.find(Account.class, accountId).withdraw(cents);
// update account set balance_cents=?, version=? where id=? and version=?
}
}
One extra column turns a silent lost update into a loud, handleable failure.
This is a reading copy. The full lesson — with the visual explainer, the interactive lab and a Run button for the code — lives in the Hibernate course, and every lesson in it is listed on the Hibernate contents page.