Course – LS – All

Get started with Spring and Spring Boot, through the Learn Spring course:

>> CHECK OUT THE COURSE

1. Introduction

In this quick tutorial, we’ll learn how to work with Java HashMap in MongoDB. MongoDB has a map-friendly API, and Spring Data MongoDB makes working with maps or lists of maps even more straightforward.

2. Setting up Our Scenario

Spring Data MongoDB comes with MongoTemplate, which has many overloaded versions of insert() that allow us to insert a map into our collections. MongoDB represents a document in JSON format. So, we can replicate it with a Map<String, Object> in Java.

We’ll implement our use cases using MongoTemplate and a simple reusable map. Let’s start by creating the map reference and injecting MongoTemplate:

class MongoDbHashMapIntegrationTest {
    private static final Map<String, Object> MAP = new HashMap<>();

    @Autowired
    private MongoTemplate mongo;
}

Then, we’ll initialize our map with a few entries, each of a different type:

@BeforeAll
static void init() {
    MAP.put("name", "Document A");
    MAP.put("number", 2);
    MAP.put("dynamic", true);
}

We’re marking it with @BeforeAll so all our tests can use it.

3. Insert a Single Map Directly

First, we call mongo.insert() and choose a collection to put it in:

@Test
void whenUsingMap_thenInsertSucceeds() {
    Map<String, Object> saved = mongo.insert(MAP, "map-collection");

    assertNotNull(saved.get("_id"));
}

No particular wrapper is required. After insertion, our map becomes a single JSON document in our collection. Most importantly, we can check for the presence of the _id property that MongoDB generates, ensuring it was correctly processed.

4. Insert Maps in Bulk Directly

We can also insert a Collection of Maps. Each insertion becomes a different document. Also, to make sure we don’t insert duplicates, we’ll use a Set.

Let’s add the map we created earlier, along with a new one, to our set:

@Test
void whenMapSet_thenInsertSucceeds() {
    Set<Map<String, Object>> set = new HashSet<>();

    Map<String, Object> otherMap = new HashMap<>();
    otherMap.put("name", "Other Document");
    otherMap.put("number", 22);

    set.add(MAP);
    set.add(otherMap);

    Collection<Map<String, Object>> insert = mongo.insert(set, "map-set");

    assertEquals(2, insert.size());
}

As a result, we get two inserts. This method helps reduce the overhead of adding a lot of documents at once.

5. Construct Document From Map and Insert

The Document class is the recommended way of handling MongoDB documents in Java. It implements Map and Bson, making it easy to work. Let’s use the constructor that accepts a map:

@Test
void givenMap_whenDocumentConstructed_thenInsertSucceeds() {
    Document document = new Document(MAP);

    Document saved = mongo.insert(document, "doc-collection");

    assertNotNull(saved.get("_id"));
}

Internally, Document uses a LinkedHashMap, guaranteeing the order of insertion.

6. Construct BasicDBObject From Map and Insert

Although the Document class is preferred, we can also construct a BasicDBObject from a map:

@Test
void givenMap_whenBasicDbObjectConstructed_thenInsertSucceeds() {
    BasicDBObject dbObject = new BasicDBObject(MAP);

    BasicDBObject saved = mongo.insert(dbObject, "db-collection");

    assertNotNull(saved.get("_id"));
}

BasicDBObject is still helpful if we’re dealing with legacy code, as the Document class is only available from MongoDB driver version 3 onwards.

7. Build Document From Stream of Object Values and Insert

In our final example, we’ll build a Document object from a Map where the value of each key is a List of Object values. Since we know the format of the values, we can build our Document by putting a property name for each value.

Let’s start by building our input map:

Map<String, List<Object>> input = new HashMap<>();
List<Object> listOne = new ArrayList<>();
listOne.add("Doc A");
listOne.add(1);

List<Object> listTwo = new ArrayList<>();
listTwo.add("Doc B");
listTwo.add(2);

input.put("a", listOne);
input.put("b", listTwo);

As we can see, there are no property names, only values. So, let’s stream our input‘s entrySet() and build our result from it. To do that, we’ll collect each entry in our input into a HashSet. Then, we’ll build a Document in the accumulator function, putting the entry key as the _id property. After that, we’ll iterate over the entry values, putting them under the appropriate property name. In the end, we’ll add each Document to our result:

Set<Document> result = input.entrySet()
  .stream()
  .collect(HashSet<Document>::new, 
    (set, entry) -> {
      Document document = new Document();

      document.put("_id", entry.getKey());
      Iterator<Object> iterator = entry.getValue()
        .iterator();
      document.put("name", iterator.next());
      document.put("number", iterator.next());

      set.add(document);
    }, 
    Set::addAll
  );

Let’s note that we don’t need the third argument from collect() in this example. But that’s because only parallel streams use the combiner function.

Finally, we can insert result into MongoDB:

mongo.insert(result, "custom-set");

This strategy is useful, for instance, if we want to convert CSV values to JSON.

8. Conclusion

In this article, we saw different ways to work with a HashMap and lists of HashMaps to insert documents into a MongoDB collection. We used MongoTemplate to simplify the task and used the most common document abstractions: BasicDBObject and Document.

And as always, the source code is available over on GitHub.

Course – LSD (cat=Persistence)

Get started with Spring Data JPA through the reference Learn Spring Data JPA course:

>> CHECK OUT THE COURSE
res – Persistence (eBook) (cat=Persistence)
Comments are closed on this article!