# 05 — The aggregate boundary [← 04 the JDBC side](04-the-jdbc-side.md) · [next: when this gets expensive →](06-when-this-gets-expensive.md) Source: [`jdbc/Order.java`](../src/main/java/com/ankurm/jdbcvsjpa/jdbc/Order.java) (the `AggregateReference customer` field). Test: [`JdbcBehaviorTest#c_aggregateReferenceDoesNotLoadTheCustomer`](../src/test/java/com/ankurm/jdbcvsjpa/JdbcBehaviorTest.java), transcript [08](output/08-aggregate-reference-no-join.txt). Domain-Driven Design's aggregate pattern says: an aggregate is a cluster of objects saved and loaded as one transactional unit, and a reference to *another* aggregate is held by identity, not by object graph. Spring Data JDBC enforces this at the type level. `Order.customer` is not a `Customer` field — it is an `AggregateReference`, a typed wrapper around a foreign-key value. `AggregateReference.getId()` returns that value without touching the database, because the value is already sitting in a column that was read as part of loading the order. [Scenario 08](output/08-aggregate-reference-no-join.txt) demonstrates the consequence directly: loading an order and then reading `order.getCustomer().getId()` produces **zero** statements touching the customer table. There is no join, no lazy proxy, no N+1 waiting to happen — because there is no mechanism in Spring Data JDBC that would ever load a second aggregate as a side effect of loading the first one. If you want the `Customer`, you ask a `CustomerRepository` for it, explicitly, and that is a second, deliberate query. This is the mechanism that makes [scenario 06](04-the-jdbc-side.md#findbyid-always-returns-the-whole-aggregate)'s "fixed cost regardless of item count" claim scale past one aggregate: the fixed cost is fixed *per aggregate*, and nothing about loading `Order` #1 ever cascades into loading `Order` #2's `Customer`, `Order` #2's `Customer`'s other orders, and so on. JPA's `@ManyToOne(fetch = LAZY)` gives you a proxy that resolves this the moment something touches it — convenient until the thing touching it is a `toString()` in a log statement three services away from the code that loaded the entity. The trade is that Spring Data JDBC will not silently fetch related data across an aggregate boundary for you, ever, under any circumstance. If your domain genuinely needs a graph fetch (here's every order together with its customer's other orders), you write that query yourself. Some teams experience that as "more code to write." Others experience the JPA alternative as "a query plan I can no longer predict by reading the entity class." Continue to [06 — when this gets expensive](06-when-this-gets-expensive.md).