Challenge

Six mappings, and what each costs

25 minintermediate115 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 Starter

Hints

  1. 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.

  2. Hint 2

    One mapping is eager on an entity that is itself eager on something else. Follow it and say how far it reaches.

  3. 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.

  4. 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?