Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

spring-data-mongodb: findAll() with list of input documents and search parameter for the embedded DBRef Document

I am using spring-data-mongo and trying to access the dbref objects with params. My project looks like this:

My models are as follows:

i. First Document is "Cars"

@Document("cars")
class CarDocument {
   @Id
   private String id;
   private String name;
   private String madeInCountry;
   private String model;
   private String madeInYear;
}

ii. Second document is "tools"

Document("tools")
class ToolDocument {
   @Id
   private String id;
   private String name;
   private String madeInCountry;
   private String madeInYear;
   private List<UsedIn> usedIn = new ArrayList<>();
}

iii. The third is embedded model "UsedIn" in (ii.) Third embedded model represents where tools are used to make cars in the manufacturing house.

class UsedIn {
   @DBRef
   private CarDocument car;
   private DateTime usedDate;
   private String usedByUsername;
}

My DAO's are as follows:

public interface CarDAO extends MongoRepository<CarDocument, String>
{
    public CarDocument findByMadeInCountry(String madeInCountry);
}
public interface ToolDAO extends MongoRepository<ToolDocument, String>
{
    public ToolDocument findByMadeInCountry(String madeInCountry);
}

Now I need list of all the "Tools" which is used in the specific car. Say a. when car is madeInCountry: "germany" and b. tool is madeInCountry: "germany"

I see that we can't apply search directly on DBRef documents. like :

String madeInCountry = "germany";
toolDAO.findByMadeInCountryAndUsedInCarMadeInCountry(madeInCountry,madeInCountry);

I get this error:

"Invalid path reference car.madeInCountry! Associations can only be pointed to directly or via their id property!"

How to this?

Do I need to do two DAO calls? Say i. first get all the cars with madeInCountry is germany

String madeInCountry = "germany";
carDAO.findByMadeInCountry(madeInCountry);

ii. findTools by the list of carDocuments and String.

I dont know, how to call this dao with list of CarDocuments and madeInCountry String ?

Do I need to use some $lookup functionality?

Thanks

like image 735
virsha Avatar asked Mar 07 '17 10:03

virsha


People also ask

What is DBRef in MongoDB Spring boot?

DBRef is MongoDB's native element to express references to other documents with an explicit format { $db : …, $ref : …, $id : … } that holds information about the target database, collection, and id value of the references element, best suited to link to documents distributed across different collections.

Why does Spring boot with MongoDB Findall () return empty array?

The issue arises because the collection name is not explicitly given in your model class, so spring-data derives the collection name from the class name ( Courses ) into camel case ( courses ). Since your actual collection is called Courses , no results are found.

What is DBRef in MongoDB?

DBRefs are references from one document to another using the value of the first document's _id field, collection name, and, optionally, its database name, as well as any other fields. DBRefs allow you to more easily reference documents stored in multiple collections or databases.

What is the difference between MongoOperations and MongoTemplate?

MongoTemplate provides a simple way for you to save, update, and delete your domain objects and map those objects to documents stored in MongoDB. You can save, update and delete the object as shown below. MongoOperations is the interface that MongoTemplate implements.


1 Answers

You can try below aggregation.

Update your UsedIn class to below.

 private Long carId;
 private CarDocument car;
 private Date usedDate;
 private String usedByUsername;

Mongo Shell Query:

db.tools.aggregate([{
    "$match": {
        "madeInCountry": "germany"
    }
}, {
    "$unwind": "$usedIn"
}, {
    "$lookup": {
        "from": "cars",
        "localField": "usedIn.carId",
        "foreignField": "_id",
        "as": "usedIn.car"
    }
}, {
    "$unwind": "$usedIn.car"
}, {
    "$match": {
        "usedIn.car.madeInCountry": "germany"
    }
}, {
    "$group": {
        _id: "$_id",
        usedIns: {
            "$push": "$usedIn"
        }
    }
}])

Spring Aggregation Code:

 Criteria toolQuery = Criteria.where("madeInCountry").in("germany");
 MatchOperation toolMatchOperation = new MatchOperation(toolQuery);
 LookupOperation lookupOperation = LookupOperation.newLookup().
                from("cars").
                localField("usedIn.carId").
                foreignField("_id").
                as("usedIn.car");
 Criteria carQuery = Criteria.where("usedIn.car.madeInCountry").is("germany");
 MatchOperation carMatchOperation = new MatchOperation(carQuery);

 TypedAggregation<ToolDocument> aggregation = Aggregation.newAggregation(ToolDocument.class, toolMatchOperation, Aggregation.unwind("usedIn"), lookupOperation, Aggregation.unwind("usedIn.car"), carMatchOperation,
                Aggregation.group("id").push("usedIn").as("usedIn"));
 List<ToolDocument> results = mongoTemplate.aggregate(aggregation, ToolDocument.class).getMappedResults();

Methods to load the data.

Car Data

public void saveCar() {
    carDao.deleteAll();

    CarDocument carDocument1 = new CarDocument();
    carDocument1.setId(1L);
    carDocument1.setName("audi");
    carDocument1.setMadeInCountry("germany");

    carDao.save(carDocument1);
}

Tool Data

public void saveTool() {

    toolDao.deleteAll();

    ToolDocument toolDocument1 = new ToolDocument();
    toolDocument1.setId(1L);
    toolDocument1.setName("wrench");
    toolDocument1.setMadeInCountry("germany");

    UsedIn usedIn1 = new UsedIn();
    usedIn1.setCarId(1L);
    usedIn1.setUsedByUsername("user");
    usedIn1.setUsedDate(new Date());

    List<UsedIn> usedIns1 = new ArrayList<>();
    usedIns1.add(usedIn1);

    toolDocument1.setUsedIn(usedIns1);

    toolDao.save(toolDocument1);
}

Update:

To answer your question about accessing the DBRefs

ii. findTools by the list of carDocuments and String.

I dont know, how to call this dao with list of CarDocuments and madeInCountry String ?

 public List<ToolDocument> findByMadeInCountryAndUsedInCarIn(String madeInCountry, List<CarDocument> carDocuments);

Like I noted in the first comment the second call that you need is a call over embedded dbref with list of car documents value. The query will look for a match and return all the car documents when a match is found for a tool document. Meaning you will get tool documents which is made in Germany that has atleast one used in - cardocument made in Germany.

Sharded update($lookup equalivent)( Idea taken from here MongoDB to Use Sharding with $lookup Aggregation Operator )

List<ToolDocument> toolDocuments = toolDao.findByMadeInCountry("germany");
List<Long> carIds = toolDocuments.stream().map(tool -> tool.getUsedIn().stream().map(UsedIn::getCarId).collect(Collectors.toList())).flatMap(List::stream).collect(Collectors.toList());
List<CarDocument> carDocuments = carDao.findByMadeInCountryAndIdIn("germany", carIds);
like image 111
s7vr Avatar answered Oct 19 '22 08:10

s7vr