Locking and Versioning


Locking and Versioning Interview with follow-up questions

1. Can you explain the concept of locking in Hibernate?

Locking in Hibernate controls concurrent access to database records, ensuring that multiple transactions do not corrupt shared data. Hibernate supports two main locking strategies:

1. Optimistic Locking

Assumes conflicts are rare. Multiple transactions can read and work on the same entity simultaneously, but a conflict check happens at commit time.

  • Implemented using the @Version annotation on an integer, long, short, or Timestamp field.
  • On update, Hibernate appends WHERE id = ? AND version = ? to the SQL. If no row is matched (another transaction already incremented the version), a jakarta.persistence.OptimisticLockException is thrown.
  • Best for high-read, low-write scenarios.
import jakarta.persistence.*;

@Entity
public class Order {
    @Id
    @GeneratedValue
    private Long id;

    @Version
    private int version;  // Hibernate manages this automatically
}

2. Pessimistic Locking

Assumes conflicts are likely. Locks the database row immediately on read, blocking other transactions from modifying it.

  • Uses SELECT ... FOR UPDATE (or FOR SHARE) at the database level.
  • Applied via LockModeType in JPA:
// Lock a single entity
Order order = em.find(Order.class, id, LockModeType.PESSIMISTIC_WRITE);

// Lock via JPQL
Order order = em.createQuery(
    "SELECT o FROM Order o WHERE o.id = :id", Order.class)
    .setParameter("id", id)
    .setLockMode(LockModeType.PESSIMISTIC_WRITE)
    .getSingleResult();
Lock Mode Behavior
OPTIMISTIC Version check at commit
OPTIMISTIC_FORCE_INCREMENT Always increments version
PESSIMISTIC_READ SELECT FOR SHARE
PESSIMISTIC_WRITE SELECT FOR UPDATE

Key gotcha: Pessimistic locks are released when the transaction ends. Holding them across long operations causes contention — prefer optimistic locking unless write conflicts are frequent.

↑ Back to top

Follow-up 1

What is the difference between optimistic and pessimistic locking?

Optimistic locking is a strategy where concurrent transactions are allowed to access and modify the data simultaneously, assuming that conflicts are rare. It relies on versioning or timestamping to detect conflicts and resolve them. Pessimistic locking, on the other hand, is a strategy where a transaction locks the data it is accessing, preventing other transactions from modifying it until the lock is released. It assumes conflicts are common and aims to prevent them by ensuring exclusive access to the data.

Follow-up 2

How does Hibernate handle locking conflicts?

When a locking conflict occurs in Hibernate, it depends on the type of locking being used. In optimistic locking, Hibernate detects conflicts by comparing the version or timestamp of the data being modified. If a conflict is detected, Hibernate throws an exception, and the transaction can be retried or rolled back. In pessimistic locking, Hibernate acquires locks on the data being accessed, preventing other transactions from modifying it. If a transaction tries to access locked data, it will be blocked until the lock is released.

Follow-up 3

Can you give an example of when you would use optimistic locking versus pessimistic locking?

Optimistic locking is suitable when conflicts are rare, and it is more important to allow concurrent access to the data. For example, in a blog application, multiple users can edit different blog posts simultaneously without blocking each other. Pessimistic locking is suitable when conflicts are common, and it is crucial to prevent concurrent modifications. For example, in a banking application, when transferring funds between accounts, it is important to ensure that no other transaction modifies the accounts involved during the transfer.

Follow-up 4

What are the advantages and disadvantages of each type of locking?

The advantages of optimistic locking are that it allows concurrent access to data, reducing contention and improving performance. It also avoids blocking and allows for better scalability. However, it relies on conflict detection and resolution mechanisms, which can introduce additional complexity. The advantages of pessimistic locking are that it ensures exclusive access to data, preventing conflicts and maintaining data integrity. It provides a straightforward approach to concurrency control. However, it can lead to blocking and reduced concurrency, impacting performance and scalability.

Follow-up 5

How does Hibernate ensure data integrity during concurrent transactions?

Hibernate ensures data integrity during concurrent transactions by using locking mechanisms. When multiple transactions try to access or modify the same data concurrently, Hibernate applies locking to prevent conflicts and maintain data consistency. With optimistic locking, conflicts are detected and resolved based on versioning or timestamping. With pessimistic locking, transactions acquire locks on the data they access, preventing other transactions from modifying it. These locking mechanisms ensure that only one transaction can modify the data at a time, preventing data corruption and maintaining data integrity.

2. What is versioning in Hibernate and why is it important?

Versioning in Hibernate is the mechanism behind optimistic locking: a numeric or timestamp field tracks how many times an entity has been modified, and Hibernate uses it to detect concurrent update conflicts.

How it works:

Add a @Version-annotated field to your entity. Hibernate automatically reads and increments this field — you never update it manually.

import jakarta.persistence.*;

@Entity
public class BankAccount {
    @Id
    @GeneratedValue
    private Long id;

    private BigDecimal balance;

    @Version
    private int version;  // can also be Long, Short, or Instant
}

When Hibernate issues an UPDATE, it adds a version predicate:

UPDATE bank_account SET balance = ?, version = 2 WHERE id = ? AND version = 1

If 0 rows are affected, another transaction has already updated the row. Hibernate throws jakarta.persistence.OptimisticLockException (wraps StaleObjectStateException).

Why versioning is important:

  1. Prevents lost updates — Without it, Transaction B can silently overwrite Transaction A's changes when both read the same initial state.
  2. No database-level blocking — Unlike pessimistic locks, no rows are held between read and write, so throughput is higher.
  3. Conflict visibility — Applications can catch OptimisticLockException and retry or inform the user.
  4. Audit readiness — The version field can signal how many times a record has changed, which is useful for debugging and logging.

Supported types for @Version: int, Integer, long, Long, short, Short, java.sql.Timestamp, java.time.Instant (Hibernate 6+).

Interview gotcha: If you bypass Hibernate and update the row directly via JDBC or native SQL without incrementing the version, the optimistic lock no longer protects that row.

↑ Back to top

Follow-up 1

How does versioning help in optimistic locking?

Versioning helps in optimistic locking by allowing multiple transactions to work on the same entity concurrently without blocking each other. When a transaction updates an entity, it checks the version number of the entity in the database. If the version number in the database matches the version number of the entity being updated, the transaction proceeds and increments the version number. If the version number in the database does not match the version number of the entity being updated, it means that another transaction has modified the entity in the meantime, and a conflict has occurred. In such cases, the transaction can be rolled back or retried, depending on the application's requirements.

Follow-up 2

What are the different ways to implement versioning in Hibernate?

There are two main ways to implement versioning in Hibernate:

  1. Timestamp-based Versioning: In this approach, a timestamp column is added to the entity table. Whenever an entity is modified, the timestamp column is updated with the current timestamp. Hibernate automatically manages the versioning based on the timestamp column.

  2. Integer-based Versioning: In this approach, an integer column is added to the entity table. Whenever an entity is modified, the version column is incremented by one. Hibernate automatically manages the versioning based on the version column.

Both approaches can be used with the @Version annotation in Hibernate to indicate the version property of an entity.

Follow-up 3

Can you explain the role of the @Version annotation?

The @Version annotation is used in Hibernate to indicate the version property of an entity. It is typically applied to an integer or timestamp property that represents the version of the entity. The role of the @Version annotation is to:

  1. Enable Optimistic Locking: The @Version annotation enables optimistic locking for an entity. It tells Hibernate to use the annotated property for versioning and conflict detection during updates.
  2. Manage Versioning Automatically: When an entity is modified and updated in the database, Hibernate automatically increments the version property annotated with @Version. This ensures that only the latest version of an entity is updated and conflicts are detected.

Note that the @Version annotation can be used with both timestamp-based and integer-based versioning approaches in Hibernate.

Follow-up 4

What happens if there is a version mismatch during a transaction?

If there is a version mismatch during a transaction, it means that another transaction has modified the entity being updated since the current transaction read it. In such cases, a version mismatch exception is thrown by Hibernate. The transaction can handle this exception by rolling back or retrying the operation, depending on the application's requirements.

Handling a version mismatch exception typically involves re-reading the entity from the database, comparing the version numbers, and deciding how to proceed based on the application's concurrency control strategy.

Follow-up 5

How does versioning help in preventing lost updates in concurrent transactions?

Versioning helps in preventing lost updates in concurrent transactions by ensuring that only the latest version of an entity is updated. When multiple transactions work on the same entity concurrently, each transaction reads the version number of the entity before making any modifications. If a transaction tries to update an entity with an outdated version number, it means that another transaction has already modified the entity, and a version mismatch occurs.

By detecting version mismatches, Hibernate can prevent lost updates by either rolling back the transaction or retrying the operation. This ensures that only the latest modifications to an entity are persisted, and no updates are lost due to concurrent modifications.

3. How does Hibernate handle deadlock situations?

Hibernate itself does not directly detect or resolve deadlocks — that responsibility falls on the underlying database (PostgreSQL, MySQL, Oracle, etc.). However, Hibernate and the application layer play an important role in minimizing and recovering from them.

What happens at the database level:

When two transactions each hold a lock the other needs, the database's deadlock detector identifies the cycle and kills one transaction (the "victim"), rolling it back and returning an error to the application.

How Hibernate surfaces deadlocks:

The database error propagates as a jakarta.persistence.PessimisticLockException or a org.hibernate.exception.LockAcquisitionException (which wraps a java.sql.SQLException with the database-specific deadlock error code).

Best practices to minimize deadlocks in Hibernate applications:

  1. Consistent lock ordering — Always acquire locks on multiple entities in the same order across all transactions (e.g., always lock the lower-ID row first).
  2. Keep transactions short — Long-running transactions hold locks longer, increasing collision probability.
  3. Prefer optimistic locking@Version-based optimistic locking avoids database-level row locks entirely for most use cases.
  4. Use PESSIMISTIC_WRITE only when necessary — Acquire the lock as late as possible in the transaction.
  5. Avoid user interaction inside transactions — Never wait for user input while holding a database lock.

Retry pattern for deadlock recovery:

int maxRetries = 3;
for (int attempt = 0; attempt < maxRetries; attempt++) {
    try {
        performTransactionalWork();
        break;
    } catch (PessimisticLockException | LockAcquisitionException e) {
        if (attempt == maxRetries - 1) throw e;
        // brief backoff before retry
    }
}

Interview follow-up: Spring's @Retryable (from spring-retry) can automate this retry logic cleanly.

↑ Back to top

Follow-up 1

What strategies can be used to prevent deadlocks?

To prevent deadlocks, you can follow these strategies:

  1. Use proper transaction isolation levels: By choosing the appropriate isolation level for your transactions, you can minimize the chances of deadlocks. For example, using a higher isolation level like Serializable can reduce the likelihood of deadlocks, but it may also impact performance.

  2. Avoid long-running transactions: Long-running transactions increase the chances of deadlocks. Try to keep your transactions short and focused.

  3. Use lock timeouts: Set a timeout for acquiring locks. If a lock cannot be acquired within the specified timeout period, the transaction can be rolled back to avoid potential deadlocks.

  4. Analyze and optimize database schema and queries: Poorly designed database schema or inefficient queries can increase the chances of deadlocks. Analyze and optimize your schema and queries to minimize the possibility of deadlocks.

Follow-up 2

How does Hibernate detect a deadlock?

Hibernate does not directly detect deadlocks. Deadlock detection is the responsibility of the underlying DBMS. The DBMS uses various algorithms and techniques to detect deadlocks. When a deadlock is detected, the DBMS may choose to resolve it automatically by aborting one of the transactions involved in the deadlock.

Follow-up 3

What happens when a deadlock occurs?

When a deadlock occurs, it means that two or more transactions are waiting for each other to release resources that they hold. This results in a circular dependency, where none of the transactions can proceed. The DBMS detects the deadlock and takes appropriate action to resolve it. The resolution can involve aborting one or more transactions involved in the deadlock to break the circular dependency and allow the remaining transactions to proceed.

Follow-up 4

Can you give an example of a situation that could lead to a deadlock?

Sure! Here's an example:

Let's say we have two transactions, T1 and T2, and two resources, R1 and R2. T1 acquires a lock on R1 and then tries to acquire a lock on R2. At the same time, T2 acquires a lock on R2 and then tries to acquire a lock on R1. Both transactions are now waiting for each other to release the resources they hold, resulting in a deadlock.

Follow-up 5

How can we handle deadlocks at the application level?

Handling deadlocks at the application level involves implementing strategies to detect and resolve deadlocks. Some approaches include:

  1. Retry mechanism: When a deadlock is detected, the application can retry the transaction after a certain delay. This gives the DBMS a chance to resolve the deadlock and allows the transaction to proceed.

  2. Deadlock detection and resolution algorithms: The application can implement its own deadlock detection and resolution algorithms. These algorithms can analyze the transaction dependencies and take appropriate action to resolve deadlocks.

  3. Graceful error handling: When a deadlock occurs, the application can handle it gracefully by notifying the user, rolling back the transaction, and providing alternative options or suggestions.

It's important to note that handling deadlocks at the application level is complex and requires careful consideration of the specific requirements and constraints of the application.

4. Can you explain the concept of dirty checking in Hibernate?

Dirty checking is Hibernate's automatic mechanism for detecting which fields of a managed (persistent) entity have changed during a session, so that only the modified columns are written to the database at flush time — without requiring explicit update() calls.

How it works:

  1. When an entity is loaded from the database, Hibernate takes a snapshot of its original field values and stores it in the persistence context (first-level cache).
  2. As the application modifies the entity's fields, those changes accumulate in memory.
  3. When the session is flushed (before a query, at commit, or explicitly), Hibernate compares the current state of each managed entity against its snapshot.
  4. For each entity where a difference is found ("dirty"), Hibernate generates and executes an UPDATE statement covering only the changed columns.
// No explicit update call needed
try (Session session = sessionFactory.openSession()) {
    Transaction tx = session.beginTransaction();

    Product product = session.get(Product.class, 1L);
    product.setPrice(new BigDecimal("29.99"));  // mark as dirty

    tx.commit();  // flush triggers UPDATE automatically
}

What triggers a flush:

  • session.flush() called explicitly
  • Before query execution (to avoid stale reads)
  • On transaction commit

Performance considerations:

  • For large sessions with many entities, dirty checking over every entity can be costly. Use session.setReadOnly(entity, true) for entities you never intend to modify.
  • StatelessSession skips dirty checking entirely, making it ideal for batch processing.
  • In Hibernate 6, bytecode enhancement can replace snapshot-based dirty checking with field-level change tracking, reducing overhead.

Interview gotcha: Dirty checking only applies to managed entities (those associated with an open session). Detached entities are not tracked and won't be auto-updated.

↑ Back to top

Follow-up 1

How does dirty checking relate to versioning and locking?

Dirty checking is closely related to versioning and locking in Hibernate. When an object is loaded from the database, Hibernate also keeps track of its version number. The version number is used to detect concurrent modifications and prevent data inconsistencies.

When dirty checking is performed, Hibernate compares the version number of the object with the version number in the database. If they match, it means that no other transaction has modified the object since it was loaded. If they don't match, it means that another transaction has modified the object, and a concurrent modification exception may be thrown.

Locking is another mechanism in Hibernate that can be used to control concurrent access to objects. By acquiring a lock on an object, a transaction can prevent other transactions from modifying the object until the lock is released. This can be useful in scenarios where strict data consistency is required.

Follow-up 2

What is the role of the Session interface in dirty checking?

The Session interface in Hibernate is responsible for managing the lifecycle of persistent objects and performing dirty checking. It acts as a bridge between the application and the underlying database.

When an object is loaded or saved, it is associated with a Session. The Session keeps track of the object's state and performs dirty checking when necessary. It also provides methods for querying, saving, updating, and deleting objects.

The Session interface provides methods such as save, update, and delete to perform CRUD operations on objects. When these methods are called, Hibernate automatically performs dirty checking to determine which properties have changed and updates the corresponding database records during the transaction commit.

Follow-up 3

How does Hibernate determine if an object is dirty?

Hibernate determines if an object is dirty by comparing its current state with its original state. When an object is loaded from the database, Hibernate keeps a copy of its original state. As the object is modified, Hibernate compares the modified state with the original state to identify the changed properties.

Hibernate uses various mechanisms to track changes to objects. One common approach is to use a combination of field access and bytecode enhancement. By intercepting field access and modifying the bytecode of the persistent class, Hibernate is able to detect changes to properties and mark the object as dirty.

Additionally, Hibernate provides the @DirtyCheck annotation that can be used to customize the dirty checking behavior for specific entities or properties. This annotation allows developers to define custom dirty checking strategies based on their requirements.

Follow-up 4

What are the performance implications of dirty checking?

Dirty checking can have both positive and negative performance implications in Hibernate.

On the positive side, dirty checking reduces the number of database operations required to update persistent objects. Only the changed properties are updated in the database during the transaction commit, which can significantly improve performance, especially in scenarios where large numbers of objects are modified.

On the negative side, dirty checking introduces some overhead in terms of memory usage and CPU cycles. Hibernate needs to keep track of the original state of objects and perform comparisons during dirty checking, which can consume additional memory and processing power. However, the impact of this overhead is usually negligible compared to the benefits of automatic dirty checking.

Overall, dirty checking is a valuable feature in Hibernate that helps to balance performance and convenience when working with persistent objects.

Follow-up 5

Can you disable dirty checking in Hibernate?

Yes, it is possible to disable dirty checking in Hibernate, although it is generally not recommended. Dirty checking is an essential mechanism that ensures data consistency and helps to maintain the integrity of the database.

To disable dirty checking, you can use the @Immutable annotation on the entity class. This annotation tells Hibernate that the object is read-only and should not be considered for dirty checking. When an object is marked as immutable, Hibernate will not track changes to its state and will not update the corresponding database records during the transaction commit.

However, it is important to note that disabling dirty checking can have unintended consequences. If an object is not dirty checked, any modifications made to it will not be persisted to the database, potentially leading to data inconsistencies. Therefore, it is generally recommended to use dirty checking in Hibernate to ensure data integrity and consistency.

5. What is the difference between a lock and a read lock in Hibernate?

In Hibernate/JPA, locking modes range from no lock to exclusive write locks. The key distinction interviewers expect you to articulate is between optimistic read/write modes and pessimistic read/write modes.

LockModeType options (jakarta.persistence):

Lock Mode Type Behavior
NONE No lock; default for reads
OPTIMISTIC (formerly READ) Optimistic Version checked at commit; no DB lock held
OPTIMISTIC_FORCE_INCREMENT (formerly WRITE) Optimistic Version always incremented at commit
PESSIMISTIC_READ Pessimistic SELECT FOR SHARE; blocks writers, allows concurrent readers
PESSIMISTIC_WRITE Pessimistic SELECT FOR UPDATE; blocks both readers (in some DBs) and writers
PESSIMISTIC_FORCE_INCREMENT Pessimistic SELECT FOR UPDATE + increments @Version

Pessimistic READ vs. WRITE:

  • PESSIMISTIC_READ — Acquires a shared lock. Other transactions can still read the row concurrently, but cannot modify it until the lock is released. Maps to SELECT ... FOR SHARE (PostgreSQL) or LOCK IN SHARE MODE (MySQL).
  • PESSIMISTIC_WRITE — Acquires an exclusive lock. No other transaction can read (with a lock) or write to the row. Maps to SELECT ... FOR UPDATE.
// Shared/read lock — others can read concurrently
Product p1 = em.find(Product.class, id, LockModeType.PESSIMISTIC_READ);

// Exclusive/write lock — full isolation
Product p2 = em.find(Product.class, id, LockModeType.PESSIMISTIC_WRITE);

Key point: Both pessimistic modes hold a database-level lock for the duration of the transaction. Prefer PESSIMISTIC_READ when you only need to prevent phantom writes while inspecting data, and PESSIMISTIC_WRITE when you will definitely be updating the row.

Interview follow-up: The old Hibernate 4 LockMode.READ and LockMode.WRITE constants are now deprecated in favour of the standard JPA LockModeType enum.

↑ Back to top

Follow-up 1

What are the implications of using a read lock on data consistency?

Using a read lock can have implications on data consistency. While multiple transactions can read an object concurrently, any modifications to the object are blocked until the read lock is released. This means that if one transaction has acquired a read lock and another transaction tries to modify the object, the modification will be blocked until the read lock is released. This can lead to potential data inconsistencies if the modifications are dependent on the current state of the object.

Follow-up 2

When would you use a read lock versus a regular lock?

You would use a read lock when you want to allow multiple transactions to read an object concurrently, but prevent any transaction from modifying it. This is useful in scenarios where data consistency is not critical and you want to optimize concurrency. On the other hand, you would use a regular lock when you want to acquire exclusive access to an object, preventing other transactions from modifying it.

Follow-up 3

How does a read lock affect concurrency?

A read lock allows multiple transactions to read an object concurrently, increasing concurrency. However, it prevents any transaction from modifying the object until the read lock is released, which can reduce concurrency for write operations.

Follow-up 4

What happens if a read lock and a regular lock are requested on the same object?

If a read lock and a regular lock are requested on the same object, the regular lock takes precedence. This means that the transaction requesting the regular lock will acquire exclusive access to the object, preventing other transactions from reading or modifying it until the regular lock is released.

Follow-up 5

Can you explain the concept of a shared lock?

A shared lock, also known as a read lock, allows multiple transactions to read an object concurrently. It is a lock that allows shared access to the object, but prevents any transaction from modifying it until the lock is released. This is in contrast to an exclusive lock, which prevents any other transaction from reading or modifying the object.

Live mock interview

Mock interview: Locking and Versioning

Intermediate ~5 min Your own free AI key

Your voice and your AI key never touch our servers; the key stays in this browser and is sent only to Google. Only your round scores are saved to track progress.