Spring Data MongoDB field projection problems - spring-data

I use spring data mongodb repository like this
public interface StringFieldSelectRepository extends MongoRepository<String, ObjectId> {
#Query(value="{}", fields="{'key':1}")
Collection<String> allKeys();
}
My Document spec like this...
{
_id : 1236f9391239,
key : "aa77mykeys",
name : "myname",
num : 389
}
I intend to extract only key field. I want return like this when I call allKeys();
["myKey1","myKey2","etc"...]
Is it impossible? Thanks for your help

Related

MongoDB - find where BinData is equal to UUID value

I try to write application in Java and Spring Data MongoDB.
My document looks:
#Data
#ToString
public class SomeDocument {
private UUID id;
private String name;
}
Repository:
#Repository
public interface SomeDocumentMongoRepository extends MongoRepository<SomeDocument, UUID> {
}
It's very simple. And I saved the document:
{
"id": "5f4ac46b-55f7-4be4-b26f-2ca041334bec",
"name": "some name"
}
Then I tried to read it from database using simple query db.someDocument.find() and I've got the result:
{ "_id" : BinData(3,"5Ev3VWvESl/sSzNBoCxvsg=="), "name" : "some name", "_class" : "org.springmongodemo.repo.SomeDocument" }
So my questions are:
How to find in mongo shell document by _id using given UUID?
What does mean number 3 in BinData(3,...)?
To find the document you'd have to encode your UUID into base64. Please see this question.
"3" is the identifier for legacy UUID data format. For more please see here.
If you want to query from in-app you should use Spring Data's features, but from your question I assume you want to know how to query using mongo native tools.

mongodb pojo codec conversion missing id field

I have a following document structure in mongo
{
"_id" : 4771902,
"upc" : "test-upc-v1",
"reportingCategory" : {
"id" : 14,
"department" : "Footwear"
}
}
My java class looks like
public class Product {
private Long _id;
private String upc;
private ReportingCategory reportingCategory;
}
public class ReportingCategory {
private Long id;
private String department;
}
I am using mongo pojo codec for conversion. "id" field under ReportingCategory is being returned as null.
Rest every other data is available. I can see that data when I convert it into RawBsonDocument, but seems like it gets lost in pojo conversion.
"id" field has no index on it, and is not used to uniquely identify this document.
Has anyone faced something similar and any work around for it ?
P.S. I am using mongo 3.6, with 3.6 async driver.
This is indeed a feature/bug in mongodb java driver.
Anyone looking for a reason and a solution for this can find one here https://jira.mongodb.org/browse/JAVA-2750

Morphia disable loading of #referenced entities

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.

How to update document in mongo to get performance?

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"
}

Remove an array entry in an object using spring data mongodb

I recently spent some time trying to use the $pull operator through Spring's Data MongoOperations interface, so I thought it would be nice to share my findings in case anyone bumps into a similar problem.
So here it goes...
I have 2 java POJOs like so :
#Document
public class OutterObject{
private String id;
private String name;
private List<InnerDocument> innerDocs;
//SETTERS - GETTERS Ommited
public class InnerDocument{
private String id;
private String name;
//SETTERS - GETTERS Ommited
This is stored in a Mongo collection like so :
"_id" : "doc2",
"_class" : "OutterObject",
"name" : "doc2",
"innerDocs" : [{
"_id" : "innerDoc21",
"name" : "innerDoc21"
}, {
"_id" : "innerDoc22",
"name" : "innerDoc22"
}]
I'm trying to use the $pull operator in order to remove all objects inside the innerDoc collection having a name value = "innerDoc22".
I know how to accomplish this using the mongo driver like so :
List<String> ids =
Arrays.asList("innerDoc22");
BasicDBObject find = new BasicDBObject();
match.put("innerDocs.name",
BasicDBObjectBuilder.start("$in", ids).get());
BasicDBObject update = new BasicDBObject();
update.put(
"$pull",
BasicDBObjectBuilder.start("innerDocs",
BasicDBObjectBuilder.start("name", "innerDoc22").get()).get());
DBCollection col= mongoOperations.getDb().getCollection("outterObject");
col.update(find , update);
I'm trying to accomplish the same thing using Spring's MongoOperations Interface.
Here is my code using the MongoOperations interface :
List<String> ids = Arrays.asList("innerDoc22");
Query removeQuery = Query.query(Criteria.where("innerDocs.name").in(ids));
WriteResult wc = mongoOperations.upsert(
removeQuery,
new Update().pull("innerDocs.name", "innerDoc22"),
OutterObject.class);
System.out.println(wc.getLastError());
I'm not getting any errors when calling getLastError() the update is simply not done in the database.
I know a similar question has already been asked here but the answer that was given does not use the MongoOperations interface.
After searching a bit and looking at the source code I realized that I needed to pass an InnerDocument object as a second parameter to the pull method so that the spring classes would be able to do the mapping correctly.
As it turns out I can navigate objects while selecting objects (I'm using "innerDocs.name" in the removeQuery) but I cannot (or havent found a way) do the same when updating a document.
Below is how I implemented the query using MongoOperations :
List<String> ids = Arrays.asList("innerDoc22", "innerDoc21");
Query removeQuery = Query.query(Criteria.where("innerDocs.name").in(ids));
WriteResult wc =
mongoOperations.upsert(removeQuery,
new Update().pull("innerDocs",
new InnerDocument("innerDoc22", null)),
OutterObject.class);
System.out.println(wc.getLastError());
You can also use the BasicDBObject instead of the InnerDocument I found this out by accident. By printing out the update object, you can see the actual mongo shell query json, which is super helpful for debugging.
:
Update updateObj = new Update()
.pull("innerDocs", new BasicDBObject("innerDocs.name","innerDoc22"));
System.out.println("UPDATE OBJ: " + updateObj.toString());
results in:
UPDATE OBJ: { "$pull" : { "innerDocs" : { "innerDocs.name" : "innerDoc22"}}}
I tried the solution given by med116 and I had to modify it to work :
Update updateObj = new Update().pull("innerDocs", new BasicDBObject("name","innerDoc22"));
Because otherwise there was no matching object in my array.
in spring data mongo,just like this:
//remove array's one elem
UpdateResult wc = mongoTemplate.upsert(removeQuery,new Update().pull("tags",Query.query(Criteria.where("tag").is("java"))),TestPull.class);
//remove array's multi-elem
UpdateResult wc1 = mongoTemplate.upsert(removeQuery,new Update().pull("tags",Query.query(Criteria.where("tag").in(Arrays.asList("mongo", "netty")))),TestPull.class);
If you simply want to remove an element from array which does not have any other property like name then write the query you wish and
Update update = new Update();
update.pull("Yourarrayname","valueyouwishtodelete");
mongoTemplate.upsert(query,update,"collectionname");
That's my solution - thanks to #ufasoli:
public Mono<ProjectChild> DeleteCritTemplChild(String id, String idch) {
Query query = new Query();
query.addCriteria(Criteria
.where("_id")
.is(id)
.and("tasks._id")
.is(idch)
);
Update update = new Update();
update.set("tasks.$", null);
return template
// findAndModify:
// Find/modify/get the "new object" from a single operation.
.findAndModify(
query, update,
new FindAndModifyOptions().returnNew(true), ProjectChild.class
);
}
This works for me!
UpdateResult updateResult = mongoTemplate.updateMulti(new Query(where("id").is(activity.getId())), new Update().pull("comments", new Query(where("id").is(commentId))), Activity.class);
It will generate:
Calling update using query: { "_id" : { "$oid" : "61f900e7c7b79442eb3855ea"}} and update: { "$pull" : { "comments" : { "_id" : "61fac32e3f9ab5646e016bc8"}}} in collection: activity