virsha
virsha

Reputation: 1168

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

Upvotes: 6

Views: 4166

Answers (2)

WrRaThY
WrRaThY

Reputation: 1998

in general you should not make relations in MongoDB.

It may be tempting at first, if you come from relational databases, but in most cases duplication is a better idea in MongoDB.

if that does not sound like a good idea to you, maybe document databases are just not for you? :)

Upvotes: 0

s7vr
s7vr

Reputation: 75964

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);

Upvotes: 3

Related Questions