ExerciseChallenge
Challenge
Six mappings, and what each costs
25 minintermediate1–15 yrs
Edge cases. You have to reason, and two valid fixes differ.
What this teaches
- Fetch type is a property of the mapping and applies to every query for that entity
- Eager to-one associations chain, pulling in a graph nobody named
- LAZY on the non-owning side of @OneToOne does not work, and there is a reason
- Two of the six are only wrong because of where the entity ends up, not how it is mapped
Starter
Starter.java
import java.util.*;
/**
* Challenge: six mappings from one codebase. Work out what each one costs.
*
* Four have a problem in the mapping. Two are mapped correctly and still fail,
* because the problem is where the entity ends up rather than how it loads.
*/
public class Starter {
// ── 1 ─────────────────────────────────────────────────────────────────
// @ManyToOne private Customer customer;
// Used by a list endpoint returning 200 orders.
// ── 2 ─────────────────────────────────────────────────────────────────
// @ManyToOne(fetch = LAZY) private Customer customer;
// ...and Customer has: @ManyToOne private Account account;
// @ManyToOne private Region region;
// ── 3 ─────────────────────────────────────────────────────────────────
// @OneToOne(fetch = LAZY, mappedBy = "order") private Invoice invoice;
// Profiling shows the invoice is loaded every time anyway.
// ── 4 ─────────────────────────────────────────────────────────────────
// @OneToMany(mappedBy = "order", fetch = EAGER) private List<Item> items;
// @OneToMany(mappedBy = "order", fetch = EAGER) private List<Payment> payments;
// ── 5 ─────────────────────────────────────────────────────────────────
// Every association is LAZY and correct.
// @GetMapping("/orders/{id}") public Order get(...) { return repo.findById(id)...; }
// ── 6 ─────────────────────────────────────────────────────────────────
// Every association is LAZY and correct.
// @Transactional service loads the order; the Thymeleaf template renders
// order.items. spring.jpa.open-in-view is at its default.
public static void main(String[] args) {
// TODO 1: for each mapping, give the query count for loading ONE
// entity and for loading a hundred. Model it if that is easier than
// reasoning about it.
// TODO 2: mapping 1 has no fetch type written at all. Say what it is
// and why that is the single most common performance bug in JPA code.
// TODO 3: mapping 2 is LAZY and still expensive once you load a
// Customer. Trace how far the loading reaches and say what stops it.
// TODO 4: mapping 3 sets LAZY and is ignored. Explain the reason —
// it is about what Hibernate has to know before it can build a proxy —
// and give three ways to fix it, in the order you would try them.
// TODO 5: mapping 4 will either produce a cartesian product or throw.
// Which, and what decides it? The collection type matters.
// TODO 6: mappings 5 and 6 are both correct. Say what fails in each,
// where, and why the fix is not in the mapping.
// TODO 7: for 6, count the queries the service method issues and the
// queries the request issues. Say which one a timer on the service
// would report, and what that means for any query-count test you write.
// TODO 8: one change to this codebase would surface more of these
// problems than any mapping edit. Name it, say what it will break, and
// say why that breakage is the point.
}
}Run it locally:
cd exercises/java/jpa-hibernate/fetch-types/02-challenge
javac Starter.java -d /tmp/out && java -cp /tmp/out StarterHints
Hint 1
For each mapping, write the query count for loading ONE entity and for loading a hundred. The second number is where the argument lives.
Hint 2
One mapping is eager on an entity that is itself eager on something else. Follow it and say how far it reaches.
Hint 3
One is correctly LAZY and still loads eagerly. The side of the relationship matters, and so does whether the row can be absent.
Hint 4
Two are mapped perfectly well and still cause LazyInitializationException. Look at what the controller returns.
Done when
- A query count for one entity and for a hundred, per mapping
- The chaining case identified, with how far it reaches
- The @OneToOne case explained, with the three ways to fix it
- The two boundary problems identified as boundary problems, not mapping problems
- You said which single change to the codebase would help most, and why it is not a mapping change
← Back to What are the fetch types, and which is the default for each mapping?