Scenario: load an order, then read its customer's id through the AggregateReference — never call customerRepository.findById for it. 1. SELECT "JDBC_ORDER"."ID" AS "ID", "JDBC_ORDER"."VERSION" AS "VERSION", "JDBC_ORDER"."CUSTOMER_ID" AS "CUSTOMER_ID" FROM "JDBC_ORDER" WHERE "JDBC_ORDER"."ID" = ? 2. SELECT "JDBC_ORDER_ITEM"."ID" AS "ID", "JDBC_ORDER_ITEM"."SKU" AS "SKU", "JDBC_ORDER_ITEM"."QUANTITY" AS "QUANTITY", "JDBC_ORDER_ITEM"."ORDER_KEY" AS "ORDER_KEY" FROM "JDBC_ORDER_ITEM" WHERE "JDBC_ORDER_ITEM"."ORDER_ID" = ? ORDER BY "ORDER_KEY" total statements: 2 statements touching jdbc_customer: 0 customer id obtained without a lookup: 3 AggregateReference is a typed foreign key: getId() is free because the id is literally the column value already in hand from loading the order. Nothing about Customer is fetched unless you explicitly ask a CustomerRepository for it. This is the mechanism that keeps a multi-aggregate object graph from becoming N+1 by default — there is no "default" traversal at all across an aggregate boundary.