Retrieving Mongo Documents based on nested objectIds by passing list of ids - mongodb

I have two collections in MongoDB, Parent an Child. Below is the structure of the document.
Parent: {
'_id': 'some_value',
'name': 'some_value',
'child': {
'_id': 'some_value',
'name': 'some_value',
'key':'value'
}
}
I am trying pass list of child Ids in MongoRepository method in order to retrieve Parent objects but getting null values. Below is my code.
import org.bson.types.ObjectId;
class MyRepository extends CrudRepository<Parent,Long> {
#Query("{'child._id': {$in : ?0 }}")
List<Parent> findByChild_IdIn(List<ObjectId> childIds);
}
I am invoking my method as shown below.
import org.bson.types.ObjectId;
List<String> childrenIds = getChildrenIdList();
List<ObjectId> childIds = childrenIds.stream().map(childrenId -> new ObjectId(childrenId)).collect(Collectors.toList());
List<Parent> parentsList = myRepository.findByChild_IdIn(childIds);
What am I doing wrong here? Why it is giving null values.

TL;DR
Annotate the id field in the inner document with #MongoId.
Longer answer
As mentioned in this answer, you would usually not require an _id field in a child document:
The _id field is a required field of the parent document, and is typically not necessary or present in embedded documents. If you require a unique identifier, you can certainly create them, and you may use the _id field to store them if that is convenient for your code or your mental model; more typically, they are named after what they represent (e.g. "username", "otherSystemKey", etc). Neither MongoDB itself, nor any of the drivers will automatically populate an _id field except on the top-level document.
In other words, unlike in RDMBS "normalized" schemas, here you would not require the children documents to have a unique id.
You may still want though that your children documents contain some kind of "id" field that refers to a document in another collection (eg "GoodBoys").
But indeed, for whatever reason, you may require a "unique" field in those inner children documents.
The following model would support the proposed structure:
#Data
#Builder
#Document(collection = "parents")
public class Parent {
#Id
private String id;
private String name;
private Child child;
#Data
#Builder
public static class Child {
#MongoId
private String id;
private String name;
private String key;
}
}
You can retrieve parents by list of ids with either of:
public interface ParentsRepository extends MongoRepository<Parent, String> {
// QueryDSL
List<Parent> findByChildIdIn(List<String> ids);
// Native Query
#Query("{'child._id': {$in: ?0}}")
List<Parent> findByChildrenIds(List<String> ids);
}
Add a few parents with something like:
parentsRepository.saveAll(Arrays.asList(
Parent.builder()
.name("parent1")
.child(Parent.Child.builder().id(new ObjectId().toString()).name("child1").key("value1").build())
.build(),
Parent.builder()
.name("parent2")
.child(Parent.Child.builder().id(new ObjectId().toString()).name("child2").key("value2").build())
.build()
));
The resulting entries in MongoDB will look like this:
{
"_id" : ObjectId("5e07384596d9077ccae89a8c"),
"name" : "parent1",
"child" : {
"_id" : "5e07384596d9077ccae89a8a",
"name" : "child1",
"key" : "value1"
},
"_class" : "com.lubumbax.mongoids.model.Parent"
}
Note two important things here:
The parent id in Mongo is an actual ObjectId that is de facto unique.
The child id in Mongo is a string that we can assume as being unique.
This approach works fine for this case in which we either have inserted the children ids from Java to MongoDB with new ObjectId().toString() or in whatever other way, as long as the resulting children id is just a string in MongoDB.
That means that the children ids are not strictly said an ObjectId in MongoDB.
#Id in the Child
If we annotate the children id field with #MongoId, the resulting query will be something like:
StringBasedMongoQuery: Created query Document{{child._id=Document{{$in=[5e0740e41095314a3401e49c, 5e0740e41095314a3401e49d]}}}} for Document{{}} fields.
MongoTemplate : find using query: { "child._id" : { "$in" : ["5e0740e41095314a3401e49c", "5e0740e41095314a3401e49d"]}} fields: Document{{}} for class: com.lubumbax.mongoids.model.Parent in collection: parents
If instead we annotate the children id field with #Id, the resulting query will be:
StringBasedMongoQuery: Created query Document{{child._id=Document{{$in=[5e0740e41095314a3401e49c, 5e0740e41095314a3401e49d]}}}} for Document{{}} fields.
MongoTemplate : find using query: { "child._id" : { "$in" : [{ "$oid" : "5e0740e41095314a3401e49c"}, { "$oid" : "5e0740e41095314a3401e49d"}]}} fields: Document{{}} for class: com.lubumbax.mongoids.model.Parent in collection: parents
Note the $oid there. The MongoDB Java driver expects the id attribute in MongoDB to be an actual ObjectId, so it tries to "cast" our string to an $oid.
The problem with that is that in MongoDB, the _id attribute of the children is simply a string and not a MongoDB's ObjectId. Thus, our repository method won't find our parent!
All ObjectId()
What happens if we insert a new document to MongoDB where the child _id is an actual ObjectId rather than a string?:
> db.parents.insert({
name: "parent3",
child: {
_id: ObjectId(),
name: "child3",
key: "value3"
}
});
The resulting entry is:
> db.parents.find({});
{
"_id" : ObjectId("5e074233669d34403ed6bcd2"),
"name" : "parent3",
"child" : {
"_id" : ObjectId("5e074233669d34403ed6bcd1"),
"name" : "child3",
"key" : "value3"
}
}
If we try to find this one now whith the #MongoId annotated child _id field, we won't find it!
The resulting query would be:
StringBasedMongoQuery: Created query Document{{child._id=Document{{$in=[5e074233669d34403ed6bcd1]}}}} for Document{{}} fields.
MongoTemplate : find using query: { "child._id" : { "$in" : ["5e074233669d34403ed6bcd1"]}} fields: Document{{}} for class: com.lubumbax.mongoids.model.Parent in collection: parents
Why? Because now the _id attribute in MongoDB is an actual ObjectId and we are trying to query it as a plain string.
We might be able to workaround that by tweaking the query with SpEL, but IMHO we are entering "Land of Pain".
That document would be found though, as we might expect, if we annotated the child _id field with #Id:
StringBasedMongoQuery: Created query Document{{child._id=Document{{$in=[5e074233669d34403ed6bcd1]}}}} for Document{{}} fields.
MongoTemplate : find using query: { "child._id" : { "$in" : [{ "$oid" : "5e074233669d34403ed6bcd1"}]}} fields: Document{{}} for class: com.lubumbax.mongoids.model.Parent in collection: parents
Once again, and as suggested at the top of this answer, I would discourage you from using ObjectId in children documents.
Some conclusions
As stated above, I would discourage anyone from using ObjectId in children documents.
A 'parents' entry in our MongoDB collection is what is unique. What that document contains may, or may not, represent a Parent entity in our Java application.
This is one of the pilars of NoSQL, in comparison to RDBMS where we would "tend to" normalize our schemas.
From that point of view, there is not such a thing as "part of the information in a document is unique" like, the nested children are unique.
The children would better be called "children element" (maybe there is a better name for this) rather than "chilren documents", because they are not actual documents but rather a part of a "parents" document (for those parents that happen to have a "child" element in their structure).
If we still want to somehow "link" the nested children elements to "exclusive" (or unique) documents in another collection, we can do so indeed (see below).
We just
Refer from nested element to documents in another collection
That in my opinion is a better practise.
The idea is that a document in a collection contains all we need in order to represent an entity.
For instance, in order to represent parents, apart of their names, age, and any other information specific of the parents, we might just want to know the name of their child (in a world where parents have only one child).
If we needed to access to more information of those children at some point we may have another collection with children detailed information. We could thus "link" or "refer" from the nested children in the parents collection to the documents in the children collection.
Our Java model would look something like this for the Children:
#Data
#Builder
#Document(collection = "children")
public class Child {
#Id
private String id;
private String name;
private String key;
private Integer age;
}
A Parent entity would not have any "hard" relation to a Child entity:
#Data
#Builder
#Document(collection = "parents")
public class Parent {
#Id
private String id;
private String name;
private ChildData child;
#Data
#Builder
public static class ChildData {
private String id;
private String name;
}
}
Note that in this case I prefer to name the nested children objects as Parent.ChildData, because it contains just the information I need about a child as to represent a Parent entity.
Note as well that I am not annotating the nested child id field with #Id. Per convention, MappingMongoConverter in this case will anyways map a field called id to a mongo _id field.
Given that there is no relation in MongoDB (as we understand them in RDBMS) between a nested child id and the children ids, we may even rename the ChildData.id field to ChildData.link.
You can see an example of this idea in the LinkitAir PoC.
There, the Flight entity model (stored in MongoDB in a flights collection) contains a nested AirportData document that via its code field "refers" to Airport entities (stored in MongoDB in a airports collection).

Related

Spring MongoRepository not returning id field of nested objects [duplicate]

I have a document with an array field with
"chapters": [
{ "id" : "14031871223912313", ...}
...
]
I would like to query return the id's with Spring Data's MongoTemplate using the following:
class Chapter {
private String id;
public String getId() {
return id;
}
}
This way the id is not populated. I have tried using the different mapping options with #Field described here http://docs.spring.io/spring-data/mongodb/docs/current/reference/html/#mapping.conventions.id-field
What am I doing wrong? I know I can always to back to mongo java driver, but I thought this should work.
Thanks in advance for any help.
Found a solution. It is populated via:
#Field("id")
private String chaperId
In MongoDB id's are _id, and every document in mongo has an _id. From the document you linked to, Spring will map #Field String id to mongo's _id field. You probably want to use a #Field('id') String id field mapping to indicate that the field you want is id not _id.

Morphia - Map single collection data into multiple pojos

I have a collection with 40 fields.
I have pojo as follows
#Entity("colName")
public class Entity1 {
//Id
//10 fields - names same as collection columns
#Embedded
//Entity2
}
Entity2 class:
#Embedded
public class Entity2 {
//20 other fields
}
I query the collection as follows
datastore.createQuery(Entity1.class).disableValidation().filter("fieldFromEntity1", "2227536").asList();
But I get always Entity2 reference as null.
I found that Entity2 has to be nested document.
Is there any way to achieve this?
Document:
{
_id: a|b|c,
field1: stringValue,
....
field40: intgerValue
}
Morphia doesn't directly support what you need. However, you can use #Preload and #Presave to manipulate the shapes of documents on their way to/from the database. I think you could also just use, say, Jackson annotations to name/nest those fields when serializing out to json. That'd be the simplest approach.

query based on matching elements in DBRef list for mongodb using spring-data-mongodb

I am pretty new to mongodb. I am using spring-data-mongodb for my queries from java. Please guide me if this is achievable.
Say I have two objects "Car" and "User" as following, where car has list of users,
Class Car {
#Id
String id;
String model;
#DBRef
List<User> users;
#DBRef
Company company;
}
Class User {
#Id
String id;
String name;
}
I want to find all cars for a user, (find all cars where car.users has given user)
Is it possible to achieve using spring-data-mongodb?
It's pretty easy if there was only one DBRef element, eg, for company I can write a query like this,
new Query(Criteria.where("company.$id").is(new ObjectId(companyId)))
But, how to achieve this if there is a list of elements referenced as DBRef??
Thanks for help.
Querying for one element on an array is exactly like query for a field equality. You could read the MongoDB documentation here. So your query will be:
new Query(Criteria.where("users.$id").is(new ObjectId(userId)))
in repository interface type this query on the method:
#Query("{'company' :{'$ref' : 'company' , '$id' : ?0}}")
Company find(String companyId);

MongoDB: query by #DBRef

I have a class hierarchy designed for store user notifications:
#Document
public class Notification<T> {
#Id
private String id;
#DBRef
private T tag;
...
}
#Document
public class NotificationA extends Notification<WrappedA> {
}
#Document
public class NotificationB extends Notification<WrappedB> {
}
...
This is useful for returning polymorphic arrays, allowing me to store any kind of data in the "tag" field. The problem starts when the wrapped objects contains #DBRef fields:
#Document
public class WrappedA {
#Id
private String id;
#DBRef
private JetAnotherClass referenced;
...
}
Queries on the fields of "tag" works fine:
db.NotificationA.find( {"tag.$id": ObjectId("507b9902...32a")} )
But I need to query on the fields of JetAnotherClass (two levels of #DBRef fields). I've tried with dot notation and also with subobjects but it returns null:
Dot notation:
db.NotificationA.findOne( {"tag.$referenced.$id": ObjectId("508a7701...29f")} )
Subobjects:
db.NotificationA.findOne( {"tag.$referenced": { "_id": ObjectId("508a7701...29f") }} )
Any help?
Thanks in advance!
Since you look like you are only querying by _id I believe you can do:
db.NotificationA.findOne({"tag.$id": ObjectId("blah")});
However:
But I need to query on the fields of JetAnotherClass (two levels of #DBRef fields).
DBRefs are not JOINs, they are merely a self describing _id in the event that you do not know the linking collection it will create a helper object so you don't have to code this yourself on the client side.
You can find more on DBRefs here: http://docs.mongodb.org/manual/applications/database-references/
Basically you can query the sub fields within the DBRef from the same document, i.e.: DBRef.$_id but you cannot, server-side, resolve that DBRef and query on the resulting fields.

spring data - Mongodb - findBy Method for nested objects

I have two domain objects,
#Document
public class PracticeQuestion {
private int userId;
private List<Question> questions;
// Getters and setters
}
#Document
public class Question {
private int questionID;
private String type;
// Getters and setters
}
My JSON doc is like this,
{
"_id" : ObjectId("506d9c0ce4b005cb478c2e97"),
"userId" : 1,
"questions" : [
{
"questionID" : 1,
"type" : "optional"
},
{
"questionID" : 3,
"type" : "mandatory"
}
]
}
I have to update the "type" based on userId and questionId, so I have written a findBy query method inside the custom Repository interface,
public interface CustomRepository extends MongoRepository<PracticeQuestion, String> {
List<PracticeQuestion> findByUserIdAndQuestionsQuestionID(int userId,int questionID);
}
My problem is when I execute this method with userId as 1 and questionID as 3, it returns the entire questions list irrespective of the questionID. Is the query method name valid or how should I write the query for nested objects.
Thanks for any suggestion.
Just use the #Query annotation on that method.
public interface CustomRepository extends MongoRepository<PracticeQuestion, String> {
#Query(value = "{ 'userId' : ?0, 'questions.questionID' : ?1 }", fields = "{ 'questions.questionID' : 1 }")
List<PracticeQuestion> findByUserIdAndQuestionsQuestionID(int userId, int questionID);
}
By adding the fields part of the #Query annotation, you are telling Mongo to only return that part of the document. Beware though, it still returns the entire document in the same format - just missing everything you did not specify. So your code will still have to return List<PracticeQuestion> and you will have to do:
foreach (PracticeQuestion pq : practiceQuestions) {
Question q = pq.getQuestions().get(0); // This should be your question.
}
Property expressions
Property expressions can refer only to a direct property of the managed entity, as shown in the preceding example. At query creation time you already make sure that the parsed property is a property of the managed domain class. However, you can also define constraints by traversing nested properties. Assume Persons have Addresses with ZipCodes. In that case a method name of List<Person> findByAddressZipCode(ZipCode zipCode);
creates the property traversal x.address.zipCode. The resolution algorithm starts with interpreting the entire part (AddressZipCode) as the property and checks the domain class for a property with that name (uncapitalized). If the algorithm succeeds it uses that property. If not, the algorithm splits up the source at the camel case parts from the right side into a head and a tail and tries to find the corresponding property, in our example, AddressZip and Code. If the algorithm finds a property with that head it takes the tail and continue building the tree down from there, splitting the tail up in the way just described. If the first split does not match, the algorithm move the split point to the left (Address, ZipCode) and continues.
Although this should work for most cases, it is possible for the algorithm to select the wrong property. Suppose the Person class has an addressZip property as well. The algorithm would match in the first split round already and essentially choose the wrong property and finally fail (as the type of addressZip probably has no code property). To resolve this ambiguity you can use _ inside your method name to manually define traversal points. So our method name would end up like so:
UserDataRepository:
List<UserData> findByAddress_ZipCode(ZipCode zipCode);
UserData findByUserId(String userId);
ProfileRepository:
Profile findByProfileId(String profileId);
UserDataRepositoryImpl:
UserData userData = userDateRepository.findByUserId(userId);
Profile profile = profileRepository.findByProfileId(userData.getProfileId());
userData.setProfile(profile);
Sample Pojo :
public class UserData {
private String userId;
private String status;
private Address address;
private String profileId;
//New Property
private Profile profile;
//TODO:setter & getter
}
public class Profile {
private String email;
private String profileId;
}
For the above Document/POJO in your Repository Class:
UserData findByProfile_Email(String email);
For ref : http://docs.spring.io/spring-data/data-commons/docs/1.6.1.RELEASE/reference/html/repositories.html
You need to use Mongo Aggregation framework :
1) Create custom method for mongo repository : Add custom method to Repository
UnwindOperation unwind = Aggregation.unwind("questions");
MatchOperation match = Aggregation.match(Criteria.where("userId").is(userId).and("questions.questionId").is(questionID));
Aggregation aggregation = Aggregation.newAggregation(unwind,match);
AggregationResults<PracticeQuestionUnwind> results = mongoOperations.aggregate(aggregation, "PracticeQuestion",
PracticeQuestionUnwind.class);
return results.getMappedResults();
2) You need to cretae a class(Because unwind operation has changed the class structure) like below :
public class PracticeQuestionUnwind {
private String userId;
private Question questions;
This will give you only those result which matches the provide userId and questionId
Result for userId: 1 and questionId : 111 :
{
"userId": "1",
"questions": {
"questionId": "111",
"type": "optional"
}
}
i too had similar issue. for that i added $ before the nested class attributes.
try below query
#Query(value = "{ 'userId' : ?0, 'questions.$questionID' : ?1 }") List<PracticeQuestion> findPracticeQuestionByUserIdAndQuestionsQuestionID(int userId, int questionID);