What is @Version and Why is It Needed
@Version is a JPA annotation for implementing optimistic locking. It automatically tracks entity version and prevents conflicting updates from multiple transactions.
Overview
@Version is a JPA annotation for implementing optimistic locking. It automatically tracks entity version and prevents conflicting updates from multiple transactions.
Junior Level
What is @Version
@Version - annotation for optimistic locking. Automatically tracks entity version and prevents conflicting updates.
@Entity
public class Order {
@Id
private Long id;
@Version
private Integer version; // incremented automatically
private String status;
}
Why It’s Needed
When two transactions update the same object, @Version guarantees one of them gets an error:
Initial: Order(id=1, version=1, status="new")
Transaction 1: reads (version=1)
Transaction 2: reads (version=1)
Transaction 2: updates -> UPDATE WHERE version=1 -> version=2 OK
Transaction 1: updates -> UPDATE WHERE version=1 -> 0 rows -> OptimisticLockException
How It Works - Simply
1. On INSERT: version = 0. On each UPDATE: version = version + 1.
2. On each UPDATE: version = version + 1
3. WHERE clause: WHERE id = ? AND version = ?
4. If version doesn't match -> 0 rows updated -> OptimisticLockException
Middle Level
Version Field Types
@Version
private Integer version; // int - increments by 1
@Version
private Long version; // long - increments by 1
@Version
private Timestamp version; // timestamp - updated with current time
Detailed Example
@Entity
public class Product {
@Id
private Long id;
@Version
private Integer version;
private String name;
private int stock;
}
// Transaction A
Product pA = em.find(Product.class, 1L); // version=1, stock=10
// Transaction B
Product pB = em.find(Product.class, 1L); // version=1, stock=10
pB.setStock(8);
em.flush(); // UPDATE SET stock=8, version=2 WHERE id=1 AND version=1 OK
// Transaction A
pA.setStock(5);
em.flush(); // UPDATE SET stock=5, version=2 WHERE id=1 AND version=1
// -> 0 rows -> OptimisticLockException!
// stock=8 (from B) not overwritten
Common Mistakes
// Manually changing version
order.setVersion(0); // breaks the locking mechanism
// No @Version for important data
@Entity
public class Account {
// no @Version -> two threads can simultaneously change balance
}
// Ignoring OptimisticLockException
try {
em.flush();
} catch (OptimisticLockException e) {
// silently ignored -> data lost
}
Senior Level
Internal Implementation
Hibernate:
- On INSERT: version = 1 (or 0 for some types)
- On UPDATE: version = version + 1
- WHERE clause: WHERE id = ? AND version = ?
- If 0 rows -> OptimisticLockException
For Timestamp:
- version = CURRENT_TIMESTAMP
- WHERE clause: WHERE id = ? AND version = ?
- Timestamp version is risky: if two transactions update entity in the same millisecond, conflict will NOT be detected. In production use Integer/Long, not Timestamp.
OPTIMISTIC_FORCE_INCREMENT
// Increment version without changing entity
em.lock(order, LockModeType.OPTIMISTIC_FORCE_INCREMENT);
// When needed:
// - On child collection change
// - For forced cache invalidation
// - When entity doesn't change but change needs to be recorded
Audit with @Version
@Entity
@EntityListeners(AuditingEntityListener.class)
public class Order {
@Id
private Long id;
@Version
private Integer version;
@LastModifiedDate
private LocalDateTime updatedAt;
// version for optimistic locking
// updatedAt for audit (when was the last change)
}
Performance Implications
@Version overhead:
- +1 column in table
- +1 condition in WHERE on UPDATE
- Practically negligible
Benefits:
- Lost update prevention
- No locks (unlike pessimistic)
- Automatic management
Production Patterns
// Pattern 1: Retry with @Version
@Retryable(value = OptimisticLockException.class, maxAttempts = 3)
@Transactional
public Order updateOrder(OrderDto dto) {
Order order = entityManager.find(Order.class, dto.id());
order.setStatus(dto.status());
return order;
}
// Pattern 2: Refresh and retry
@Transactional
public Order updateWithRefresh(OrderDto dto) {
Order order = entityManager.find(Order.class, dto.id());
try {
order.setStatus(dto.status());
entityManager.flush();
return order;
} catch (OptimisticLockException e) {
entityManager.refresh(order); // get actual data
order.setStatus(dto.status()); // apply to actual
entityManager.flush();
return order;
}
}
Best Practices
@Version on all mutable entities
Retry on OptimisticLockException
Don't change version manually
Monitor conflict frequency
OPTIMISTIC_FORCE_INCREMENT when needed
Without @Version for critical data
Manual version management
Ignoring OptimisticLockException
Pessimistic locking without reason
Interview Cheat Sheet
Must know:
- @Version - annotation for optimistic locking, automatically increments version on UPDATE
- WHERE clause: WHERE id = ? AND version = ? - if 0 rows -> OptimisticLockException
- Types: Integer/Long (increment by 1), Timestamp (updated with current time, risky)
- On INSERT: version = 0, on first UPDATE: version = 1
- OPTIMISTIC_FORCE_INCREMENT - increments version without changing entity
- @Version on all mutable entities - prevents lost updates
Frequent follow-up questions:
- Why is Timestamp risky for @Version? Two updates in same millisecond - conflict not detected
- When OPTIMISTIC_FORCE_INCREMENT? Child collection change, cache invalidation, entity unchanged but change needs recording
- @Version performance overhead? +1 column, +1 condition in WHERE - practically negligible
- Can version be changed manually? No - breaks locking mechanism
Red flags (DO NOT say):
- “Timestamp for @Version in production” - conflict in same millisecond not detected
- “Manual version change” - breaks optimistic locking
- “Without @Version for Account/Balance” - lost updates possible
- “I ignore OptimisticLockException” - data silently lost
Related topics:
- [[17. How to Implement Optimistic Locking in JPA]]
- [[18. How to Implement Pessimistic Locking in JPA]]
- [[15. What Does the refresh() Method Do]]
- [[20. How Do Cascade Operations Work]]