# 02 — The JPA side [← 01 the shared domain](01-the-shared-domain.md) · [next: the SQL log →](03-the-sql-log.md) Source: [`jpa/Order.java`](../src/main/java/com/ankurm/jdbcvsjpa/jpa/Order.java), [`jpa/OrderItem.java`](../src/main/java/com/ankurm/jdbcvsjpa/jpa/OrderItem.java), [`jpa/Customer.java`](../src/main/java/com/ankurm/jdbcvsjpa/jpa/Customer.java). Tests: [`JpaBehaviorTest.java`](../src/test/java/com/ankurm/jdbcvsjpa/JpaBehaviorTest.java). ## The five things demonstrated here 1. **Lazy collections need a live session.** [Scenario 01](output/01-lazy-outside-session.txt) loads an `Order` inside a transaction, returns it, and touches `.getItems()` after that transaction (and the Hibernate session it owned) has closed — `LazyInitializationException: ... (no session)`. `spring.jpa.open-in-view: false` in `application.yml` is what makes the session close at the transaction boundary instead of silently staying open for the rest of the request; open-in-view defaults to `true` in plain Spring Boot and papers over exactly this failure until it happens somewhere without a surrounding web request. 2. **The default fetch shape is N+1.** [Scenario 02](output/02-n-plus-one.txt): `findAll()` for 3 orders, then `.getItems().size()` on each in a loop, produces 1 + 3 = 4 SELECTs. Nothing about the code looks wrong; it is the ordinary shape of a service method. 3. **`@EntityGraph` flattens it to a constant.** [Scenario 03](output/03-entity-graph-fixed-cost.txt) re-runs the same lookup through `findWithItemsAndCustomerById`, an `@EntityGraph` query, for a 1-item and a 5-item order — both take exactly 1 statement. This is the fix for scenario 2, and it is also the default (no annotation needed) on the JDBC side; see [05 — the aggregate boundary](05-the-aggregate-boundary.md). 4. **Dirty checking writes before you call save().** [Scenario 04](output/04-dirty-checking-autoflush.txt): inside one transaction, mutate a managed item's `quantity` field directly — no `save()` call anywhere — then run an unrelated query. The pending UPDATE appears *before* that second query runs, because Hibernate flushes dirty state ahead of anything that could otherwise see stale data. "I never called save()" is not evidence that nothing was written. 5. **`orphanRemoval` is what turns "removed from the list" into a DELETE.** [Scenario 05](output/05-orphan-removal-delete.txt): `order.removeItem(item)` followed by `save()` deletes that row because `orphanRemoval = true` is set on the `@OneToMany`. Drop that attribute and the row survives with a stale `order_id` — the more common real bug report. ## What most tutorials don't show Every one of the five behaviours above is *correct* — this is not a list of JPA bugs. The argument this article makes is narrower: each of these five facts has to be known in advance and opted into (an `@EntityGraph`, an `orphanRemoval = true`, an awareness of the flush timing) for the obvious code to do the obvious thing. Spring Data JDBC, covered next, gets three of these five for free by not having the mechanism that causes them. Continue to [03 — the SQL log](03-the-sql-log.md).