Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to save and query dynamic fields in Spring Data MongoDB?

I'm on Spring boot 1.4.x branch and Spring Data MongoDB.

I want to extend a Pojo from HashMap to give it the possibility to save new properties dynamically.

I know I can create a Map<String, Object> properties in the Entry class to save inside it my dynamics values but I don't want to have an inner structure. My goal is to have all fields at the root's entry class to serialize it like that:

{
   "id":"12334234234",
   "dynamicField1": "dynamicValue1",
   "dynamicField2": "dynamicValue2"
}

So I created this Entry class:

@Document
public class Entry extends HashMap<String, Object> {

    @Id
    private String id;

    public String getId() {
        return id;
    }

    public void setId(String id) {
        this.id = id;
    }
}

And the repository like this:

public interface EntryRepository extends MongoRepository<Entry, String> {
}

When I launch my app I have this error:

Error creating bean with name 'entryRepository': Invocation of init method failed; nested exception is org.springframework.data.mapping.model.MappingException: Could not lookup mapping metadata for domain class java.util.HashMap!

Any idea?

like image 450
Franck Anso Avatar asked Sep 28 '17 10:09

Franck Anso


2 Answers

Here we can achieve using JSONObject

The entity will be like this

@Document
public class Data {
    @Id
    private String id;
    private JSONObject details;
    //getters and setters
}

The POJO will be like this

public class DataDTO {
    private String id;
    private JSONObject details;
        //getters and setters
}

In service

Data formData = new Data();
JSONObject details = dataDTO.getDetails();
details.put("dynamicField1", "dynamicValue1");
details.put("dynamicField2", "dynamicValue2");
formData.setDetails(details);
mongoTemplate.save(formData );

i have done as per my business,refer this code and do it yours. Is this helpful?

like image 197
Jay Avatar answered Oct 24 '22 01:10

Jay


TL; DR;

  1. Do not use Java collection/map types as a base class for your entities.
  2. Repositories are not the right tool for your requirement.
  3. Use DBObject with MongoTemplate if you need dynamic top-level properties.

Explanation

Spring Data Repositories are repositories in the DDD sense acting as persistence gateway for your well-defined aggregates. They inspect domain classes to derive the appropriate queries. Spring Data excludes collection and map types from entity analysis, and that's why extending your entity from a Map fails.

Repository query methods for dynamic properties are possible, but it's not the primary use case. You would have to use SpEL queries to express your query:

public interface EntryRepository extends MongoRepository<Entry, String> {

    @Query("{ ?0 : ?1 }")
    Entry findByDynamicField(String field, Object value);
}

This method does not give you any type safety regarding the predicate value and only an ugly alias for a proper, individual query.

Rather use DBObject with MongoTemplate and its query methods directly:

List<DBObject> result = template.find(new Query(Criteria.where("your_dynamic_field")
                                          .is(theQueryValue)), DBObject.class);

DBObject is a Map that gives you full access to properties without enforcing a pre-defined structure. You can create, read, update and delete DBObjects objects via the Template API.

A last thing

You can declare dynamic properties on a nested level using a Map, if your aggregate root declares some static properties:

@Document
public class Data {

    @Id
    private String id;
    private Map<String, Object> details;
}
like image 18
mp911de Avatar answered Oct 24 '22 00:10

mp911de