As to most good questions there are many sides to the answer.
The historical/symmetry answer
When it comes to references between entities Spring Data JDBC supports 1:1 (the one you ask about) and 1:N (lists, sets and maps).
For the latter anything but a back-reference is just weird/wrong.
And with using a back-reference for 1:1 becomes basically the same, simplifying the code, which is a good thing.
The DML process answer
With the back-reference, the process of inserting and deleting becomes much easier: Insert the aggregate root (customer
in your example) first, then all the referenced entities. And it continues to work if those entities have further entities. Deletes work the other way round but are equally straight forward.
The dependency answer
Referenced entities in an aggregate can only exist as part of that aggregate. In that sense they depend on the aggregate root. Without that aggregate root there is no inner entity, while the aggregate root very often might just as well exist without the inner entity. It therefore makes sense, that the inner entity carries the reference.
The ID answer
With this design, the inner entity doesn't even need an id. It's identity is perfectly given by the identity of the aggregate root and in case of multiple one-to-one relationships to the same entity class, the back-reference column used.
Alternatives
All the reasons are more or less based on a single one-to-one relationship. I certainly agree that it looks a little weird for two such relationships to the same class and with 5 as in your example it becomes ridiculous. In such cases you might want to look in alternatives:
Use a map
Instead of modelling your Customer
class like this:
class Customer {
@Id
Long id;
String name;
Address orderAddress
Address serviceAddress
Address deliveryAddress
Address installationAddress
Address accountAddress
}
Use a map like this
class Customer {
@Id
Long id;
String name;
Map<String,Address> addresses
}
Which would result in an address
table like so
CREATE TABLE "address" (
"customer" BIGINT,
"customer_key" VARCHAR(20). NOT NULL,
"city" VARCHAR(255) NOT NULL
);
You may control the column names with a @MappedCollection
annotation and you may add transient getter and setter for individual addresses if you want.
Make it a true value
You refer to Address
as a value while I referred to it as an entity. If it should be considered a value I think you should map it as an embedded like so
class Customer {
@Id
Long id;
String name;
@Embedded(onEmpty = USE_NULL, prefix="order_")
Address orderAddress
@Embedded(onEmpty = USE_NULL, prefix="service_")
Address serviceAddress
@Embedded(onEmpty = USE_NULL, prefix="delivery_")
Address deliveryAddress
@Embedded(onEmpty = USE_NULL, prefix="installation_")
Address installationAddress
@Embedded(onEmpty = USE_NULL, prefix="account_")
Address accountAddress
}
This would make the address
table superfluous since the data would be folded into the customer
table:
CREATE TABLE "customer" (
"id" BIGSERIAL NOT NULL,
"name" VARCHAR(255) NOT NULL,
"order_city" VARCHAR(255) NOT NULL,
"service_city" VARCHAR(255) NOT NULL,
"deliver_city" VARCHAR(255) NOT NULL,
"installation_city" VARCHAR(255) NOT NULL,
"account_city" VARCHAR(255) NOT NULL,
PRIMARY KEY (id)
);
Or is it an aggregate?
But maybe you need addresses on their own, not as part of a customer.
If that is the case an address is its own aggregate.
And references between aggregates should be modelled as ids or AggregateReference
. This is described in more detail in Spring Data JDBC, References, and Aggregates