0

Currently I'm trying to, in an atomic way, since there will be multiple processes trying to access the same document, update or insert a document that contains multiple fields, one of which is a Map<Instant, Position>.

For this, I'm trying to use MongoTemplate, specifically the findAndModify() method which I've read provides the ACID properties I'm looking for. Given this, the query I implemented was the following:

slot.positions.forEach { position ->
    mongoTemplate.findAndModify(
        Query.query(Criteria.where("assetId").`is`(slot.assetId).and("startTime").`is`(slot.start)),
        Update().set("positions.${position.key}", position.value).setOnInsert("updatedAt", Instant.now()),
        FindAndModifyOptions.options().upsert(true),
        SlotDocument::class.java,
    )
}

The logic behind this was to try and upsert each position one at a time given that I do not know how to upsert the entire document all at once.

This attempt actually saves two positions successfully but then fails with the message:

org.springframework.core.convert.ConversionFailedException: Failed to convert from type [java.lang.String] to type [java.time.Instant] for value [2023-08-28T09:57:30]
...
Caused by: java.time.format.DateTimeParseException: Text '2023-08-28T09:57:30' could not be parsed at index 19

This could be related to another issue I identified while debugging which was ever since I changed to the MongoTemplate, my MappgingMongoConverter stopped transforming . into MIL as specified by the code below

@Configuration
class MongoConfig {

    /**
    * This was my new attempt when changing to MongoTemplate
    */
    @Bean
    fun mongoTemplate(mongoDatabaseFactory: MongoDatabaseFactory, mappingMongoConverter: MappingMongoConverter): MongoTemplate {
        mappingMongoConverter.setMapKeyDotReplacement("MIL")
        return MongoTemplate(mongoDatabaseFactory, mappingMongoConverter)
    }

    /**
     * Replaces dots in map keys that are to be stored in MongoDB
     * This was the initial and successful attempt when using MongoRepository
     */
    @Autowired
    fun setMapKeyDotReplacement(mappingMongoConverter: MappingMongoConverter) {
        mappingMongoConverter.setMapKeyDotReplacement("MIL")
    }
}

So in the end, to summarise, I cannot:

  • Upsert my slot document in order to, keeping the Atomic property, update the document and the content of its map field;
  • Replace the . in the keys (Instant) of the map which is causing problems with the data structure in MongoDB
Pmsmm
  • 352
  • 5
  • 17

1 Answers1

1
  1. Upserting Documents with Nested Fields:

To upsert a document with nested fields using MongoTemplate, you're on the right track with the findAndModify method. However, your query and update might need a slight adjustment to properly handle the nested map.

Here's a revised version of your code:

slot.positions.forEach { position ->
    val query = Query.query(Criteria.where("assetId").`is`(slot.assetId).and("startTime").`is`(slot.start))
    val update = Update().set("positions.${position.key}", position.value).setOnInsert("updatedAt", Instant.now())
    mongoTemplate.findAndModify(query, update, FindAndModifyOptions().upsert(true), SlotDocument::class.java)
}
  1. Handling Dot Notation in Map Keys:

Since you're dealing with map keys of type Instant, which can't contain dots, you need to handle the key transformation yourself. Unfortunately, the MappingMongoConverter's mapKeyDotReplacement property isn't designed to handle this kind of transformation dynamically.

To work around this issue, you can create a custom function to transform Instant keys into a string format that doesn't contain dots.

For example, you can convert the Instant to milliseconds since the epoch:

fun instantToMapKey(instant: Instant): String {
    return instant.toEpochMilli().toString()
}

And then, when you're accessing the positions map in your code, use this function to transform Instant keys:

val transformedKey = instantToMapKey(position.key)
val update = Update().set("positions.$transformedKey", position.value).setOnInsert("updatedAt", Instant.now())

Remember to use the same transformation when querying or retrieving values from the positions map.

By following these steps, you should be able to upsert documents with nested map fields and handle the transformation of Instant keys to avoid dot notation issues.

vlanto
  • 457
  • 6
  • I marked this as accepted answer but still I can't seem to be at peace with the fact that I can't just make everything work together to achieve the result I want (Using the MappingMongoConverter to replace the dots or simply just be able to use Instant as map key since I think I read in a few places that dots and dollar signs are actually permitted as field names) – Pmsmm Aug 31 '23 at 09:22