I'm beginner with MongoDB and i'm trying some stuff.
I want to store URL and to avoid duplicate URL I create an unique index on the url.
Like that
collection.createIndex(new BasicDBObject("url", type).append("unique", true));
But each time I launch my program the index is create again isn't it ?
Because, now my program is only inserting one url "http://site.com" and if I restart my program this url is insert again like if there isn't index.
Creating the index each time is the wrong way to handle an index ?
Here is an example of my code
mongo.getCollection().ensureIndex(new BasicDBObject("url", 1).append("unique", "true"));
mongo.getCollection().insert(new BasicDBObject("url", "http://site.com").append("crawled", 0));
mongo.getCollection().insert(new BasicDBObject("url", "http://site.com").append("crawled", 0));
And the output:
{ "_id" : { "$oid" : "50d627cf44ae5d6b5e9cf106"} , "url" : "http://site.com" , "crawled" : 0}
{ "_id" : { "$oid" : "50d627cf44ae5d6b5e9cf107"} , "url" : "http://site.com" , "crawled" : 0}
Thanks
EDIT :
Here is my class Mongo which handle MongoDB
import java.net.UnknownHostException;
import java.util.List;
import java.util.Set;
import com.mongodb.BasicDBObject;
import com.mongodb.DB;
import com.mongodb.DBCollection;
import com.mongodb.DBObject;
import com.mongodb.MongoClient;
public class Mongo {
private MongoClient mongoClient;
private DB db;
private DBCollection collection;
private String db_name;
public Mongo(String db){
try {
mongoClient = new MongoClient( "localhost" , 27017 );
this.db = mongoClient.getDB(db);
this.db_name = db;
} catch (UnknownHostException e) {
e.printStackTrace();
}
}
public void drop(){
mongoClient.dropDatabase(db_name);
}
public void listCollections(){
Set<String> colls = db.getCollectionNames();
for (String s : colls) {
System.out.println(s);
}
}
public void listIndex(){
List<DBObject> list = collection.getIndexInfo();
for (DBObject o : list) {
System.out.println("\t" + o);
}
}
public void setCollection(String col){
this.collection = db.getCollection(col);
}
public void insert(BasicDBObject doc){
this.collection.insert(doc);
}
public DBCollection getCollection(){
return collection;
}
public void createIndex(String on, int type){
collection.ensureIndex(new BasicDBObject(on, type).append("unique", true));
}
}
And here is my class which handle my program
public class Explorer {
private final static boolean DEBUG = false;
private final static boolean RESET = false;
private Mongo mongo;
private String host;
public Explorer(String url){
mongo = new Mongo("explorer");
mongo.setCollection("page");
if (RESET){
mongo.drop();
System.out.println("Set RESET to FALSE and restart the program.");
System.exit(1);
}
if (DEBUG) {
mongo.listCollections();
}
this.host = url.toLowerCase();
BasicDBObject doc = new BasicDBObject("url", "http://site.com").append("crawled", 0);
mongo.getCollection().ensureIndex(new BasicDBObject("url", 1).append("unique", true));
mongo.getCollection().insert(new BasicDBObject("url", "http://site.com").append("crawled", 0));
mongo.getCollection().insert(new BasicDBObject("url", "http://site.com").append("crawled", 0));
process();
}
private void process(){
BasicDBObject query = new BasicDBObject("crawled", 0);
DBCursor cursor = mongo.getCollection().find(query);
try {
while(cursor.hasNext()) {
System.out.println(cursor.next());
}
} finally {
cursor.close();
}
}
}
You'll need to pass the unique value as the boolean value true, not as a string, and it's the second parameter that are options:
...ensureIndex(new BasicDBObject("url", 1), new BasicDBObject("unique", true));
Also, I tested it manually using the mongo interpreter:
> db.createCollection("sa")
{ "ok" : 1 }
> db.sa.ensureIndex({"url":1},{unique:true})
> db.sa.insert({url:"http://www.example.com", crawled: true})
> db.sa.insert({url:"http://www.example.com", crawled: true})
E11000 duplicate key error index: test.sa.$url_1 dup key: { : "http://www.example.com" }
> db.sa.insert({url:"http://www.example2.com/", crawled: false})
> db.sa.insert({url:"http://www.example.com", crawled: false})
E11000 duplicate key error index: test.sa.$url_1 dup key: { : "http://www.example.com" }
>
There are only the two objects:
> db.sa.find()
{ "_id" : ObjectId("50d636baa050939da1e4c53b"), "url" : "http://www.example.com", "crawled" : true }
{ "_id" : ObjectId("50d636dba050939da1e4c53d"), "url" : "http://www.example2.com/", "crawled" : false }
I don't fully understand your problem but I feel it's very likely that you should use ensureIndex instead of createIndex as the latter always tries to create the index while the former will only ensure that it exists.
Just stumbled over this question and there are some changes since Version 3.0.0
db.collection.ensureIndex(keys, options)
Deprecated since version 3.0.0: db.collection.ensureIndex() is now an
alias for db.collection.createIndex().
Creates an index on the specified field if the index does not already
exist.
To Use the unique index of the mongodb, you should use the method with 2 parameters where 3rd boolean parameter is for the "unique" index.
mongo.getCollection().ensureIndex(new BasicDBObject("url", 1),"unq_url", true));
Also I see that you dont have a collection name specified in getCollection();
What collection would that select? curious
Related
How would I convert the following MongoDB query into a query to be used by my Java Spring application? I can't find a way to use pipeline with the provided lookup method.
Here is the query I am attempting to convert. I also want to note that I didn't use $unwind as I wanted the deliveryZipCodeTimings to stay as a grouped collection in the return object.
db.getCollection('fulfillmentChannel').aggregate([
{
$match: {
"dayOfWeek": "SOME_VARIABLE_STRING_1"
}
},
{
$lookup: {
from: "deliveryZipCodeTiming",
let: { location_id: "$fulfillmentLocationId" },
pipeline: [{
$match: {
$expr: {
$and: [
{$eq: ["$fulfillmentLocationId", "$$location_id"]},
{$eq: ["$zipCode", "SOME_VARIABLE_STRING_2"]}
]
}
}
},
{
$project: { _id: 0, zipCode: 1, cutoffTime: 1 }
}],
as: "deliveryZipCodeTimings"
}
},
{
$match: {
"deliveryZipCodeTimings": {$ne: []}
}
}
])
Building upon the info given by #dnickless, I was able to solve this. I'll post the complete solution in the hopes it helps someone else in the future.
I'm using mongodb-driver:3.6.4
First, I had to create a custom aggregation operation class so that I could pass in a custom JSON mongodb query to be used in the aggregation operation. This will allow me to use pipeline within a $lookup which is not supported with the driver version I am using.
public class CustomProjectAggregationOperation implements AggregationOperation {
private String jsonOperation;
public CustomProjectAggregationOperation(String jsonOperation) {
this.jsonOperation = jsonOperation;
}
#Override
public Document toDocument(AggregationOperationContext aggregationOperationContext) {
return aggregationOperationContext.getMappedObject(Document.parse(jsonOperation));
}
}
Now that we have the ability to pass a custom JSON query into our mongodb spring implementation, all that is left is to plug those values into a TypedAggregation query.
public List<FulfillmentChannel> getFulfillmentChannels(
String SOME_VARIABLE_STRING_1,
String SOME_VARIABLE_STRING_2) {
AggregationOperation match = Aggregation.match(
Criteria.where("dayOfWeek").is(SOME_VARIABLE_STRING_1));
AggregationOperation match2 = Aggregation.match(
Criteria.where("deliveryZipCodeTimings").ne(Collections.EMPTY_LIST));
String query =
"{ $lookup: { " +
"from: 'deliveryZipCodeTiming'," +
"let: { location_id: '$fulfillmentLocationId' }," +
"pipeline: [{" +
"$match: {$expr: {$and: [" +
"{ $eq: ['$fulfillmentLocationId', '$$location_id']}," +
"{ $eq: ['$zipCode', '" + SOME_VARIABLE_STRING_2 + "']}]}}}," +
"{ $project: { _id: 0, zipCode: 1, cutoffTime: 1 } }]," +
"as: 'deliveryZipCodeTimings'}}";
TypedAggregation<FulfillmentChannel> aggregation = Aggregation.newAggregation(
FulfillmentChannel.class,
match,
new CustomProjectAggregationOperation(query),
match2
);
AggregationResults<FulfillmentChannel> results =
mongoTemplate.aggregate(aggregation, FulfillmentChannel.class);
return results.getMappedResults();
}
I would like to add this my solution which is repeating in some aspect the solutions posted before.
Mongo driver v3.x
For Mongo driver v3.x I came to the following solution:
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
import com.mongodb.BasicDBList;
import com.mongodb.BasicDBObject;
import com.mongodb.util.JSON;
import org.bson.Document;
import org.springframework.data.mongodb.core.aggregation.AggregationOperation;
import org.springframework.data.mongodb.core.aggregation.AggregationOperationContext;
public class JsonOperation implements AggregationOperation {
private List<Document> documents;
public JsonOperation(String json) {
Object root = JSON.parse(json);
documents = root instanceof BasicDBObject
? Collections.singletonList(new Document(((BasicDBObject) root).toMap()))
: ((BasicDBList) root).stream().map(item -> new Document((Map<String, Object>) ((BasicDBObject) item).toMap())).collect(Collectors.toList());
}
#Override
public Document toDocument(AggregationOperationContext context) {
// Not necessary to return anything as we override toPipelineStages():
return null;
}
#Override
public List<Document> toPipelineStages(AggregationOperationContext context) {
return documents;
}
}
and then provided that aggregation steps are given in some resource aggregations.json:
[
{
$match: {
"userId": "..."
}
},
{
$lookup: {
let: {
...
},
from: "another_collection",
pipeline: [
...
],
as: "things"
}
},
{
$sort: {
"date": 1
}
}
]
one can use above class as follows:
import static org.springframework.data.mongodb.core.aggregation.Aggregation.newAggregation;
Collection<ResultDao> results = mongoTemplate.aggregate(newAggregation(new JsonOperation(resourceToString("aggregations.json", StandardCharsets.UTF_8))), "some_collection", ResultDao.class).getMappedResults();
Mongo driver v4.x
As JSON class was removed from Mongo v4, I have rewritten the class as follows:
import java.util.Collections;
import java.util.List;
import org.bson.Document;
import org.springframework.data.mongodb.core.aggregation.AggregationOperation;
import org.springframework.data.mongodb.core.aggregation.AggregationOperationContext;
public class JsonOperation implements AggregationOperation {
private List<Document> documents;
private static final String DUMMY_KEY = "dummy";
public JsonOperation(String json) {
documents = parseJson(json);
}
static final List<Document> parseJson(String json) {
return (json.startsWith("["))
? Document.parse("{\"" + DUMMY_KEY + "\": " + json + "}").getList(DUMMY_KEY, Document.class)
: Collections.singletonList(Document.parse(json));
}
#Override
public Document toDocument(AggregationOperationContext context) {
// Not necessary to return anything as we override toPipelineStages():
return null;
}
#Override
public List<Document> toPipelineStages(AggregationOperationContext context) {
return documents;
}
#Override
public String getOperator() {
return documents.iterator().next().keySet().iterator().next();
}
}
but implementation is now a bit ugly because of string manipulations. If somebody has a better idea of how to parse array of objects in a more elegant way, please edit this post or drop a comment. Ideally there should be some method in Mongo core that allows to parse either JSON object or list (returns BasicDBObject/BasicDBList or Document/List<Document>).
Also note that I have skipped the step of transforming Document instances in toPipelineStages() method as it is not necessary in my case:
#Override
public List<Document> toPipelineStages(AggregationOperationContext context) {
return documents.stream().map(document -> context.getMappedObject(document)).collect(Collectors.toList());
}
The drivers are pretty much always a little bit behind the current language features that MongoDB provides - hence some of the latest and greatest features are simply not nicely accessible through the API yet. I am afraid this is one of those cases and you'll need to resort to using strings. Kind of like so (untested):
AggregationOperation match = Aggregation.match(Criteria.where("dayOfWeek").is("SOME_VARIABLE_STRING_1"));
AggregationOperation match2 = Aggregation.match(Criteria.where("deliveryZipCodeTimings").ne([]));
String query = "{ $lookup: { from: 'deliveryZipCodeTiming', let: { location_id: '$fulfillmentLocationId' }, pipeline: [{ $match: { $expr: { $and: [ { $eq: ['$fulfillmentLocationId', '$$location_id']}, { $eq: ['$zipCode', 'SOME_VARIABLE_STRING_2']} ]} } }, { $project: { _id: 0, zipCode: 1, cutoffTime: 1 } }], as: 'deliveryZipCodeTimings' } }";
Aggregation.newAggregation(match, (DBObject) JSON.parse(query), match2);
I faced some JSON parsing exceptions when I used the way explained in the accepted answer, so I dig deep the default MongoDB java driver(version 3) Document class to build up aggregation query and found out any aggregation query can be build u as follows,
Replace each of the element in the mongo console query as follows
Curly braces({) -> new Document()
parameter names are same
Colon(:) -> Coma(,)
Coma(,) -> .append()
Square bracket([) -> Arrays.asList()
AggregationOperation customLookupOperation = new AggregationOperation() {
#Override
public Document toDocument(AggregationOperationContext context) {
return new Document(
"$lookup",
new Document("from", "deliveryZipCodeTiming")
.append("let",new Document("location_id", "$fulfillmentLocationId"))
.append("pipeline", Arrays.<Object> asList(
new Document("$match", new Document("$expr", new Document("$and",
Arrays.<Object>asList(
new Document("$eq", Arrays.<Object>asList("$fulfillmentLocationId", "$$location_id")),
new Document("$eq", Arrays.<Object>asList("$zipCode", "SOME_VARIABLE_STRING_2"))
)))),
new Document("$project", new Document("_id",0).append("zipCode", 1)
.append("cutoffTime", 1)
)
))
.append("as", "deliveryZipCodeTimings")
);
}
};
Finally you can use the aggregation operation in the aggrgation pipeline,
Aggregation aggregation = Aggregation.newAggregation(matchOperation,customLookupOperation,matchOperation2);
For anyone who finds a simple solution and don't want to bother with raw json queries here is wrapper:
#RequiredArgsConstructor
public class PipelineLookUpWrapper implements AggregationOperation {
private final LookupOperation lookup;
private final Aggregation pipelineAggregation;
#Override
public Document toDocument(AggregationOperationContext context) {
return lookup.toDocument(context);
}
#Override
public String getOperator() {
return lookup.getOperator();
}
#Override
public List<Document> toPipelineStages(AggregationOperationContext context) {
List<Document> lookUpPipelineStages = lookup.toPipelineStages(context);
Document lookUp = (Document) lookUpPipelineStages.iterator().next().get(getOperator());
lookUp.append("pipeline", pipelineAggregation.getPipeline().getOperations()
.stream()
.flatMap(operation -> operation.toPipelineStages(context).stream())
.toList());
return lookUpPipelineStages;
}
}
Usage:
var originalLookUp = Aggregation.lookup("from", "localField", "foreignField", "as");
Aggregation pipelineAggregation = Aggregation.newAggregation(Aggregation.match(new Criteria()), Aggregation.skip(1));
AggregationOperation lookUpWithPipeline = new PipelineLookUpWrapper(originalLookUp, pipelineAggregation);
I am working on a JHipster project with an AngularJS front-end and a Java back-end. I am using Spring data with the MongoDb database.
I did a grouping operation on the field budgetCode. So, for each budgetCode, I succeded to have the list of all the linked taskCodes.
Here, the method aggregateAllTaskCodes which does the grouping operation:
Repository layer
public class ClarityResourceAffectationRepositoryImpl implements ClarityResourceAffectationRepositoryCustom {
#Override
public List<ClarityResourceAffectationReport> aggregateAllTaskCodes() {
Aggregation aggregation = newAggregation(
group("budgetCode").addToSet("budgetCode").as("budgetCode").addToSet("taskCode").as("taskCode"),
sort(Sort.Direction.ASC, previousOperation(),"budgetCode"));
AggregationResults groupResults = mongoTemplate.aggregate(aggregation, ClarityResourceAffectation.class,
ClarityResourceAffectationReport.class);
List<ClarityResourceAffectationReport> clarityResourceAffectationReports = groupResults.getMappedResults();
return clarityResourceAffectationReports;
}
}
Service layer
public class ClarityResourceAffectationServiceImpl implements ClarityResourceAffectationService{
#Override
public List<ClarityResourceAffectationReport> aggregateAllTaskCodes() {
log.debug("Request to aggregateByCodeBudgetForCodeTache : {}");
List<ClarityResourceAffectationReport> result = clarityResourceAffectationRepository
.aggregateAllTaskCodes();
return result;
}
}
REST API layer
public class ClarityResourceAffectationResource {
#GetMapping("/clarity-resource-affectations/list-task-codes")
#Timed
public ResponseEntity<List<ClarityResourceAffectationReport>> aggregateTabAllTaskCodes() {
log.debug("REST request to get aggregateTabAllTaskCodes : {}");
List<ClarityResourceAffectationReport> result = clarityResourceAffectationService.aggregateAllTaskCodes();
return new ResponseEntity<>(result, HttpStatus.OK);
}
}
ClarityResourceAffectation
#Document(collection = "clarity_resource_affectation")
public class ClarityResourceAffectation implements Serializable {
#Id
private String id;
#Field("budget_code")
private String budgetCode;
#Field("task_code")
private String taskCode;
public String getBudgetCode() {
return budgetCode;
}
public void setBudgetCode(String budgetCode) {
this.budgetCode = budgetCode;
}
public String getTaskCode() {
return taskCode;
}
public void setTaskCode(String taskCode) {
this.taskCode = taskCode;
}
}
ClarityResourceAffectationReport
public class ClarityResourceAffectationReport implements Serializable {
private static final long serialVersionUID = 1L;
private String budgetCode;
private String taskCode;
private String listTaskCode;
public String getBudgetCode() {
return budgetCode;
}
public void setBudgetCode(String budgetCode) {
this.budgetCode = budgetCode;
}
public String getTaskCode() {
return taskCode;
}
public void setTaskCode(String taskCode) {
this.taskCode = taskCode;
}
public String[] getListTaskCode() {
return listTaskCode;
}
public void setListTaskCode(String[] listTaskCode) {
this.listTaskCode = listTaskCode;
}
}
clarity-resource-affectation.service.js
(function() {
'use strict';
angular
.module('dashboardApp')
.factory('ClarityResourceAffectation', ClarityResourceAffectation);
ClarityResourceAffectation.$inject = ['$resource'];
function ClarityResourceAffectation ($resource) {
var resourceUrl = 'clarity/' + 'api/clarity-resource-affectations/:id';
return $resource(resourceUrl, {}, {
'query': { method: 'GET', isArray: true},
'aggregateAllTaskCodes': {
method: 'GET',
isArray: true,
url: 'clarity/api/clarity-resource-affectations/list-task-codes'
}
});
}
})();
When I call the function in the AngularJS front-end and I display that on a table, for each budgetCode, I have the list of the taskCodes in an array of one element. For example, for the budgetCode [ "P231P00"] I can have this list of taskCodes: [ "61985" , "43606" , "60671" , "43602"]
Well, I would like to have the list of the linked taskCodes, not in an array of one element but in an array of several elements like that:
[ ["61985"] , ["43606"] , ["60671"] , ["43602"] ]
What do I have to change in my code in order to do that?
Just for information, my javascript code which create the array based on the aggregate function:
clarity-resource-affectation-list-task-codes.controller.js
(function() {
'use strict';
angular
.module('dashboardApp')
.controller('ClarityResourceAffectationTableauBordNbCollaborateursController', ClarityResourceAffectationTableauBordNbCollaborateursController);
ClarityResourceAffectationTableauBordNbCollaborateursController.$inject = ['$timeout', '$scope', '$stateParams', 'DataUtils', 'ClarityResourceAffectation'];
function ClarityResourceAffectationTableauBordNbCollaborateursController ($timeout, $scope, $stateParams, DataUtils, ClarityResourceAffectation) {
var vm = this;
//Call of the function
allTaskCodes()
function allTaskCodes()
{
ClarityResourceAffectation.aggregateAllTaskCodes(function(readings) {
var dataAllTaskCodes;
dataAllTaskCodes = [];
alert(readings);
readings.forEach(function (item) {
dataAllTaskCodes.push({
label: item.budgetCode,
value: item.taskCode,
listvalue: item.listTaskCode
});
});
vm.dataAllTaskCodes = dataAllTaskCodes;
});
}
}
})();
Temporary solution:
Actually, I found a temporary solution by completing the method I created in the Service Layer:
#Override
public List<ClarityResourceAffectationReport> aggregateAllTaskCodes() {
log.debug("Request to aggregateAllTaskCodes : {}");
List<ClarityResourceAffectationReport> result = clarityResourceAffectationRepository
.aggregateAllTaskCodes();
Iterator<ClarityResourceAffectationReport> iterator = result.iterator();
while (iterator.hasNext())
{
ClarityResourceAffectationReport resAffectationReport = iterator.next();
String taskCodes = resAffectationReport.getTaskCode();
//Delete all exept letters, numbers and comma
taskCodes = taskCodes.replaceAll("[^a-zA-Z0-9,]","");
String[] listTaskCodes = taskCodes.split(",");
resAffectationReport.setListTaskCodes(listTaskCodes);
}
return result;
}
Also, I added an additional field to ClarityResourceAffectationReport which is listTaskCode. I updated the report class above. Finally, when I do an alert:
alert(readings[1].listvalue[0]), I have a result like 2630. So, I succeeded to have the first taskCode of a particular budgetCode.
I understood that what is important here is not the fact that as I told above for a budgetCode like [ "P231P00"], I must have a list like: [ "61985" , "43606" , "60671" , "43602"] or [ ["61985"] , ["43606"] , ["60671"] , ["43602"] ]. I just must have an array, not a string.
When I display alert(readings[1].listvalue), I have["2630","61297","61296","61299"] which is clearly an array because I can access each of the elements by calling alert(readings[1].listvalue[0]), alert(readings[1].listvalue[1]], etc...
I tried what you advised me
But, it is still not working. Here, my repository code:
#Override
public List<ClarityResourceAffectationReport> aggregateAllTaskCode() {
AggregationOperation project = new AggregationOperation() {
#Override
public DBObject toDBObject(AggregationOperationContext aggregationOperationContext) {
return new BasicDBObject("$project", new BasicDBObject("budgetCode", "$budget_code").append("taskCode", Arrays.asList("$task_code")));
}
};
Aggregation aggregation = newAggregation(project,
group("budgetCode").addToSet("budgetCode").as("budgetCode").addToSet("taskCode").as("taskCode"),
sort(Sort.Direction.ASC, previousOperation(),"budgetCode"));
AggregationResults groupResults = mongoTemplate.aggregate(aggregation, ClarityResourceAffectation.class,
ClarityResourceAffectationReport.class);
List<ClarityResourceAffectationReport> clarityResourceAffectationReports = groupResults.getMappedResults();
log.debug("clarityResourceAffectationReports.size() => " + clarityResourceAffectationReports.size());
log.debug("aggregation.toString() => " + aggregation.toString());
return clarityResourceAffectationReports;
}
Here, you can find the logs:
clarityResourceAffectationReports.size() => 1
aggregation.toString() => {"aggregate" : "__collection__" , "pipeline" : [ { "$project" : { "budgetCode" : "$budget_code" , "taskCode" : [ "$task_code"]}} , { "$group" : { "_id" : "$budgetCode" , "budgetCode" : { "$addToSet" : "$budgetCode"} , "taskCode" : { "$addToSet" : "$taskCode"}}} , { "$sort" : { "_id" : 1 , "budgetCode" : 1}}]}
Thanks in advance
You need to use $project to change the taskCodes value into array of single value before $group.
I don't see any hook in the api to address this.
You can use AggregationOperation to create $project stage using mongodb (BasicDBObject) types.
AggregationOperation project = new AggregationOperation() {
#Override
public DBObject toDBObject(AggregationOperationContext aggregationOperationContext) {
return new BasicDBObject("$project", new BasicDBObject("budgetCode", 1).append("taskCode", Arrays.asList("$taskCode")));
}
};
Something like
Aggregation aggregation = newAggregation(project,
group("budgetCode").addToSet("budgetCode").as("budgetCode").addToSet("taskCode").as("taskCode"),
sort(Sort.Direction.ASC, previousOperation(), "budgetCode"));
Using lambda
Aggregation aggregation = newAggregation(
aggregationOperationContext -> new BasicDBObject("$project", new BasicDBObject("budgetCode", 1).append("taskCode", Arrays.asList("$taskCode"))),
group("budgetCode").addToSet("budgetCode").as("budgetCode").addToSet("taskCode").as("taskCode"),
sort(Sort.Direction.ASC, previousOperation(), "budgetCode"));
I am using an aggregate query in mongodb to find the sum of an attribute in all the documents present in a collection.
Query:
db.conversation.aggregate( [
{
$match:{
$and:[{"mailBoxId":"1","isHidden":false}]
}
},
{
$group:
{
_id: {"mailBoxId":"$mailBoxId","isHidden":"$isHidden"} ,
messageCount: { $sum:"$messageCount" }
}
}
]);
The result returned by Mongodb is fine and is in this format.
{
"result" : [
{
"_id" : {
"mailBoxId" : "2",
"isHidden" : false
},
"messageCount" : 2
}
],
"ok" : 1
}
I just want the messageCount field. I am using MongoTemplate(Spring) class to query the database.
Query retrievalQuery = new Query();
retrievalQuery.addCriteria(Criteria.where("mailBoxId").is(userId).and("isHidden").is(false));
return mongoTemplate.find(retrievalQuery, );
I am confused how to store the resultant object returned by Mongodb and extract a specific field from it.
Pls help.
The way you are trying to use aggregate in mongoTemplate is wrong . Try this i am sure it will help.
Aggregation agg = Aggregation.newAggregation(
Aggregation.match(
Criteria.where("mailBoxId").is("1").and("isHidden").is(false)),
Aggregation.group("$mailBoxId").sum("$unReadMessagesCount").as("unReadMessagesCount")
);
System.out.println("Query ==>>["+agg.toString()+"]");
AggregationResults<AggResultObj> data = mongoTemplate.aggregate(agg, "collectionName", AggResultObj.class);
System.out.println("UnReadMesasgeCode :"+data.getUniqueMappedResult().getUnReadMessagesCount());
The AggResultObj will be looks like
public class AggResultObj{
String _id;
int unReadMessagesCount;
public String get_id() {
return _id;
}
public void set_id(String _id) {
this._id = _id;
}
public int getUnReadMessagesCount() {
return unReadMessagesCount;
}
public void setUnReadMessagesCount(int unReadMessagesCount) {
this.unReadMessagesCount = unReadMessagesCount;
}
}
For more information you can see my blog where i have created a example for the same for your scenario. please click https://satishkumardangi.blogspot.in/2016/09/using-mongo-db-aggregation-with-spring.html
Try this
Query retrievalQuery = new Query();
retrievalQuery.addCriteria(Criteria.where("mailBoxId").is(userId).and("isHidden").is(false));
var result = mongoTemplate.find(retrievalQuery);
var final = result[1].messageCount;
return final;
I'm trying to use the upsert feature of mongodb v3.2 using java,
so every solution not including a java response would not be accepted.
My problem is that the upsert command override nested objects instead of adding new ones, I have tried to use '$addToSet' and 'push', but without success and I get an error message indicating that the storage engine does not support this command.
I want to update the client's document as well as their inner objects such as checks and checks's values.
the global structure of the client doc is as below.
Client
|
|__Checks // array of checks , update or insert operation
|
|__values // array of values, every check has its own values (20 max)
// update using index(id)
link of the: Example's source code
My intention is to use only one query to update client's document without using many queries.
I'm not specialist in mongodb, so every advice or critics would be appreciated.
Even if I'm doing this all wrong, feel free to notify me, and please using java for mongo 3.2.
Here is the source code used to generate the last result.
package org.egale.core;
import com.mongodb.MongoClient;
import com.mongodb.client.MongoCollection;
import com.mongodb.client.MongoDatabase;
import com.mongodb.client.model.UpdateOptions;
import java.util.ArrayList;
import java.util.List;
import org.bson.Document;
/**
*
* #author Zied
*/
public class MongoTest {
/**
* Pojo used to populate data
*/
static class CheckModel {
public String client;
public String checkId;
public String name;
public String command;
public String description;
public String topic;
public int refresh = 60;
public int status;
public String output;
}
static MongoClient mongoClient = new MongoClient();
static String dbName = "eagle";
private static List<Document> getCheckValues(CheckModel checkModel, int index) {
final List<Document> checkValues = new ArrayList<>();
final Document val = new Document()
.append("id", index)
.append("output", checkModel.output)
.append("status", checkModel.status);
checkValues.add(val); // second execution should not ovveride the content of value but a new
return checkValues;
}
private static void insertCheck(MongoDatabase db, CheckModel checkModel) {
int idx =++index % 20;
final List<Document> checks = new ArrayList<>();
final Document check = new Document()
.append("name", checkModel.name)
.append("command", checkModel.command)
.append("id", checkModel.checkId)
.append("description", checkModel.description)
.append("topic", checkModel.topic)
.append("last_output", checkModel.output)
.append("index", index)
.append("last_status", checkModel.status)
.append("values", getCheckValues(checkModel,idx))
.append("refresh", checkModel.refresh);
checks.add(check);
Document client = new Document()
.append("name", checkModel.client)
.append("checks", checks);
//.append("$addToSet" , new Document("checks", checks)); // <<- error here '$addToSet' is not recocnized
db.getCollection("clients") // execute client insert or update
.updateOne(
new Document().append("_id", checkModel.client), new Document("$set", client), new UpdateOptions().upsert(true)
);
}
static int index = 0;
// Name of the topic from which we will receive messages from = " testt"
public static void main(String[] args) {
MongoDatabase db = mongoClient.getDatabase(dbName);
CheckModel checkModel = new CheckModel();
checkModel.command = "ls -lA";
checkModel.client = "client_001";
checkModel.description = "ls -l command";
checkModel.checkId = "lsl_command";
checkModel.name = "client 001";
checkModel.output = "result of ls -l";
checkModel.status = 0;
checkModel.topic = "basic_checks";
checkModel.refresh = 5000;
initDB(db);
// insert the first check
insertCheck(db, checkModel);
// insert the second check after some modification
// insertCheck(db, modifyData(checkModel));
}
// mdofiy data to test the check
private static CheckModel modifyData(CheckModel checkModel){
checkModel.status = 1;
checkModel.output = "ls commadn not found";
return checkModel;
}
private static void initDB(MongoDatabase db) {
MongoCollection<Document> collection = db.getCollection("configuration");
if (collection.count() == 0) {
Document b = new Document()
.append("_id", "app_config")
.append("historical_data", 20)
.append("current_index", 0);
collection.insertOne(b);
}
Document b = new Document().append("none", "none");
MongoCollection<Document> clients = db.getCollection("clients");
clients.insertOne(b);
clients.deleteOne(b);
MongoCollection<Document> topics = db.getCollection("topics");
topics.insertOne(b);
topics.deleteOne(b);
}
}
You may use $push, $each, $slice to solve your problem, see alse https://docs.mongodb.org/manual/reference/operator/update/slice/.
db.students has following documents
{ "_id" : 10, "scores" : [ 1, 2, 3 ] }
db.students.update(
{ _id: 10 },
{
$push: {
scores: {
$each: [ 4 ],
$slice: -3
}
}
}
)
result is:
{ "_id" : 10, "scores" : [ 2, 3, 4] }
I am using Spring Data with a Mongo DB embedded database and have the following document structure:
{
id : 111
messaage : abcd
commentsList: [
{
id : 123
text: test text
numberOfLikes : 5
numberOfDislikes: 2
}
]
}
I am trying to get a comment by id and update the numberOfLikes field by one and can't seem to get it to work, here is what I've tried and the java class structure:
public class Post {
private String id;
private String message;
private List<Comment> commentsList = new ArrayList<>();
...
}
public class Comment {
private String id;
private String text;
private int numberOfLikes;
private int numberOfDislikes;
...
}
Query query = new Query();
query.addCriteria(Criteria.where("commentsList._id").is("123"));
List<MongoComment> commentList = mongoTemplate.find(query, MongoComment.class);
Currently the returned list is always null.
Since you are trying to get a comment by id and update the numberOfLikes field by one, you essentially want to replicate this mongo shell update operation:
db.post.update(
{ "commentsList.id": "123" },
{
"$inc": { "commentsList.$.numberOfLikes": 1 }
}
)
The equivalent Spring Data MongoDB code follows:
import static org.springframework.data.mongodb.core.query.Criteria.where;
import static org.springframework.data.mongodb.core.query.Query;
import static org.springframework.data.mongodb.core.query.Update;
...
WriteResult wr = mongoTemplate.updateMulti(
new Query(where("commentsList.id").is("123")),
new Update().inc("commentsList.$.numberOfLikes", 1),
MongoPost.class
);
The id of the elements in the array "commentsList" is "id" (without the '_').
query.addCriteria(Criteria.where("commentsList.id").is("123"))
That should work.
The "_id" you're trying to query is the identifier Mongodb automatically generates for each document. Your document in the database looks like this:
{
_id: ObjectId("56b46e1d1d1353e38886dcc34f"),
id : 111,
messaage : "abcd",
commentsList: [
{
id : 123,
text: "test text",
numberOfLikes : 5,
numberOfDislikes: 2
}
]
}