'How to conditionally include CreationDate when making a bulk upsert ($setOnInsert with $set creates error)
I am a beginner with mongoDB and I am trying to either update or create a very large list of products. Products that already exist in the database will have a "creationDate" that I wish to not overwrite. When using the following code:
public AddProductResponse add(final String trackingId, final String catalogName, final List<Product> products) {
final AddProductResponse res = new AddProductResponse();
res.products(products);
final List<WriteModel<Product>> writes = new ArrayList<>();
for (final Product product : products) {
final OffsetDateTime creationDate = OffsetDateTime.now();
product.setLastUpdate(OffsetDateTime.now());
final Bson filter = Filters.eq(Constants.PRODUCT_ID_PROPERTY, product.getId());
final Bson update = new Document("$set", product).append("$setOnInsert", new Document(Constants.PRODUCT_CREATION_DATE_PROPERTY, creationDate));
final UpdateOptions options = new UpdateOptions().upsert(true);
writes.add(new UpdateOneModel<>(filter, update, options));
}
try {
final BulkWriteResult bulkWriteResult = getCollection(trackingId, catalogName, COLLECTION_TYPE, Product.class).bulkWrite(writes, new BulkWriteOptions().ordered(false));
LOG.info("Bulk write result: {}", bulkWriteResult);
if (!bulkWriteResult.wasAcknowledged()) {
res.setSuccess(false);
res.reason(Constants.UNKNOWN_ERROR_MESSAGE);
} else {
res.setSuccess(true);
}
} catch (final Exception e) {
res.setSuccess(false);
LOG.info("Error: {}", e.getMessage());
res.setReason(e.getMessage());
}
return res;
}
I receive the error message: 'Updating the path 'creationDate' would create a conflict at 'creationDate'' and I understand the reason laid out in the other SO thread, but i'm wondering if there is a way to achieve this some other way. Additionally, below is the test that I wrote to ensure the functionality is achieved. I appreciate any help.
@Test
public void testAddProductListEndpointWithExistingProduct() throws JsonProcessingException {
final Product p = getProduct();
final String token = getToken(trackingId);
addCatalog(trackingId, catalog, null, token);
final Response addProductResponse = addProduct(trackingId, catalog, p, token);
final Document doc = getDatabase().getCollection(productsCollection).find(new BasicDBObject("id", p.getId()))
.first();
final List<Product> products = new ArrayList<>();
products.add(new Product().id("p1").name("Product 1").creationDate(OffsetDateTime.now().plusHours(1)).categoryHierarchies(List.of(new Category().id("id1").label("label1").type("type1"))));
products.add(new Product().id("p2").name("Product 2").categoryHierarchies(List.of(new Category().id("id1").label("label1").type("type1"))));
products.add(new Product().id("p3").name("Product 3").categoryHierarchies(List.of(new Category().id("id1").label("label1").type("type1"))));
final Response addProductResponse2 = addProducts(trackingId, catalog, products, token);
final Document doc2 = getDatabase().getCollection(productsCollection).find(new BasicDBObject("id", "p1")).first();
//creation date of existing product should not be overwritten
Assert.assertTrue(doc.get("creationDate").equals(doc2.get("creationDate")));
}
Solution 1:[1]
If you wish to upsert the products using a bulk and a list of products, so that each product in your list will be inserted if it does not already exists and will override the existing document in case of existence, but not the creationDate property that is directly under the product, it can be done:
If the creation date is under your product you can't both $set it under product and $setOnInsert, so if your product structure is:
product: {
creationDate: ISODate,
propX: 'a',
propY: {propZ: 2, propT: 4, propA: ['A', 'B']}
}
Your code should be something like:
final Bson update = new Document("$set", 'product.propX', 'product.propY')
.append("$setOnInsert", new Document(Constants.PRODUCT_CREATION_DATE_PROPERTY, creationDate));
Note that the bulk operations are written using your code, before being executed as a query. This means you can use a loop to update the $set part of the bulk in order to add all properties directly under product, from your product list to your $set clause.
You can see it works on the playground, in the answer to a similar question.
Sources
This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.
Source: Stack Overflow
| Solution | Source |
|---|---|
| Solution 1 |
