Hibernate Operations


Hibernate Operations Interview with follow-up questions

1. What is the difference between the get() and load() methods in Hibernate?

Both get() and load() retrieve a persistent entity by primary key, but they differ in database access timing and missing-record behavior. This is one of the most frequently asked Hibernate interview questions.

get() / find() — eager, immediate

Hits the database immediately. Returns the fully initialized entity, or null if no row exists with that ID:

// Hibernate native
User user = session.get(User.class, 42L);  // SQL fires now
if (user == null) { /* not found */ }

// JPA standard equivalent (preferred in modern code)
User user = em.find(User.class, 42L);

Both methods check the first-level (session) cache first — if the entity is already loaded, no SQL is issued.

load() / getReference() — lazy, proxy-based

Returns a proxy object immediately, without hitting the database. The SQL fires only when a non-ID property is first accessed. If the row does not exist, ObjectNotFoundException / EntityNotFoundException is thrown at that point — not when load() is called:

// Hibernate native
User proxy = session.load(User.class, 42L);  // no SQL
System.out.println(proxy.getName());         // SQL fires here; throws if missing

// JPA standard equivalent
User proxy = em.getReference(User.class, 42L);

Comparison table

Aspect get() / find() load() / getReference()
Database hit Immediate Deferred (lazy)
Not found Returns null Throws exception on access
Returns Fully initialized entity Proxy
Use case Read/display entity data Foreign key reference only
Checks L1 cache Yes Yes

Key interview gotchas

  • getReference() / load() is useful when you only need to set a relationship without loading the data (e.g., order.setUser(em.getReference(User.class, userId))).
  • In Hibernate 6, session.load() is deprecated — use session.getReference() to align with JPA.
  • Accessing a getReference() proxy after the session is closed throws LazyInitializationException.
  • If the entity is already in the L1 cache, getReference() may return the real entity rather than a proxy.
↑ Back to top

Follow-up 1

When should we use get() over load()?

You should use the get() method over the load() method when you want to immediately fetch the object from the database. This is useful when you need the object to be available immediately and you are certain that the object exists in the database.

For example, if you need to display the details of a specific user on a web page, you can use the get() method to fetch the user object from the database and display the details.

However, if you are not sure whether the object exists in the database or if you want to defer the database hit until the object is actually accessed, you should use the load() method.

Follow-up 2

What happens if the object is not found in the database in both cases?

If the object is not found in the database, the behavior of get() and load() methods is different.

  • The get() method returns null if the object is not found in the database.

  • On the other hand, the load() method throws an exception (specifically, ObjectNotFoundException) if the object is not found in the database.

Therefore, if you are using the load() method, you need to handle the exception appropriately.

Follow-up 3

Can you explain the concept of Lazy Loading in relation to these methods?

Lazy loading is a technique used in Hibernate to defer the loading of an object until it is actually accessed. In the context of get() and load() methods, lazy loading is related to the fetching of associated objects.

When you use the get() method, Hibernate eagerly fetches the associated objects along with the main object from the database. This means that all the associated objects are immediately available.

On the other hand, when you use the load() method, Hibernate lazily fetches the associated objects. It creates a proxy object for the associated objects and defers the actual database hit until the associated objects are accessed.

Lazy loading can help improve performance by reducing the number of database hits, especially when dealing with large object graphs with many associations. However, it can also lead to LazyInitializationException if the associated objects are accessed outside the Hibernate session.

2. Can you explain the difference between save() and persist() methods in Hibernate?

Both save() and persist() make a transient entity persistent, but they differ in API ownership, behavior, and return values. Interviewers ask this to test knowledge of Hibernate-native vs JPA-standard APIs.

save() — Hibernate-native (legacy)

  • Defined on Hibernate's Session interface.
  • Returns the generated identifier (Serializable).
  • Can be called outside a transaction (issues INSERT immediately — risky).
  • If the entity already has an ID set, it may still attempt an INSERT.
Session session = sf.openSession();
session.beginTransaction();
User user = new User("Alice");
Long id = (Long) session.save(user);  // returns generated ID
session.getTransaction().commit();

persist() — JPA standard (preferred)

  • Defined on jakarta.persistence.EntityManager.
  • Returns void.
  • Must be called within an active transaction; throws TransactionRequiredException otherwise.
  • Defers the INSERT until flush time (except with IDENTITY generation, which requires an immediate INSERT to obtain the ID).
EntityManager em = emf.createEntityManager();
em.getTransaction().begin();
User user = new User("Alice");
em.persist(user);               // void — INSERT deferred to flush
em.getTransaction().commit();

Comparison table

Aspect save() persist()
API Hibernate Session (proprietary) JPA EntityManager (standard)
Return Generated ID (Serializable) void
Outside transaction Works (dangerous) Throws exception
Status in Hibernate 6 Retained but discouraged Preferred

Modern recommendation

Use persist() in all new code. Spring Data JPA's repository.save() internally calls persist() for new entities (no ID) and merge() for existing ones (ID present).

Key interview gotchas

  • save() called without a transaction can produce an INSERT that is never rolled back if the surrounding logic fails.
  • With SEQUENCE generation, persist() still hits the database to get the next sequence value even before the flush.
  • Calling save() on an already-persistent entity is a no-op in terms of INSERT but may cause unexpected behavior — use merge() or rely on dirty checking instead.
↑ Back to top

Follow-up 1

What are the return types of these methods?

The return type of the save() method in Hibernate is Serializable, which represents the generated identifier of the saved object. The return type of the persist() method is void, which means it does not return anything.

Follow-up 2

How do these methods behave in a transaction?

Both the save() and persist() methods in Hibernate behave in the same way when used within a transaction. If the transaction is rolled back, any objects saved using either method will not be persisted in the database.

Follow-up 3

When should we use save() over persist()?

You should use the save() method over the persist() method in Hibernate when you need to obtain the generated identifier of the saved object. The save() method returns the generated identifier, while the persist() method does not. If you do not need the generated identifier, it is generally recommended to use the persist() method as it is slightly more efficient.

3. What is the difference between update() and merge() methods in Hibernate?

Both update() and merge() re-synchronize a detached entity's state with the database, but they handle the session state differently — merge() is the JPA standard and the safer choice.

update() — Hibernate-native

Re-attaches a detached entity to the current session. The entity becomes managed again and its full state is scheduled for UPDATE at flush time. Throws NonUniqueObjectException if a managed instance with the same ID is already in the session:

User detached = ...;  // detached — from a closed session
detached.setEmail("[email protected]");

Session session = sf.openSession();
session.beginTransaction();
session.update(detached);  // re-attaches detached object
session.getTransaction().commit();  // UPDATE fires

merge() — JPA standard (preferred)

Copies the state of the detached object onto a managed instance (either an existing one in the session or a newly loaded one). Returns the managed entity — the original detached instance is not modified or re-attached:

EntityManager em = emf.createEntityManager();
em.getTransaction().begin();
User managed = em.merge(detached);   // returns managed copy
// Use 'managed', not 'detached', from here on
em.getTransaction().commit();

Comparison table

Aspect update() merge()
API Hibernate Session (proprietary) JPA EntityManager (standard)
If same ID already in session Throws NonUniqueObjectException Merges state into existing managed instance
Return void Managed entity (new reference)
Detached instance after call Becomes managed Stays detached
UPDATE scope Always full row Full row (dirty checking may optimize)

Modern recommendation

Always use merge(). It is JPA-standard, portable, and handles the duplicate-in-session scenario safely. Spring Data JPA's repository.save() uses merge() for existing entities (those with a non-null ID).

Key interview gotchas

  • After merge(), always use the returned managed instance — the original detached object is not re-attached.
  • session.update() is deprecated in Hibernate 6 in favor of session.merge().
  • merge() may issue a SELECT before the UPDATE if the entity is not already in the session cache.
  • Neither update() nor merge() is needed for entities that are already persistent in the current session — dirty checking handles updates automatically.
↑ Back to top

Follow-up 1

Can you explain with an example when to use update() and when to use merge()?

Sure! Let's say we have a User object that is retrieved from the database and then detached from the session. If we make changes to this detached User object and want to update it in the database, we should use the merge() method. Here's an example:

// Retrieving the User object from the database
User user = session.get(User.class, 1);

// Detaching the User object from the session
session.evict(user);

// Making changes to the detached User object
user.setName("John Doe");

// Updating the User object in the database using merge()
session.merge(user);

Follow-up 2

What happens if we invoke update() on an object that has no corresponding record in the database?

If we invoke the update() method on an object that has no corresponding record in the database, Hibernate will throw an exception. This is because the update() method is used to update a persistent object, meaning it should already exist in the database. If the object does not exist, Hibernate cannot update it and throws an exception.

Follow-up 3

What happens if we invoke merge() on an object that has no corresponding record in the database?

If we invoke the merge() method on an object that has no corresponding record in the database, Hibernate will create a new persistent object with the same state as the detached object and insert it into the database. This means that a new record will be inserted into the database with the same state as the detached object. The merge() method does not throw an exception in this case.

4. How does the saveOrUpdate() method work in Hibernate?

saveOrUpdate() is a Hibernate-native convenience method that determines whether to INSERT or UPDATE based on the entity's identifier and state. It is the predecessor to Spring Data's repository.save().

How it works

Hibernate applies the following logic:

  1. If the identifier is null (or zero for primitives) → treats the entity as transient and calls save() (INSERT).
  2. If the identifier is set → treats the entity as detached and calls update() (UPDATE).
  3. If the entity is already in the session cache → no-op or refresh.
Session session = sf.openSession();
session.beginTransaction();

User newUser = new User();
newUser.setName("Alice");
session.saveOrUpdate(newUser);   // INSERT — id is null

User existing = new User();
existing.setId(5L);
existing.setName("Bob");
session.saveOrUpdate(existing);  // UPDATE — id is set

session.getTransaction().commit();

Hibernate 6 status

saveOrUpdate() is deprecated in Hibernate 6. The recommended replacements are:

  • session.persist() for new (transient) entities.
  • session.merge() for detached entities.
  • Spring Data JPA: repository.save() encapsulates the same logic using Persistable or @Version checks.

Spring Data JPA equivalent

Spring Data's CrudRepository.save() implements the same pattern:

// Spring Data — works for both new and existing entities
userRepository.save(user);
// Internally: persist() if isNew(), merge() otherwise

isNew() is determined by: null ID, or implementing Persistable with a custom isNew() method, or using @Version field (null version = new).

Key interview gotchas

  • saveOrUpdate() can cause NonUniqueObjectException if a managed instance with the same ID exists in the session — the same pitfall as update().
  • Relying on a null ID to detect "new" entities only works with auto-generated IDs; if IDs are assigned manually, use Persistable or @Version.
  • In Spring Data, save() returns the managed entity after persist/merge — always use the returned value, not the original object.
↑ Back to top

Follow-up 1

What happens if the object does not exist in the database?

If the object does not exist in the database, the saveOrUpdate() method will save it as a new record. It will generate a new identifier for the object and insert it into the database as a new row.

Follow-up 2

What happens if the object already exists in the database?

If the object already exists in the database, the saveOrUpdate() method will update its state with the values from the object in the session cache. It will generate an update statement and execute it to update the corresponding row in the database.

Follow-up 3

Can you explain the concept of 'dirty checking' in relation to this method?

In Hibernate, 'dirty checking' is the mechanism used to detect changes made to an object's state. When the saveOrUpdate() method is called, Hibernate automatically performs dirty checking to determine if the object needs to be saved or updated. It compares the current state of the object with its original state (which is stored in the session cache) to identify any changes. If there are changes, Hibernate generates the appropriate SQL statements to persist those changes in the database.

5. Can you explain the evict() and remove() methods in Hibernate?

evict() and remove() both operate on entities in the session, but they serve opposite purposes: evict() detaches an entity without touching the database, while remove() marks it for deletion from the database.

evict() / detach() — removes from session cache only

Detaches the entity from the session. The object moves from the persistent to the detached state. The database row is unchanged. No SQL is generated:

User user = session.get(User.class, 1L);  // persistent
session.evict(user);                        // detached — no SQL
// Changes to user after this point will NOT be flushed

// JPA standard equivalent
em.detach(user);

Common use case: batch processing, where you flush() + evict() (or clear()) in a loop to control memory usage.

remove() / delete() — deletes from database

Marks the entity for deletion. The entity moves from persistent to removed state. A DELETE SQL is issued at flush time:

// JPA standard (preferred)
User user = em.find(User.class, 1L);
em.remove(user);  // DELETE FROM users WHERE id = 1 at flush

// Hibernate native (legacy, deprecated in H6)
session.delete(user);

The entity must be in the managed (persistent) state. Passing a detached instance to remove() throws IllegalArgumentException.

Comparison

Aspect evict() / detach() remove() / delete()
Effect on session Removes from L1 cache Marks as removed
Effect on database None DELETE at flush
Entity state after Detached Removed
SQL generated None DELETE

Key interview gotchas

  • evict() before flush() discards unwritten changes — this is usually a bug unless intentional.
  • session.delete() is deprecated in Hibernate 6; use session.remove() which mirrors the JPA API.
  • Evicting is for memory/performance management; removing is for data deletion — they are conceptually unrelated despite both touching the session.
  • After remove(), the entity's ID is still set on the Java object, but accessing the removed entity through the session after flush will not find it.
↑ Back to top

Follow-up 1

What is the purpose of these methods?

The purpose of the evict() method is to remove an object from the session cache, which can be useful when you want to detach an object from the session and prevent any changes made to it from being persisted to the database. The purpose of the remove() method is to delete an object from the database.

Follow-up 2

What happens to the object state after these methods are invoked?

After the evict() method is invoked, the object becomes detached from the session. This means that any changes made to the object will not be synchronized with the database. After the remove() method is invoked, the object is marked for deletion and will be deleted from the database when the transaction is committed.

Follow-up 3

Can you explain the concept of 'detached objects' in relation to these methods?

In Hibernate, a detached object is an object that is no longer associated with a session. It means that the object is not being managed by Hibernate and any changes made to the object will not be automatically persisted to the database. The evict() method can be used to detach an object from the session and make it a detached object. The remove() method, on the other hand, does not detach the object from the session, but marks it for deletion, which means it will be deleted from the database when the transaction is committed.

Live mock interview

Mock interview: Hibernate Operations

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.

Next lesson Integration with Spring →