Relationships and Aggregates

Basic Spring
≈ 2 min read

The main idea is the aggregate

Spring Data JDBC treats related objects not as an arbitrary entity graph, but through aggregate boundaries. An aggregate has a root — the object through which the rest of the application loads and saves the entire related whole. This helps make an explicit decision about which data should change together in one repository operation.

@Table("orders")
class Order {
    @Id
    Long id;

    String customerEmail;

    @MappedCollection(idColumn = "order_id")
    Set<OrderItem> items = new HashSet<>();
}

@Table("order_items")
class OrderItem {
    @Id
    Long id;

    String product;
    int quantity;
}

Here Order is the aggregate root, and order items are part of it. The order_id column in the item table connects rows to the aggregate owner. Saving an order means saving the state of this whole according to the Spring Data JDBC model.

A relationship between separate aggregates

If two objects have independent lifecycles and are changed through different repositories, they usually should not be connected into one automatically persisted object graph. Such aggregates more often store the identifier of another aggregate. For example, an order can store a customerId without embedding the entire customer object inside itself.

The aggregate boundary affects not only class design but also the SQL used during saving. An aggregate that is too large makes even a small change an expensive operation, so it should contain only data that truly needs to be persisted together consistently.