3

I'm trying to implement a rest api using RepositoryRestResource and RestTemplate

It all works rather well, except for loading @DBRef's

Consider this data model:

public class Order
{
   @Id
   String id;

   @DBRef
   Customer customer;

   ... other stuff
}

public class Customer
{
    @Id
    String id;

    String name;

    ...
}

And the following repository (similar one for customer)

@RepositoryRestResource(excerptProjection = OrderSummary.class)
public interface OrderRestRepository extends MongoRepositor<Order,String>{}

The rest api returns the following JSON:

{
  "id" : 4,
  **other stuff**,
  "_links" : {
    "self" : {
      "href" : "http://localhost:12345/api/orders/4"
    },
    "customer" : {
      "href" : "http://localhost:12345/api/orders/4/customer"
    }
  }
}

Which if loaded correctly by the resttemplate will create a new Order instance with customer = null

Is it possible to eagerly resolve the customer on the repository end and embed the JSON?

s7vr
  • 73,656
  • 11
  • 106
  • 127
p.streef
  • 3,652
  • 3
  • 26
  • 50

1 Answers1

7

Eagerly resolving dependent entities in this case will raise most probably N+1 database access problem. I don't think there is a way to do that using default Spring Data REST/Mongo repositories implementation.

Here are some alternatives:

  1. Construct an own custom @RestController method that would access the database and construct desired output
  2. Use Projections to populate fields from related collection, e.g.

    @Projection(name = "main", types = Order.class)
    public interface OrderProjection {
        ...
    
        // either
        @Value("#{customerRepository.findById(target.customerId)}")
        Customer getCustomer();
    
        // or
        @Value("#{customerService.getById(target.customerId)}")
        Customer getCustomer();
    
        // or
        CustomerProjection getCustomer();
    } 
    
    @Projection(name = "main", types = Customer.class)
    public interface CustomerProjection {
        ...
    }
    
  3. The customerService.getById can employ caching (e.g. using Spring @Cachable annotation) to mitigate the performance penalty of accessing the database additionally for each result set record.

  4. Add redundancy to your data model and store copies of the Customer object fields in the Order collection on creation/update.

This kind of problem arises, in my opinion, because MongoDB doesn't support joining different document collections very well (its "$lookup" operator has significant limitations in comparison to the common SQL JOINs). MongoDB docs also do not recommend using @DBRef fields unless joining collections hosted in distinct servers:

Unless you have a compelling reason to use DBRefs, use manual references instead.

Here's also a similar question.

Community
  • 1
  • 1
Sergey Shcherbakov
  • 4,534
  • 4
  • 40
  • 65
  • I fixed it using nested projections. This works like a charm. Since I only need the eager resolving when retrieving a single order the amount of queries will be minimal. Thanks! – p.streef Apr 25 '17 at 08:41