Setting last modification timestamp when using insertOne and findOneAndUpdate - java

I need all my inserted/updated documents in MongoDb to have an auto-updated currentDate
So let's assume I have the following Json shipment object (which I'm getting from a 3rd party restful API) :
String jsonString = {"tracking_number": "123", "deliveryAddress": { "street_line_1": "12 8th St.", "city": "NY", "state": "NY" }, "cutomers": [ { "firstName": "John", "email": "john#gmail.com" }, { "firstName": "Alex", "email": "alex#gmail.com" } ] }
Problem #1, I need to insert the object into the DB and set "currentDate", but insertOne does not work for me:
MongoClient mongo = new MongoClient(mongodb_host, mongodb_port);
MongoDatabase db = mongo.getDatabase("Test");
MongoCollection<Document> collection = db.getCollection("InsertOneExample");
Document doc = Document.parse(jsonString);
doc.append("lastModifiedTs", new BSONTimestamp());
collection.insertOne(doc);
System.out.println(doc);
This one does not populate "lastModifiedTs" as you can see below
Document{{tracking_number=123, deliveryAddress=Document{{street_line_1=12 8th St., city=NY, state=NY}}, cutomers=[Document{{firstName=John, email=john#gmail.com}}, Document{{firstName=Alex, email=alex#gmail.com}}], lastModifiedTs=TS time:null inc:0, _id=5a6b88a66cafd010f1f2cffd}}
Problem #2
If I'm getting an update on my shipment, the tracking number is the same, but all the other fields may change.
The following code crashes:
FindOneAndUpdateOptions options = new FindOneAndUpdateOptions();
options.returnDocument(ReturnDocument.AFTER);
options.upsert(true);
Bson update = Updates.combine(Document.parse(jsonString), Updates.currentTimestamp("lastModifiedTs"));
Document query = new Document("tracking_number", "123");
Document result = collection.findOneAndUpdate(query, update, options);
With the exception: "Invalid BSON field name equipmentShipmentAddress"
So it looks like I cannot just to put the entire updated document into the "update"
If I set update just to Updates.currentTimestamp("lastModifiedTs"), the code will update just the field "lastModifiedTs", but I need it to modify all the fields.
If I set the query to be the new object, then due to my "upsert" setting, it'll add the new document without replacing the old one.
Notes: needless to say, I can perform several operations: (1) insert the object, get the "_id" field, (2) update the "lastModifiedTs" field (3) read the object by "_id" and get the updated "lastModifiedTs" value, but it's three operation where I expect to be able to achieve everything with a single operation
How can I achieve my goal elegantly?
Thanks

Solution to Problem #1 - Insert new Date() to provide new datetime.
Document doc = Document.parse(jsonString);
doc.append("lastModifiedTs", new Date());
collection.insertOne(doc);
Solution to Problem #2 - Use findOneAndReplace
FindOneAndReplaceOptions options = new FindOneAndReplaceOptions();
options.returnDocument(ReturnDocument.AFTER);
Document replace = Document.parse(jsonString);
replace.append("lastModifiedTs", new Date());
Document query = new Document("tracking_number", "123");
Document result = collection.findOneAndReplace(query, replace, options);

Related

Update aggregate java - mongodb - $(update)

db.getCollection('test').update({
},
[{$set:
{
books: {$concatArrays:[
[],
[{
"status": "available",
"shelf_id": "$shelf_id",
"rack_no": "$rack_no",
"book_id": new ObjectId()
}]
]}
}
}])
I am trying to convert the above query in Java. I am getting BatchCombiner exception.
Document findDoc = new Document();
LinkedList<UpdateManyModel<Document>> docs = new LinkedList<UpdateManyModel<Document>>();
String docId = new ObjectId().toString();
Document objectIDDoc = new Document("book_id", docId).append("status", "active")
.append("rack_no", "$rack_no");
ArrayList<ArrayList<Document>> documents = new ArrayList<ArrayList<Document>>();
ArrayList<Document> instanceList = new ArrayList<Document>();
instanceList.add(objectIDDoc);
documents.add(new ArrayList<Document>());
documents.add(instanceList);
Document instancesFinalDoc = new Document("instances", new Document("$concatArrays", documents));
Document docToBeUpdated = new Document("$set", instancesFinalDoc);
I am not sure how to convert the list to Bson. Could anyone please help?
I am getting the below error:
com.mongodb.MongoBulkWriteException: Bulk write operation error on server vmwahcdb09-stg.corp.netapp.com:27016. Write errors:
[BulkWriteError{index=0, code=52, message='The dollar ($) prefixed field '$concatArrays' in 'instances.$concatArrays' is not valid for storage.', details={}}].
You are almost there.
The error is reproducible if you try to update using older driver version. You need to do as below.
Create a ClientSession
ClientSession session = mongoClient.startSession()
Then, whatever you have is perfectly correct. But You need to use updateMany latest one which supports aggregate
writeCollection.updateMany(session, findBson, updateList, updateOptions);
For your case, you need to add docToBeUpdated to a List<Document> and use as updateList.

How to custom search for text query in mongodb?

I'm new in mongodb. I have following data as a JSON format in mongodb. I need to search the bookLabel or the shortLabel for the book and it should show me all the information about the book. For example: if I query for 'Cosmos' it'll show all the description about the book, like: bookLabel, writer, yearPublish, url. How can I do that in java? Need query, please help.
"Class":"Science",
"Description":[
{
"bookLabel":"Cosmos (Mass Market Paperback)",
"shortLabel":"Cosmos",
"writer":"Carl Sagan",
"yearPublish":[
"2002"
],
"url":"https://www.goodreads.com/book/show/55030.Cosmos"
},
{
"bookLabel":"The Immortal Life of Henrietta Lacks",
"shortLabel":"Immortal Life",
"writer":"Rebecca Skloot",
"yearPublish":[
"2010, 2011"
],
"url":"https://www.goodreads.com/book/show/6493208-the-immortal-life-of-henrietta-lacks"
}
],
"Class":"History",
"Description":[
{
"bookLabel":"The Rise and Fall of the Third Reich",
"shortLabel":"Rise and Fall",
"writer":"William L. Shirer",
"yearPublish":[
"1960"
],
"url":"https://www"
}
]
}
With MongoDB Java Driver v3.2.2 you can do something like this:
FindIterable<Document> iterable = collection.find(Document.parse("{\"Description.shortLabel\": {$regex: \"Cosmos\"}"));
This returns all documents containing Cosmos in the Description.shortLabel nested field. For an exact match, try this {"Description.shortLabel": "Cosmos"}. Replace shortLabel with bookLabelto search the bookLabel field. Then you can do iterable.forEach(new Block<Document>()) on the returned documents. To search both bookLabel and shortLabel, you can do a $or{}. My syntax could be wrong so check the MongoDB manual. But this is the general idea.
For this, you can use MongoDB's Text Search Capabilities. You'll have to create a text index on your collection for that.
First of all create a text index on your collection on fields bookLabel and shortLabel.
db.books.createIndex({ "Description.bookLabel" : "text", "Description.shortLabel" : "text" })
Note that this is done in the Mongo shell
Then
DBObject command = BasicDBObjectBuilder
.start("text", "books")
.append("search", "Cosmos")
.get();
CommandResult result = db.command(command);
BasicDBList results = (BasicDBList) result.get("results");
for(Object o : results) {
DBObject dbo = (DBObject) ((DBObject) o).get("obj");
String id = (String) dbo.get("_ID");
System.out.println(id);
}
Haven't really tested this. But just give it a try. Should work.

MongoDB - How to find and append / find and upsert both document and fields

In Mongo, is there a built-in way to update a document and instead of replacing all contents of the query document, to update those nodes which are the same and append those which do not exist in the original document.
For example, imagine I insert the following document into my collection:
{
"name" : "Goku",
"level" : 9000
}
Now, at some later point, I wish to update my existing document with the following document I received:
{
"name" : "Goku",
"son" : "Gohan"
}
Ideally, I would like a way to perform an update and produce the following document:
{
"name" : "Goku",
"level" : 9000,
"son" : "Gohan"
}
The standard case is to overwrite the existing document with the new document (as it should be). However, is there a built-in or clever way to achieve the result above without first finding the first document, appending onto it, and then performing an update?
Thanks.
-- EDIT --
#pennstatephil has the correct answer below. Just in case anyone's is helped by this, here's an implementation of this example in Java as of driver version 2.12.0:
String json = "{'name' : 'Goku', 'level' : 9000 }";
DBObject document = (DBObject) JSON.parse(json);
BasicDBObject update = new BasicDBObject("$set", document);
BasicDBObject query = new BasicDBObject().append("name", document.get("name"));
collection.findAndModify(query, null, null, false, update, false, true);
json = "{'name' : 'Goku', 'son' : 'Gohan'}";
document = (DBObject) JSON.parse(json);
update = new BasicDBObject("$set", document);
query = new BasicDBObject().append("name", document.get("name"));
collection.findAndModify(query, null, null, false, update, false, true);
I believe findAndModify and $set (on the update clause) is what you're looking for.

Elastic search range dates

I have created an Elastic search index from a Mongo database.
The documents in Mongo have the following structure:
{
"_id" : ObjectId("525facace4b0c1f5e78753ea"),
"time" : ISODate("2013-10-17T09:23:56.131Z"),
"type" : "A",
"url" : "www.google.com",
"name" : "peter",
}
The index was created (apparently) without any problems.
Now, I am trying to use Elastic Search to retrieve the documents in the index between two dates. I have read that I have to use range queries, but I have tried many times things like
MatchQueryBuilder queryBuilder = QueryBuilders.matchQuery("name", "peter").type(Type.PHRASE).minimumShouldMatch("99%");
LocalDateTime toLocal = new LocalDateTime(2013,12,18, 0, 0);
Date to = toLocal.toDate();
LocalDateTime fromLocal = new LocalDateTime(2013,12,17, 0, 0);
Date from = fromLocal.toDate();
RangeQueryBuilder queryDate = QueryBuilders.rangeQuery("time").to(to).from(from);
FilterBuilder filterDate = FilterBuilders.queryFilter(queryDate);
srb = esH.client.prepareSearch("my_index");
srb.setQuery(queryBuilder);
srb.setFilter(filterDate);
sr = srb.execute().actionGet();
and I get 0 hits although there should be many results. I have tried to enter strings instead of dates, but same results.
When I perform a basic query without filters such as:
MatchQueryBuilder queryBuilder = QueryBuilders.matchQuery("name", "peter").type(Type.PHRASE).minimumShouldMatch("99%");
SearchRequestBuilder srb = esH.client.prepareSearch("my_index");
rb.setQuery(queryBuilder);
SearchResponse sr = srb.execute().actionGet();
I get hits with that look like this:
{
"_index" : "my_index",
"_type" : "type",
"_id" : "5280d3c2e4b05e95aa703e34",
"_score" : 1.375688, "_source" : {"type":["A"],"time":["Mon Nov 11 13:55:30 CET 2013"],"name":["peter"]}
}
Where the field time does not have the format ISODate("2013-10-17T09:23:56.131Z")anymore.
To sum up, what would be the Java code (and types) for querying between two dates (and times), taking into account the format?
You are probably passing the wrong field name to the range query at this line:
RangeQueryBuilder queryDate = QueryBuilders.rangeQuery("time").to(to).from(from);
It should probably be #timestamp (or the field you're using to store your timestamp) instead of time. Additionally, it seems that there is no time field in Elasticsearch for the example document you included. This also points to the issue that the time field wasn't converted correctly from Mongo to Elasticsearch.
Can you try
FilterBuilders.rangeFilter("#timestamp").from("from time").to("toTime")
This will work -
You can pass in Long timestamps to the gte and lte params.
QueryBuilders.rangeQuery("time").gte(startTime).lte(endTime);
Make sure to add an "L" at the end of the startTime and endTime, so that it knows its a long and not an int.

Remove and Add new array into the JSON object using MongoDB

I have JSON object :
{ "_id" : "1", "_class" : "com.model.Test", "projectList" : [ {
"projectID" : "Spring", "resourceIDList" : [ "Mark","David" ] },
{ "projectID" : "MongoDB", "resourceIDList" : [ "Nosa ] }
I need to be able to remove the resourceIDList for Project "Spring" and assign a new ResourceIDList.
The ResourceIDList is just List
Whenever I try to use the following,nothing is updated at DB:
Query query = new Query(where("_id").is("1").and("projectID").is("Spring"));
mongoOperations.updateMulti( query,new Update().set("ressourceIDList", populateResources()), Test.class);
Replacing the resourceIDList in the embedded document matching {"projectList.projectID":"Spring"} may be accomplished in the JavaScript shell like so:
(I like to start with the JS shell, because it is less verbose than Java and the syntax is relatively straightforward. Examples with JS can then be applied to any of the language drivers.)
> db.collection.update({_id:"1", "projectList.projectID":"Spring"}, {$set:{"projectList.$.resourceIDList":["Something", "new"]}})
The documentation on using the "$" operator to modify embedded documents may be found in the "The $ positional operator" section of the "Updating" documentation:
http://www.mongodb.org/display/DOCS/Updating#Updating-The%24positionaloperator
There is more information on embedded documents in the "Dot Notation (Reaching into Objects)" Documentation:
http://www.mongodb.org/display/DOCS/Dot+Notation+%28Reaching+into+Objects%29
The above may be done in the Java Driver like so:
Mongo m = new Mongo("localhost", 27017);
DB db = m.getDB("test");
DBCollection myColl = db.getCollection("collection");
ArrayList<String> newResourceIDList = new ArrayList<String>();
newResourceIDList.add("Something");
newResourceIDList.add("new");
BasicDBObject myQuery = new BasicDBObject("_id", "1");
myQuery.put("projectList.projectID", "Spring");
BasicDBObject myUpdate = new BasicDBObject("$set", new BasicDBObject("projectList.$.resourceIDList", newResourceIDList));
myColl.update(myQuery, myUpdate);
System.out.println(myColl.findOne().toString());
If you have multiple documents that match {"projectList.projectID" : "Spring"} you can update them at once using the multi = true option. With the Java driver it would look like this:
myColl.update(myQuery, myUpdate, false, true);
In the above, "false" represents "upsert = false", and "true" represents "multi = true". This is explained in the documentation on the "Update" command:
http://www.mongodb.org/display/DOCS/Updating#Updating-update%28%29
Unfortunately, I am not familiar with the Spring framework, so I am unable to tell you how to do this with the "mongoOperations" class. Hopefully, the above will improve your understanding on how embedded documents may be updated in Mongo, and you will be able to accomplish what you need to do with Spring.

Categories

Resources