Hibernate and Spring Data JPA Interview Questions: N+1, Lazy Loading and Transactions

By Preeti Verma

For Java backend roles, the database layer is where interviews get practical. Almost every candidate has used Spring Data JPA, so interviewers use Hibernate questions to find out who understands what happens underneath and who only calls repository.save().

These are the questions that come up most, with the points a strong answer makes.

1. What is the difference between JPA, Hibernate and Spring Data JPA?

  • JPA is a specification: a set of interfaces and annotations for object-relational mapping.
  • Hibernate is an implementation of that specification (and has extra features of its own).
  • Spring Data JPA is a layer on top that removes boilerplate: you declare a repository interface and Spring generates the implementation.

One sentence on the relationship is enough: Spring Data JPA uses JPA, and Hibernate is usually the JPA provider doing the real work.

2. What is the N+1 problem, and how do you fix it?

You load a list of N parents with one query, then touch a lazy child collection on each, which triggers N more queries. A page that should need two queries ends up running hundreds.

How to show you have met it in real life: you noticed it from SQL logs or a slow endpoint. Ways to fix it:

  • Join fetch in JPQL: select o from Order o join fetch o.items.
  • @EntityGraph on the repository method to fetch the association in the same query.
  • Batch fetching (@BatchSize or hibernate.default_batch_fetch_size) to load children in groups instead of one by one.
  • DTO projections when you only need a few columns, so you never load full entities at all.

Add the catch: fetch-joining more than one collection can cause a Cartesian product and, with List, a MultipleBagFetchException. A thoughtful answer picks the fix based on the situation.

3. Lazy vs eager loading

Lazy loading fetches an association only when you first access it. Eager loading fetches it immediately with the parent. By default, @OneToMany and @ManyToMany are lazy, while @ManyToOne and @OneToOne are eager. Most teams make everything lazy and fetch explicitly where needed, because eager mappings can silently load huge object graphs.

4. What is LazyInitializationException?

It happens when you touch a lazy association after the persistence context (the session) is closed, for example in a controller after the service method has returned. Fixes: fetch what you need inside the transaction (join fetch or entity graph), map to a DTO inside the service, or keep the session open for the request (the Open Session in View setting, which many teams turn off because it hides query problems and holds database connections longer). Say which you prefer and why.

5. Entity states and the persistence context

An entity can be transient (new, not tracked), persistent/managed (tracked by the context), detached (was managed, context closed) or removed. The first-level cache is the persistence context itself: within one session, loading the same id twice returns the same object without a second query.

The follow-up that separates levels: dirty checking. When a managed entity changes inside a transaction, Hibernate notices at flush time and issues an UPDATE automatically, even if you never call save().

6. save(), saveAndFlush(), flush() and merge()

save makes a new entity persistent or merges a detached one. flush pushes pending changes to the database without committing. saveAndFlush does both. merge copies the state of a detached entity onto a managed one and returns the managed copy, so use the returned object, not the one you passed in.

7. How does @Transactional actually work?

Spring wraps your bean in a proxy. A call from outside goes through the proxy, which opens a transaction, runs your method, then commits or rolls back. Two classic pitfalls follow from this:

  • Self-invocation. Calling a @Transactional method from another method in the same class bypasses the proxy, so no transaction starts.
  • Visibility and type. Annotations on private methods are ignored by proxy-based AOP.

Another favourite: by default, Spring rolls back on unchecked exceptions only. A checked exception commits unless you set rollbackFor.

8. Transaction propagation: REQUIRED vs REQUIRES_NEW

REQUIRED (the default) joins an existing transaction or starts one. REQUIRES_NEW suspends the current transaction and starts a separate one that commits or rolls back on its own. A good use case: writing an audit log that must be saved even if the main operation fails. Mention that it uses a second database connection, so overusing it can exhaust the pool.

9. Optimistic vs pessimistic locking

  • Optimistic locking uses a @Version column. On update, Hibernate checks the version has not changed and throws an OptimisticLockException if someone else got there first. It is cheap and suits low-conflict data.
  • Pessimistic locking takes a database lock (select ... for update) so others wait. Use it when conflicts are frequent and the cost of a retry is high.

Link it to a story: two users editing the same record, one silently overwriting the other, and how a version column stops it.

10. equals and hashCode in entities

Using only the generated id in equals/hashCode breaks when the id is null before the first save, and using all fields breaks when fields change. Common advice: use a business key if one exists, or a carefully written id-based implementation that is stable across the entity's life. Interviewers want to see that you know it is a trap, not that you memorised one answer.

11. Cascade types and orphanRemoval

CascadeType.PERSIST, MERGE, REMOVE and ALL pass operations from parent to child. orphanRemoval = true deletes a child when it is removed from the parent's collection. Warn about CascadeType.REMOVE on a many-to-many: deleting one side can delete shared data you wanted to keep.

12. JPQL vs native queries vs Criteria

JPQL queries entities and is database-independent. Native queries use plain SQL and are useful for database-specific features or complex reporting, at the cost of portability. Criteria builds queries in code and is useful for dynamic filters. In Spring Data you will often use derived queries (findByStatusAndCreatedAtAfter) for simple cases and @Query for the rest.

13. Pagination and why findAll() is dangerous

Loading every row into memory works in development and fails in production. Use Pageable and Page or Slice. Mention that Page runs an extra count query, and Slice skips it when you only need to know if there is a next page.

Scenario questions

  • "An endpoint that lists orders became slow after we added a new field. How do you investigate?" (Turn on SQL logging, look for N+1, check indexes.)
  • "Two requests update the same row and one change is lost. How do you prevent that?" (Optimistic locking.)
  • "Your @Transactional method does not roll back. Why?" (Checked exception, self-invocation, or a swallowed exception.)

How to practise

Create a small project with Orders and Items, switch on spring.jpa.show-sql, and watch the queries as you change fetch types. Seeing N+1 in your own logs teaches more than any article. For the wider Spring picture, read our Spring Boot interview questions with scenarios and SQL interview questions for developers, then say your answers out loud in a mock interview.

FAQ

Do I need to know Hibernate internals for a 2 to 3 year role? Know the N+1 problem, lazy loading, @Transactional pitfalls and locking basics. Deeper internals are a bonus at senior levels.

Should I use native queries to avoid these problems? Not by default. Learn why the problem happens first, then choose JPQL, projections or native SQL on purpose.

The takeaway

Show that you have looked at the SQL your code generates. Explain N+1 with a fix, know why @Transactional can silently do nothing, and describe how you would stop two users overwriting each other. That practical awareness is what the round is testing.