MongoDB handling failure of insert in Java - java

I would like to handle failure of insert to collection (using Java) in order to be sure that my insert was successful. In case that insert fails, I want to perform some fall-back action.
Assuming following code in Java and latest mongo driver (version 2.11.3):
BasicDBObject doc = new BasicDBObject("name", "MongoDB");
WriteResult result = coll.insert(WriteConcern.SAFE, doc);
I am confused by the fact that the insert returns WriteResult and it could throw MongoException. What am I supposed to do in order to safely detect any possible failure of insert? Please, provide code example. And if you can clarify when insert throws exception and when it just returns some error write result. I tried to search in java driver API docs at http://api.mongodb.org/java/2.11.3/ for it; howerever, this infromation is missing there.
WriteResult result;
try {
result = coll.insert(WriteConcern.SAFE, doc);
}
catch(MongoException ex){
logger.warn("Insert failed.", ex);
throw ex;
}
//Shall I check result here for additional errors?
If I should check, what type of error I am able to detect by checking insert result?

You need to take a look at "WriteConcern", it has the all behaviors you need.
You can use it per one write like this:
coll.insert(dbObj, WriteConcern.SAFE);
If you use WriteConcern.SAFE your operation will wait for an acknowledgement from the primary server, so if no exception is raised then you're ok.
Or you can set default behaviour for all write operations when you are creating MongoClient:
MongoClientOptions.Builder builder = new MongoClientOptions.Builder();
builder.writeConcern(WriteConcern.JOURNAL_SAFE);
MongoClient mongoClient = new MongoClient(
new ServerAddress("localhost"), builder.build());
[Based on Colin Morelli's comment] If you don't use a WriteConcern that raises exceptions, you can use the WriteResult.getLastError() to determine if it was successful or not. Similarly, if you use WriteConcern.SAFE, and the write succeeds, WriteResult will have useful information on it such as the number of records that were written.
Here you can read about WriteConcern in general.

Related

How should you handle MongoDB-Exceptions in Java?

I want to handle exceptions, which are thrown from a query (find(...).first()) to MongoDB (Driver 3.7) in Java (the database is not stored locally). However there are no possible exceptions named in the JavaDocs and also in the MongoDB documentaton itself. Can there really occur no exceptions? I doubt that, because I think there could occur e.g. some network errors.
My queries look something like this:
final MongoCollection<Document> collection = database.getCollection("my-collection");
final Bson bsonFilter = Filters.eq("someName", "test");
final Document result = collection.find(bsonFilter).first();
Consider the following code. It connects to a MongoDB instance locally and gets a collection named "test" from the database named "users".
final String connectionStr = "mongodb://localhost/";
MongoClient mongoClient = MongoClients.create("mongodb://localhost/");
MongoDatabase database = mongoClient.getDatabase("users");
MongoCollection<Document> collection = database.getCollection("test");
If you provide a wrong host name for the connectionStr value, like "mongodb://localhostXYZ/" (and no such host exists) the code will throw an exception, like:
com.mongodb.MongoSocketException: localhostXYZ},
caused by {java.net.UnknownHostException: localhostXYZ}}],
..., ...
com.mongodb.MongoSocketException is a MongoDB Java driver exception. It is a runtime exception. It is also a sub-class of MongoException. From the MongoDB Java API:
public class MongoException extends RuntimeException
Top level Exception for all Exceptions, server-side or client-side, that come
from the driver.
The documentation also lists the following are sub-classes (all are runtime exceptions)
MongoChangeStreamException, MongoClientException, MongoExecutionTimeoutException, MongoGridFSException, MongoIncompatibleDriverException, MongoInternalException, MongoInterruptedException, MongoServerException, MongoSocketException.
So, all the exceptions thrown by MongoDB Java driver APIs are runtime exceptions. These are, in general, not meant to be caught and handled (but, you know how to use try-catch, and a runtime exception can be caught and handled).
Let us consider your code:
final MongoCollection<Document> collection = database.getCollection("my-collection");
final Bson bsonFilter = Filters.eq("someName", "test");
final Document result = collection.find(bsonFilter).first();
The first statement database.getCollection("my-collection"), when it runs the code is looking for a collection named "my-collection".
If you want to make sure the collection exists in the database, then verify using the listCollectionNames​() and check the collection name exists in the returned list. In case the collection name doesn't exist, you can throw an exception (if you want to). This exception is what you have figure:
if you want to tell the user or the application that there was no
such collection named "my-collection", you can show or print a
message saying so (and then abort the program) or throw a runtime
exception with an appropriate message.
So, the code might look like this:
if listCollectionNames​() doesn't contain "my-collection"
then
print something and abort the program
-or-
throw a runtime exception
else
continue with program execution
Your code final Document result = collection.find(bsonFilter).first(); is not correct. collection.find returns a FindIterable<TDocument> not a Document. So, the query output can be determined by further examining the FindIterable object; it may have documents or none. And, the find method doesn't throw any exceptions.
Based on if there are any documents returned or not you can show a message to the client. This is not a case you throw an exception.

How to get error message from the scala/java MongoDB api

I'm using Casbah (mongodb scala library). I have an insert that doesn't work.
val builder = MongoDBObject.newBuilder
builder += "_id" -> token.uuid
builder += "email" -> token.email
builder += "creationTime" -> token.creationTime
builder += "expirationTime" -> token.expirationTime
builder += "isSignUp" -> token.isSignUp
val writeResult = mycollection += (builder.result)
If I change this for something simpler (like, a simple {"hello": "world"} document), the insert is done. So I know there's something that doesn't work with this particular insert. However, I find no way to know why. I'd like to get some feedback from Mongo or from Casbah.
However the WriteResult class, which apparently comes directly from the Java MongoDB driver, seems very opaque: http://api.mongodb.com/java/3.0/com/mongodb/WriteResult.html
How can I get some info about why an insert is failing? I'm not asking about this particular insert. Just, in general, how can I get info about the error that caused an insert to fail?
Thanks for your help.
Casbah is a Scala wrapper over the Java MongoDB driver.
mycollection += (builder.result)
is translated into
mycollection.save(builder.result)
If the operation had an error it will throw an exception like described here.
The WriteResult containing information about the write if no error happened.
I would check:
getN and isUpdateOfExisting values in WriteResult because save is doing either update or insert (read more here).
wasAcknowledged value in WriteResult to make sure you get the exception and you don't have the WriteConcern set to UNACKNOWLEDGED.

Using MongoDB 3.4 to load and save userdata

How can I find a document and retrieve it if found, but insert and retrieve it if not found in one command?
I have an outline for the formats I wish my documents to look like for a user's data. Here is what it looks like
{
"username": "HeyAwesomePeople",
"uuid": "0f91ede5-54ed-495c-aa8c-d87bf405d2bb",
"global": {},
"servers": {}
}
When a user first logs in, I want to store the first two values of data (username and uuid) and create those empty values (global and servers. Both those global and servers will later on have more information filled into them, but for now they can be blank). But I also don't want to override any data if it already exists for the user.
I would normally use the insertOne or updateOne calls to the collection and then use the upsert (new UpdateOptions().upsert(true)) option to insert if it isn't found but in this case I also need to retrieve the user's document aswell.
So in a case in which the user isn't found in the database, I need to insert the outlined data into the database and return the document saved. In a case where the user is found in the database, I need to just return the document from the database.
How would I go about doing this? I am using the latest version of Mongo which has deprecated the old BasicDBObject types, so I can't find many places online that use the new 'Document' type. Also, I am using the Async driver for java and would like to keep the calls to the minimum.
How can I find a document and retrieve it if found, but insert and retrieve it if not found in one command?
You can use findOneAndUpdate() method to find and update/upsert.
The MongoDB Java driver exposes the same method name findOneAndUpdate(). For example:
// Example callback method for Async
SingleResultCallback<Document> printDocument = new SingleResultCallback<Document>() {
#Override
public void onResult(final Document document, final Throwable t) {
System.out.println(document.toJson());
}
};
Document userdata = new Document("username","HeyAwesomePeople")
.append("uuid", "0f91ede5")
.append("global", new Document())
.append("servers", new Document());
collection.findOneAndUpdate(userdata,
new Document("$set", userdata),
new FindOneAndUpdateOptions()
.upsert(true)
.returnDocument(ReturnDocument.AFTER),
printDocument);
The query above will try to find a document matching userdata; if found set it to the same value as userdata. If not found, the upsert boolean flag will insert it into the collection. The returnDocument option is to return the document after the action is performed.
The upsert and returnDocument flags are part of FindOneAndUpdateOptions
See also MongoDB Async Java Driver v3.4 for tutorials/examples. The above snippet was tested with current version of MongoDB v3.4.x.

DynamoDB: ConditionalCheckFailedException

I am dealing with a ConditionalCheckFailedException and I am not exactly sure which condition is failing the check. When I open up debugger and examine the exception variable, I can't find any useful info.
Below is my Java Dynamo Client code. I am trying to make a conditional write to DynamoDB using DynamoDBSaveExpression when:
The client date in the table comes before the current client date that I am trying to write (stored as EPOCH time)
An entry does not exist in the table (I check for the FEEDBACK_KEY as it is the primary key in my table)
When I write the first entry into the table, it works, but on updates when an entry exists, I get the ConditionalCheckFailedException exception. Any ideas?
final DynamoDBSaveExpression expression = new DynamoDBSaveExpression();
final Map<String, ExpectedAttributeValue> expectedAttributes =
ImmutableMap.<String, ExpectedAttributeValue>builder()
.put(ThemesMessageEligibility.TableKeys.CLIENT_DATE,
new ExpectedAttributeValue()
.withComparisonOperator(ComparisonOperator.LT)
.withValue(new AttributeValue().withN(clientDate)))
.put(ThemesMessageEligibility.TableKeys.FEEDBACK_KEY,
new ExpectedAttributeValue(false))
.build();
expression.setExpected(expectedAttributes);
expression.setConditionalOperator(ConditionalOperator.OR);
// Conditional write if the clientDate is after the dynamo's client Date
try {
dynamoMapper.save(themesFeedbackComponentContainer, expression);
} catch (ConditionalCheckFailedException ex) {
...
}
I would remove the second condition, or change it so that it conditions on the item existing (new ExpectedAttributeValue(true)). UpdateItem will just overwrite the existing item even if it exists, so it seems like the CLIENT_DATE condition is the only one you need.
The API call as written above will only succeed on the first write (that is, when the item does not exist). In retrospect, if you only want the first write to an item to succeed (and fail if the item already exists) the CLIENT_DATE condition is not necessary (as there are no attributes in the existing image to compare to).

find all items where a list field contains a value in dynamodb

I'm new to DynamoDb and I'm struggling to work out how to do this (using the java sdk).
I currently have a table (in mongo) for notifications. The schema is basically as follows (I've simplified it)
id: string
notifiedUsers: [123, 345, 456, 567]
message: "this is a message"
created: 12345678000 (epoch millis)
I wanted to migrate to Dynamodb, but I can't work out the best way to select all notifications that went to a particular user after a certain date?
I gather I can't have an index on a list like notifiedUsers, therefore I can't use a query in this case - is that correct?
I'd prefer not to scan and then filter, there could be a lot of records.
Is there a way to do this using a query or another approach?
EDIT
This is what I'm trying now, it's not working and I'm not sure where to take it (if anywhere).
Condition rangeKeyCondition = new Condition()
.withComparisonOperator(ComparisonOperator.CONTAINS.toString())
.withAttributeValueList(new AttributeValue().withS(userId));
if(startTimestamp != null) {
rangeKeyCondition = rangeKeyCondition.withComparisonOperator(ComparisonOperator.GT.toString())
.withAttributeValueList(new AttributeValue().withS(startTimestamp));
}
NotificationFeedDynamoRecord replyKey = new NotificationFeedDynamoRecord();
replyKey.setId(partitionKey);
DynamoDBQueryExpression<NotificationFeedDynamoRecord> queryExpression = new DynamoDBQueryExpression<NotificationFeedDynamoRecord>()
.withHashKeyValues(replyKey)
.withRangeKeyCondition(NOTIFICATIONS, rangeKeyCondition);
In case anyone else comes across this question, in the end we flattened the schema, so that there is now a record per userId. This has lead to problems because it's not possible with dynamoDb to atomically batch write records. With the original schema we had one record, and could write it atomically ensuring that all users got that notification. Now we cannot be certain, and this is causing pain.

Categories