Spring Data JPA & Transactions
Repositories, entity mapping, fetching and N+1, transactions, propagation, isolation and locking.
JPA vs Hibernate vs Spring Data JPA?
JPA (Jakarta Persistence) is the ORM specification. Hibernate implements it (Hibernate 7 in Boot 4). Spring Data JPA sits on top and generates repository implementations from interfaces — derived queries, paging, auditing — removing DAO boilerplate.
CrudRepository vs JpaRepository?
CrudRepository offers basic CRUD; ListCrudRepository returns Lists. JpaRepository adds JPA-specific operations — flush, saveAndFlush, batch deletes, getReferenceById — plus paging and sorting.
How do derived query methods work?
Spring Data parses the method name into a query: findByCustomerIdAndStatusOrderByCreatedAtDesc becomes where customerId = ? and status = ? order by createdAt desc. Keywords include And, Or, Between, LessThan, Like, In, IsNull, Top3 and Distinct. For complex queries use @Query — long method names hurt readability.
Does save() insert or update?
save calls persist if the entity is new and merge otherwise. “New” means a null id (or null @Version, or Persistable.isNew()). With manually assigned ids, save sees a non-null id and merges — an extra SELECT — unless you implement Persistable.
What is the N+1 query problem and how do you fix it?
Loading N parents with one query and then touching a lazy association on each triggers N more queries. Fixes: a fetch join (join fetch o.lines), an @EntityGraph, batch fetching (hibernate.default_batch_fetch_size), or a DTO projection. Detect it with SQL logging, Hibernate statistics or tests asserting query counts.
LAZY vs EAGER fetching — what should you use?
Make associations LAZY — including @ManyToOne and @OneToOne, which are EAGER by default — and decide per use case what to fetch. EAGER loads associations every time, often with extra queries, and can’t be switched off per query.
What causes LazyInitializationException and how do you fix it properly?
Accessing an uninitialised lazy association after the persistence context closed — typically in a controller or during JSON serialisation. Don’t fix it with EAGER or Open Session in View; fetch what the use case needs inside the @Transactional service method and return DTOs.
What is Open Session in View and why disable it?
Spring Boot keeps the persistence context open for the whole web request by default (spring.jpa.open-in-view=true, logged as a warning), so lazy loading works in controllers. It hides N+1 queries outside the service layer and holds DB connections longer. Set it to false for APIs.
What is the owning side of a bidirectional relationship?
The side whose table holds the foreign key — usually the @ManyToOne side with @JoinColumn. The inverse side declares mappedBy. Only changes on the owning side are written, so keep both sides in sync with helper methods such as order.addLine(line).
How does @Transactional work?
An AOP proxy intercepts calls; a TransactionInterceptor asks the PlatformTransactionManager (JpaTransactionManager) to begin a transaction and bind the connection to the thread, invokes the method, then commits (flushing dirty entities) or rolls back on exception.
Which exceptions trigger a rollback by default?
Unchecked exceptions and Errors. Checked exceptions commit unless you set rollbackFor = Exception.class. An exception caught and swallowed inside the method never reaches the proxy, so the transaction commits.
REQUIRED vs REQUIRES_NEW propagation?
REQUIRED (default) joins the current transaction or starts one; an inner failure marks the whole transaction rollback-only, even if the outer method catches it (UnexpectedRollbackException on commit). REQUIRES_NEW suspends the current transaction and runs an independent one on a second connection — useful for audit records that must survive the caller’s rollback.
Explain transaction isolation levels and the anomalies they prevent.
READ_UNCOMMITTED— dirty reads possible.READ_COMMITTED(PostgreSQL/Oracle default) — no dirty reads; non-repeatable reads and phantoms possible.REPEATABLE_READ(MySQL default) — re-reading a row returns the same data; phantoms possible in theory.SERIALIZABLE— behaves as if transactions ran one at a time.
Higher levels cost concurrency; protect specific invariants with optimistic or pessimistic locks instead.
Optimistic vs pessimistic locking?
Optimistic (@Version): no locks held; the update includes where version = ?, and a concurrent change raises ObjectOptimisticLockingFailureException — return 409 or retry. Best when conflicts are rare. Pessimistic (@Lock(PESSIMISTIC_WRITE), i.e. SELECT … FOR UPDATE) blocks other writers until commit — for hot rows with frequent conflicts, at the cost of contention and deadlock risk.
What does readOnly = true do?
Hibernate skips dirty checking and entity snapshots (less CPU and memory) and uses manual flush mode; the driver or database may optimise or route to a replica. It also documents intent.
What are projections and why use them?
Interface or record (DTO) projections select only the needed columns: List<OrderSummary> findByCustomerId(String id). They avoid loading associations and dirty checking — often the biggest performance win for read-heavy endpoints.
How do you handle schema migrations?
Use Flyway or Liquibase: versioned scripts run on startup (and in tests against Testcontainers). Use spring.jpa.hibernate.ddl-auto=validate or none in production — never update or create-drop.
No questions match these filters.