JPA Full Guide: Interview Self‑Test with Answers

This article provides a comprehensive interview self‑test covering JPA, Hibernate, and Spring Data JPA, including entity lifecycle states, repository proxy creation, PartTree method parsing, first‑level cache behavior, dirty checking, transaction binding, N+1 query problems, lazy loading exceptions, JPQL vs native queries, save vs persist differences, and the semantics of findAll returning an empty collection.

CodeSmart Hoops
CodeSmart Hoops
CodeSmart Hoops
JPA Full Guide: Interview Self‑Test with Answers

1. Relationship between JPA, Hibernate, Spring Data JPA

JPA (Java Persistence API) is a specification that defines standard ORM interfaces, similar to JDBC for database access. Hibernate is the most widely used open‑source implementation of the JPA specification, handling object‑to‑table mapping, SQL generation, caching, and dirty checking. Spring Data JPA builds on top of JPA, providing a Repository abstraction; developers declare an empty interface extending JpaRepository and Spring generates a proxy implementation at startup, enabling zero‑code CRUD operations.

The typical stack is Spring Data JPA → Hibernate (JPA implementation) → JDBC.

2. Entity four states and transitions

Each entity instance is in one of four states:

Transient – newly created, not managed, no primary key.

Persistent – managed by the EntityManager inside the PersistenceContext; changes are automatically synchronized.

Detached – previously managed but now outside the PersistenceContext; has a primary key but changes are not persisted.

Removed – marked for deletion; will be removed from the database on transaction commit.

State transition methods:

Transient → Persistent: persist() or save() Persistent → Detached: clear() (whole context) or evict(entity) (single entity)

Detached → Persistent: merge() or save() Persistent → Removed: remove() or delete() Only entities in the Persistent state are tracked for dirty checking; calling clear() moves them to Detached, after which modifications are not automatically persisted.

3. Repository dynamic proxy creation process

Spring scans all repository interfaces via RepositoryConfigurationDelegate.registerRepositories().

For each interface, RepositoryMetadata extracts the domain type (e.g., ProductEntity) and ID type. ProxyFactory creates a JDK dynamic proxy with the repository interface as the target.

The core interceptor QueryExecutorMethodInterceptor (holding an EntityManager) is added. factory.getProxy() produces the proxy instance, which is registered in the Spring container.

When a repository method is invoked, the interceptor routes the call:

productJpaRepository.findAll()
 └─ QueryExecutorMethodInterceptor.invoke()
     ├─ Is it a CRUD method? → delegate to SimpleJpaRepository
     ├─ Is it a derived query method? → PartTree parsing → execute query
     └─ Is it an @Query method? → use the annotated query

4. PartTree method‑name parsing principle

Calling findByCategoryAndPriceLessThan("Electronics", 1000.0) triggers four steps:

Step 1 – Prefix extraction : Recognize find as a SELECT query.

Step 2 – Subject extraction : The part after By becomes the predicate; modifiers like OrderBy, Distinct, First / Top are also detected.

Step 3 – Predicate parsing : CategoryAndPriceLessThan splits into two parts: category = ?1 and price < ?2.

Step 4 – JPQL generation : The final query is

SELECT p FROM ProductEntity p WHERE p.category = ?1 AND p.price < ?2

.

Spring Data chooses the query strategy in the order: CREATE (method name) → USE_DECLARED_QUERY (@Query) → CREATE_IF_NOT_FOUND (fallback to method name).

5. First‑level cache flush timing and clear/evict/refresh differences

The first‑level cache is the PersistenceContext inside the EntityManager (session‑level cache). FlushMode.AUTO (default): automatic flush before queries. FlushMode.COMMIT: flush only on transaction commit.

Manual flush(): forces immediate synchronization.

Clear/evict/refresh: clear() – clears the entire context; all managed entities become Detached. evict(entity) – removes a single entity from the context, making it Detached. refresh(entity) – reloads the entity from the database, overwriting local changes.

6. Dirty checking mechanism

When an entity is loaded, Hibernate creates a snapshot of its original field values and stores it in an EntityEntry.

On transaction commit or explicit flush(), Hibernate compares current values with the snapshot; differing fields are marked dirty and an UPDATE statement is generated.

For collection fields (e.g., @OneToMany), Hibernate replaces them with custom implementations such as PersistentSet or PersistentBag. Calls to add() or remove() on these collections are tracked and synchronized during flush.

Within a @Transactional method, modifying a managed entity does not require an explicit save(); the transaction commit triggers dirty checking automatically.

7. @Transactional binding EntityManager mechanism

The method entry annotated with @Transactional causes Spring to create an EntityManager.

Spring binds the EntityManagerHolder to the current thread via

TransactionSynchronizationManager.bindResource(emf, emHolder)

.

Repository calls (e.g., repository.findById()) retrieve the bound EntityManager from the ThreadLocal, ensuring the same transaction context.

On transaction commit, Hibernate flushes, commits, and closes the EntityManager.

Finally, TransactionSynchronizationManager.unbindResource() removes the binding.

Analogy: the transaction manager is like a waiter attaching the order to a specific table; all service for that table uses the same order.

8. N+1 query problem and solution comparison

The N+1 problem occurs when a primary query (1) loads a list of parent rows and then an additional query (N) is executed for each row to fetch associated data, resulting in 1+N SQL statements.

@EntityGraph – declarative annotation specifying attributePaths; simple and concise but coarse‑grained.

JOIN FETCH – JPQL syntax JOIN FETCH p.categoryEntity; provides precise control over the generated SQL but cannot be combined with Pageable.

@BatchSize(size = 10) – annotation that batches collection loading, reducing the number of queries from N to N/10; still results in multiple queries.

Recommended practice: prefer @EntityGraph for most cases, use JOIN FETCH when exact SQL control is needed, and supplement with @BatchSize for large but infrequently accessed associations.

9. Detached entity modifications are not persisted

ProductEntity product = repository.findById(1).orElseThrow();
// transaction ended, product is Detached
product.setName("Changed");
// expectation: automatic update → does NOT happen!

Correct approach: re‑attach the entity via save() (which performs a merge()).

product.setName("Changed");
repository.save(product); // merge, re‑associates with PersistenceContext

Analogy: a detached entity is like a bank passbook after you leave the counter; changes are ignored until you hand it back.

10. @Transactional self‑invocation issue

Spring’s @Transactional works through AOP proxies; only calls that go through the proxy trigger the transaction interceptor. An internal method call (e.g., this.doUpdate()) bypasses the proxy, so the transaction is not applied.

@Service
public class ProductService {
    public void updateName(Integer id, String name) {
        this.doUpdate(id, name); // internal call → @Transactional ignored
    }

    @Transactional
    public void doUpdate(Integer id, String name) {
        ProductEntity p = repository.findById(id).orElseThrow();
        p.setName(name);
    }
}

Fixes:

Place @Transactional on the external method ( updateName()).

Move the transactional method to a separate service and inject it.

Root cause: this.doUpdate() is a plain Java call, not intercepted by Spring’s proxy.

11. LazyInitializationException solutions

Solution 1 (recommended): Use a DTO. Convert the entity to a DTO inside the transaction and return the DTO, avoiding lazy loading after the session closes.

Solution 2: Force initialization inside the service by accessing the lazy association before returning.

Solution 3: Apply @EntityGraph to pre‑load required associations.

12. JPQL vs nativeQuery usage scenarios

JPQL operates on entities and their fields, is database‑agnostic, and suits most business queries.

@Query("SELECT p FROM ProductEntity p WHERE p.name LIKE %:keyword%")
List<ProductEntity> searchByName(@Param("keyword") String keyword);

nativeQuery executes raw SQL, useful for database‑specific features (e.g., MySQL DATE_FORMAT, window functions) or when JPQL cannot express the needed logic.

@Query(value = "SELECT * FROM product WHERE price > :price", nativeQuery = true)
List<ProductEntity> findExpensiveProducts(@Param("price") Double price);

Guideline: prefer JPQL; fall back to native queries only when JPQL is insufficient.

13. Difference between save() and persist()

persist()

(EntityManager):

Handles only Transient entities.

Throws IllegalArgumentException if a Detached entity is passed.

Semantically means “make this new object persistent”. save() (Spring Data Repository):

Internally decides between persist() (for Transient) and merge() (for Detached) based on primary‑key presence.

Works for both new and existing entities, making it more flexible.

Analogy: persist() is like a clerk opening a brand‑new account; save() is like a clerk who decides whether to open a new account or reactivate an existing one.

14. findAll() returns empty collection instead of null

Methods like findAll() and other collection‑returning queries always return an empty List (e.g., ArrayList) when no rows match; they never return null. Only findById() returns an Optional<T>.

List<ProductEntity> products = repository.findAll();
if (products.isEmpty()) {
    // handle empty result
}

Reason: Spring Data JPA consistently returns a collection instance to avoid null‑check boilerplate.

Original Source

Signed-in readers can open the original source through BestHub's protected redirect.

Sign in to view source
Republication Notice

This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactadmin@besthub.devand we will review it promptly.

TransactionRepositoryEntityHibernateJPASpring Data JPA
CodeSmart Hoops
Written by

CodeSmart Hoops

A working programmer who loves coding and basketball. By day I debug code; by night I dissect tactics. I write articles to document my journey, focusing on Java, AI, Python and other programming topics, with occasional posts about basketball, English, and books. Hope it's helpful—thanks for following and support.

0 followers
Reader feedback

How this landed with the community

Sign in to like

Rate this article

Was this worth your time?

Sign in to rate
Discussion

0 Comments

Thoughtful readers leave field notes, pushback, and hard-won operational detail here.