Description
The goal is support linking documents without the need of using the fixed structure of a DBRef
(related to: #2780).
Linking via Entity id
The @Id
value of the referenced entity is used for linking.
The domain type serves as collection source on read.
class Order {
@Id Long orderId;
@ManualReference
Customer customer;
}
class Customer {
@Id Long customerId;
}
// write
{
'_id' : 1,
'customer' : 100,
}
// read
db.customer.find({ '_id' : 100 }).limit(1)
Linking via custom object
The reference value used for storing is provided via a DomainType -> ObjectReference
converter.
The lookup
attribute uses SpEL to create the filter query following the annotated query syntax from @Query
. The target collection
that can be either read from the source document, domain type or is a fixed value.
class CustomerReferenceConverter implements Converter<Customer, ObjectReference> {
public ObjectReference convert(Customer source) {
return new org.bson.Document("id", source.getId())
.append("collection", "customers");
}
}
class Order {
@Id Long orderId;
@ManualReference(lookup = "{ '_id' : '?#{id}' }", collection = "?#{collection}")
Customer customer;
}
// write
{
'_id' : 1,
'customer' : {
'id' : 100,
'collection' : 'customers'
}
}
// read
db.customers.find({ '_id' : 100 }).limit(1)
The lookup
filter can contain any valid expression as long as it is resolvable against the stored reference.
Linking multiple entities
Linking a collection of entities follows the same conversion patterns as described above. The main difference is that the values are stored within an array.
The provided filter expression is created for every entry and concatenated via the $or
operator.
class Customer {
@Id Long customerId;
@ManualReference(lookup = "{ '_id' : '?#{target}' }")
List<Order> orders;
}
// write
{
'_id' : 100,
'orders' : [1,2,3,5,8]
}
// read
db.order.find({ '$or' : [ { '_id' : 1 }, { '_id' : 2 }, ...] })
Element order can be messed up when loading documents from the store this way. The original order must be restored by using the filter as a reference without having to fetch objects one by one.
Lazy Loading
References can be resolved lazily delaying resolution on first access.
In this case on read a proxy is generated holding the actual source value and the lookup filter.
class Order {
@Id Long orderId;
@ManualReference(lazy = true)
Customer customer;
}
Object methods like (toString
) on unresolved proxies shall not trigger resolution but be served from the available source value. On write, in case the Proxy never got resolved, the original source is written back to the collection without an attempt of resolving it.