I'm trying to save and load an object containing a #DBRef to another object that happens to be of a child class type and Spring Data MongoDB loads the field as null.
class Person {
#Id
private long id;
#DBRef
private Food food;
}
class Food {
#Id
private long id;
}
class Burger extends Food {}
I'm saving the objects separately:
Burger burger = new Burger();
foodRepository.save(burger);
Person person = new Person();
person.setFood(burger);
personRepository.save(person);
What happens is that the burger object gets saved in the food collection with the _class value of Burger in MongoDB and the $ref in the Person document points to burger and not food.
person collection:
{
"_id" : NumberLong(1),
"food" : {
"$ref" : "burger",
"$id" : NumberLong(2)
},
"_class" : "Person"
}
food collection:
{
"_id" : NumberLong(2),
"_class" : "Burger"
}
If I load the object using findAll() or findById(), the food field is null. But if i use findByFood() with the burger object, the person object is returned. What am I not understanding correctly here?
personRepository.findById(1L).getFood(); // null
personRepository.findByFood(burger); // Person object
This got answered in the Spring Data MongoDB JIRA board by Christoph Strobl. Pasting his answer below in case anyone finds this. I personally picked Option 1 to solve my issue.
Persisting different types of Food via a Repository uses the Repository interface type argument to determine the actual MongoDB collection.
In this case all subtypes of Food will end up in the food collection, unless they are explicitly redirected via the collection attribute of the #Document annotation.
The Person repository on the other hand does not know about the Food Repository and the collection routing to the food collection. So the mapping sees the Burger entity in Person#food and creates a reference to the burger collection, that then cannot be resolved, leading to the null value you see.
{
"_id" : 1,
"food" : {
"$ref" : "burger",
"$id" : 1
},
"_class" : "Person"
}
You could try one of the following:
Use the #Document annotation and explicitly name the collection to store Food and it sub types, so that the mapping layer can set the $ref correctly.
#Document("food")
class Food {
#Id private long id;
}
Use MongoOpperations#save to persist Food. That way every subclass will end up in its own collection, which allows "$ref" : "burger" to be resolved.
Related
I have 2 collections.
#Entity
public class TypeA {
//other fields
#Reference
List<TypeB> typeBList;
}
#Entity
public class TypeB{
//Fields here.
}
After a save operation, sample TypeA document is as below :
{
"_id" : ObjectId("58fda48c60b200ee765367b1"),
"typeBList" : [
{
"$ref" : "TypeB",
"$id" : ObjectId("58fda48c60b200ee765367ac")
},
{
"$ref" : "TypeB",
"$id" : ObjectId("58fda48c60b200ee765367af")
}
]
}
When I query for TypeA , morphia eagerly loads all the TypeB entites, which I dont want.
I tried using the #Reference(lazy = true). But no help.
So is there a way I can write a query using morphia where I only get all $ids inside the typeBList?
Have a list ob ObjectIds instead of a #Reference and manually fetch those entries when you need them.
Lazy will only load referenced entities on demand, but since you are trying to access something in that list, they will be loaded.
Personal opinion: #Reference looks great when you start, but its use can quickly cause issues. Don't build a schema with lots of references — MongoDB is not a relational database.
I am new to Spring Data Mongo. I've a scenario where I want to create a Study if already not present in mongo db. If its already present, then I've to update it with the new values.
I tried in the following way, which works fine in my case, but I'm not sure this is the correct/Best/Advisable way to update etc as far as performance is concerned.
Could anyone please guide on this?
public void saveStudy(List<Study> studies) {
for (Study study : studies) {
String id = study.getId();
Study presentInDBStudy = studyRepository.findOne(id);
//find the document, modify and update it with save() method.
if(presentInDBStudy != null) {
presentInDBStudy.setTitle(task.getTitle());
presentInDBStudy.setDescription(study.getDescription());
presentInDBStudy.setStart(study.getStart());
presentInDBStudy.setEnd(study.getEnd());
repository.save(presentInDBStudy);
}
else
repository.save(study);
}
}
You will have to use the MongoTemplate.upsert() to achieve this.
You will need to add two more classes: StudyRepositoryCustom which is an interface and a class that extends this interface, say StudyRepositoryImpl
interface StudyRepositoryCustom {
public WriteResult updateStudy(Study study);
}
Update your current StudyRepository to extend this interface
#Repository
public interface StudyRepository extends MongoRepository<Study, String>, StudyRepositoryCustom {
// ... Your code as before
}
And add a class that implements the StudyRepositoryCustom. This is where we will #Autowire our MongoTemplate and provide the implementation for updating a Study or saving it if it does not exist. We use the MongoTemplate.upsert() method.
class StudyRepositoryImpl implements StudyRepositoryCustom {
#Autowired
MongoTemplate mongoTemplate;
public WriteResult updateStudy(Study study) {
Query searchQuery = new Query(Criteria.where("id").is(study.getId());
WriteResult update = mongoTemplate.upsert(searchQuery, Update.update("title", study.getTitle).set("description", study.getDescription()).set(...)), Study.class);
return update;
}
}
Kindly note that StudyRepositoryImpl will automatically be picked up by the Spring Data infrastructure as we've followed the naming convention of extending the core repository interface's name with Impl
Check this example on github, for #Autowire-ing a MongoTemplate and using custom repository as above.
I have not tested the code but it will guide you :-)
You can use upsert functionality for this as described in mongo documentation.
https://docs.mongodb.com/v3.2/reference/method/db.collection.update/
You can update your code to use <S extends T> List<S> save(Iterable<S> entites); to save all the entities. Spring's MongoRepository will take care of all possible cases based on the presence of _id field and its value.
More information here https://docs.mongodb.com/manual/reference/method/db.collection.save/
This will work just fine for basic save operations. You don't have to load the document for update. Just set the id and make sure to include all the fields for update as it updates by replacing the existing document.
Simplified Domain Object:
#Document(collection = "study")
public class Study {
#Id
private String id;
private String name;
private String value;
}
Repository:
public interface StudyRepository extends MongoRepository<Study, String> {}
Imagine you've existing record with _id = 1
Collection state before:
{
"_id" : 1,
"_class" : "com.mongo.Study",
"name" : "saveType",
"value" : "insert"
}
Run all the possible cases:
public void saveStudies() {
List<Study> studies = new ArrayList<Study>();
--Updates the existing record by replacing with the below values.
Study update = new Study();
update.setId(1);
update.setName("saveType");
update.setValue("update");
studies.add(update);
--Inserts a new record.
Study insert = new Study();
insert.setName("saveType");
insert.setValue("insert");
studies.add(insert);
--Upserts a record.
Study upsert = new Study();
upsert.setId(2);
upsert.setName("saveType");
upsert.setValue("upsert");
studies.add(upsert);
studyRepository.save(studies);
}
Collection state after:
{
"_id" : 1,
"_class" : "com.mongo.Study",
"name" : "saveType",
"value" : "update"
}
{
"_id" : 3,
"_class" : "com.mongo.Study",
"name" : "saveType",
"value" : "insert"
}
{
"_id" : 2,
"_class" : "com.mongo.Study",
"name" : "saveType",
"value" : "upsert"
}
I am using MongoTemplate's UpdateFirst() to update the inner document. But the update operation is failing.
my Mongo Collection Structure:
{
"_id" : ObjectId("540dfaf9615a9bd62695b2ed"),
"_class" : "com.springpoc.model.Awards",
"brand" : [
{
"name" : "Brand1",
"descr" : "Desc1"
}
],
"name" : "Award1",
"narname" : "Nar1"
}
Java Code:
mongoTemplate.updateFirst(
query(where("name").is("Award1")),
Update.update("brand.$.descr", "Desc2"),
Awards.class);
Award Class Structure
public class Awards {
private String id;
List<Brand> brand = new ArrayList<Brand>();
private String name;
private String narname;
Brand Class Structure
public class Brand {
private String name;
private String descr;
Any suggestions on how to update the document "Brand.Name" to new value.
If you want to use operator $ in update portion, you have to explicitly write that array in the query portion. So,
mongoTemplate.updateFirst(
query(where("name").is("Award1")),
Update.update("brand.$.descr", "Desc2"),
Awards.class);
should be
mongoTemplate.updateFirst(
query(where("name").is("Award1"))
.and("brand.name").is("Brand1"), // "brand" in "brand.name" is necessary, others according to your requirement
Update.update("brand.$.descr", "Desc2"),
Awards.class);
If you know the position of element in array, `$' is unnecessary, you can try like this:
mongoTemplate.updateFirst(
query(where("name").is("Award1")),
Update.update("brand.0.descr", "Desc2"), // 0 is the index of element in array
Awards.class);
Same way to handle name field.
Is it possible to retrieve the specific value in an array of objects inside an another object in mongodb??
#Document
class vehicleStation {
#Id
String stationId;
List<car> cars;
}
#Document
class car{
#Id
String carNo;
String name;
}
And my json structure will look like below
{"_id":"0001","cars":[{"_id":"C001",
"name":"Honda"},{"_id":"C002","name":"Ford"}]}
Is it possible retrieve the value of "name" for a particular vehicleStation (stationId="0001" and carNo="C002") which is "Ford"
how to query the mongodb to get the value "Ford" for vehicleStation (stationId="0001" and carNo="C002")
You could do that using the $elemMatch projection operator. e.g. from the shell
> db.so.find({_id:"0001"},{cars:{$elemMatch:{"_id":"C002"}}})
{ "_id" : "0001", "cars" : [ { "_id" : "C002", "name" : "Ford" } ] }
Since you are doing an _id query at the top level this returns only one document. You can do
db.so.findOne({_id:"0001"},{cars:{$elemMatch:{"_id":"C002"}}}).cars[0]
to get the inner object or
db.so.findOne({_id:"0001"},{cars:{$elemMatch:{"_id":"C002"}}}).cars[0].name
to get only the name
Note - If you have multiple subobjects with the _id as C002 this will only the first match as documented.
I am trying to reproduce the classic blog schema of one Post to many Comments using Morphia and the Play Framework.
My schema in Mongo is:
{ "_id" : ObjectId("4d941c960c68c4e20d6a9abf"),
"className" : "models.Post",
"title" : "An amazing blog post",
"comments" : [
{
"commentDate" : NumberLong("1301552278491"),
"commenter" : {
"$ref" : "SiteUser",
"$id" : ObjectId("4d941c960c68c4e20c6a9abf")
},
"comment" : "What a blog post!"
},
{
"commentDate" : NumberLong("1301552278492"),
"commenter" : {
"$ref" : "SiteUser",
"$id" : ObjectId("4d941c960c68c4e20c6a9abf")
},
"comment" : "This is another comment"
}
]}
I am trying to introduce a social networking aspect to the blog, so I would like to be able to provide on a SiteUser's homepage the last X comments by that SiteUser's friends, across all posts.
My models are as follows:
#Entity
public class Post extends Model {
public String title;
#Embedded
public List<Comment> comments;
}
#Embedded
public class Comment extends Model {
public long commentDate;
public String comment;
#Reference
public SiteUser commenter;
}
From what I have read elsewhere, I think I need to run the following against the database (where [a, b, c] represents the SiteUsers) :
db.posts.find( { "comments.commenter" : {$in: [a, b, c]}} )
I have a List<SiteUser> to pass in to Morphia for the filtering, but I don't know how to
set up an index on Post for Comments.commenter from within Morphia
actually build the above query
Either put #Indexes(#Index("comments.commenter")) on the Post class, or #Indexed on the commenter field of the Comment class (Morphia's Datastore.ensureIndexes() will recurse in the classes and correctly create the comments.commenter index on the Post collection)
I think ds.find(Post.class, "comments.commenter in", users) would work, ds being a Datastore and users your List<SiteUser> (I don't use #Reference though, so I can't confirm; you might have to first extract the list of their Keys).