Reputation: 1168
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
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
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