Data

JPA, Repositories, Transactions

The data layer โ€” where most production bugs live. Focus on the parts that trip experienced devs: N+1, transaction propagation, lazy loading.

01
Foundation

JPA Mental Model

JPA is a specification, Hibernate is the implementation Spring Boot uses by default. The persistence context is the first-level cache โ€” a unit of work that tracks managed entities.

Entity State Machine
Transient save() Managed tx ends Detached remove() Removed save() again
02
Mapping

Entity Mapping

  • @Entity, @Table, @Id, @GeneratedValue, @Column
  • @GeneratedValue strategies: IDENTITY (DB auto-increment, most common) vs SEQUENCE
RelationshipDefault FetchAction
@OneToManyLAZYSafe
@ManyToManyLAZYSafe
@ManyToOneEAGEROverride to LAZY
@OneToOneEAGEROverride to LAZY
Always set @ManyToOne(fetch = FetchType.LAZY) and @OneToOne(fetch = FetchType.LAZY). The eager defaults are the #1 cause of surprise N+1 queries.
03
Data Access

Repositories

Repository โ†’ CrudRepository โ†’ JpaRepository โ€” each adds more methods.

  • save(), findById(), findAll(), delete(), count(), existsById()
  • flush(), saveAndFlush() โ€” force writes to DB immediately
  • Pagination: findAll(Pageable) returns Page<T> with total count and page metadata
@Repository annotation is optional when extending JpaRepository โ€” Spring detects it automatically.
04
Queries

Query Styles

StyleExampleWhen to Use
Derived method namefindByEmailAndStatus(...)Simple conditions, 1-2 fields
@Query JPQL@Query("SELECT u FROM User u WHERE...")Complex conditions, joins
@Query native SQL@Query(value="SELECT...", nativeQuery=true)DB-specific features
Specificationrepo.findAll(spec, pageable)Dynamic filters (search APIs)

JPQL uses entity class names and field names, not table/column names. Specifications are worth the complexity for filter-heavy search endpoints where the WHERE clause is dynamic.

05
Consistency

Transactions

@Transactional โ€” begins tx before method, commits after, rolls back on unchecked exception. Default: unchecked exceptions (RuntimeException) roll back, checked exceptions do not.

@Transactional(readOnly = true) โ€” use on all read methods. No dirty checking, potential read-replica routing, and Hibernate flushes are skipped.
PropagationBehaviour
REQUIRED (default)Join existing tx, or create new one
REQUIRES_NEWAlways create new tx, suspend existing
MANDATORYMust run inside existing tx โ€” throws if none
SUPPORTSJoin if exists, run non-transactionally if not
NOT_SUPPORTEDAlways run non-transactionally, suspend existing
NEVERMust NOT run in a tx โ€” throws if one exists
NESTEDSavepoint inside existing tx โ€” rollback to savepoint
Self-invocation bypasses the proxy โ€” a @Transactional method calling another @Transactional method in the same class doesn't start a new transaction. Extract to a separate bean.
06
Performance

The N+1 Problem

Loading 100 orders fires 1 query, then 100 more queries (one per order) to load items. LAZY loading triggers a new query each time you access the collection in a loop.

spring.jpa.show-sql=true + count the queries. Or use p6spy / slow query logs in production.

N+1 vs JOIN FETCH
Without JOIN FETCH
With JOIN FETCH
SELECT * FROM orders โ†’ 1 query for each order: SELECT * FROM order_items WHERE order_id = ? โ†’ 100 queries Total: 101 queries
SELECT o.*, i.* FROM orders o JOIN order_items i ON i.order_id = o.id Total: 1 query
FixHowTrade-off
JOIN FETCH in JPQL SELECT o FROM Order o JOIN FETCH o.items One query, may produce duplicates โ€” use DISTINCT
@EntityGraph @EntityGraph(attributePaths = {"items"} ) on repo method Declarative, no JPQL needed
@BatchSize @BatchSize(size = 50) on collection Reduces queries to N/50, not 1