Custom Method for Update Query With Spring Data Mongorepository

Spring Data and MongoDB repository - how to create an update query?

The MongoDB query language is a query-only language. Thus, there's no such thing as an update query. If you need to executed dedicated updates with a Spring Data repository on top of MongoDB, you need a custom implementation method.

// Interface for custom functionality
interface SomeCustomRepository {
void updateMethod(…);
}

// Custom implementation
class FooRepositoryImpl implements SomeCustomRepository {

public void updateMethod(…) {
mongoTemplate.update(…);
}
}

// Core repository declaration combining CRUD functionality and custom stuff
interface FooRepository extends CrudRepository<Foo, ObjectId>, SomeCustomRepository {

}

This approach is also described in the reference documentation.

How do I update a list using MongoDB and Spring's repository?

Using MongoTemplate, Query and Update by spring data mongodb

  • Update.addToSet Add items to a Set (non-duplicatable)
  • Update.push Append items to an Array (duplicatable)
import org.springframework.data.mongodb.core.*;

@Autowired
MongoTemplate mongoTemplate;
Query query = new Query();
query.addCriteria(Criteria.where("_id").is(idToUpdate));
Update update = new Update();
update.addToSet("targetField", "newValue");
mongoTemplate.updateFirst(query, update, "collectionName");
  • To get result document, using FindAndModifyOptions
FindAndModifyOptions options = FindAndModifyOptions.options()
.returnNew(true);
TargetClass result = mongoTemplate.findAndModify(query, update, options, TargetClass.class, "collectionName");
  • Add multiple value
update.addToSet("targetField")
.each("value1", "value2");
update.push("targetField")
.each("value1", "value2");
  • Remove item at position
update.pop("targetField", position);
  • Remove item by value
update.pull("targetField", "value");
  • Remove multiple items
update.pullAll("targetField", new Object[]{"value1", "value2"});

Update Specific Fields with Spring Data Rest and MongoDB

Spring Data Rest uses Spring Data repositories to automatically retrieve and manipulate persistent data using Rest calls (check out https://docs.spring.io/spring-data/rest/docs/current/reference/html/#reference).

When using Spring Data MongoDB, you have the MongoOperations interface which is used as a repository for your Rest endpoints.
However MongoOperations currently does not supports specific fields updates !

PS: It will be awesome if they add this feature like @DynamicUpdate in Spring Data JPA

But this doesn't mean it can be done, here's the workaround I did when I had this issue.

Firstly let me explain what we're going to do:

  1. We will create a controller which will override all the PUT operations so that we can implement our own update method.
  2. Inside that update method, we will use MongoTemplate which do have the ability to update specific fields.

N.B. We don't want to re-do these steps for each model in our application, so we will retrieve which model to update dynamically. In order to do that we will create a utility class. [This is optional]

Let's start by adding the org.reflections api to our project dependency which allows us to get all the classes which have a specific annotation (@Document in our case):

<dependency>
<groupId>org.reflections</groupId>
<artifactId>reflections</artifactId>
<version>0.9.12</version>
</dependency>

Then create a new class, called UpdateUtility and add the following methods and also replace the MODEL_PACKAGE attribute with your own package containing your entities:

public class UpdateUtility {

private static final String MODEL_PACKAGE = "com.mycompany.myproject.models";
private static boolean initialized = false;
private static HashMap<String, Class> classContext = new HashMap<>();

private static void init() {
if(!initialized) {
Reflections reflections = new Reflections(MODEL_PACKAGE);
Set<Class<?>> classes = reflections.getTypesAnnotatedWith(Document.class); // Get all the classes annotated with @Document in the specified package

for(Class<?> model : classes) {
classContext.put(model.getSimpleName().toLowerCase(), model);
}

initialized = true;
}
}

public static Class getClassFromType(String type) throws Exception{
init();
if(classContext.containsKey(type)) {
return classContext.get(type);
}
else {
throw new Exception("Type " + type + " does not exists !");
}
}
}

Using this utility class we can retreive the model class to update from it's type.
E.g: UpdateUtility.getClassFromType() will returns User.class

Now let's create our controller:

public class UpdateController {

@Autowired
private MongoTemplate mongoTemplate;

@PutMapping("/{type}/{id}")
public Object update(@RequestBody HashMap<String, Object> fields,
@PathVariable(name = "type") String type,
@PathVariable(name = "id") String id) {
try {
Class classType = UpdatorUtility.getClassFromType(type); // Get the domain class from the type in the request
Query query = new Query(Criteria.where("id").is(id)); // Update the document with the given ID
Update update = new Update();

// Iterate over the send fields and add them to the update object
Iterator iterator = fields.entrySet().iterator();
while(iterator.hasNext()) {
HashMap.Entry entry = (HashMap.Entry) iterator.next();
String key = (String) entry.getKey();
Object value = entry.getValue();
update.set(key, value);
}

mongoTemplate.updateFirst(query, update, classType); // Do the update
return mongoTemplate.findById(id, classType); // Return the updated document
} catch (Exception e) {
// Handle your exception
}
}
}

Now we're able to update the specified fields without changing the calls.
So in your case, the call would be:

PUT http://MY-DOMAIN/user/MY-USER-ID { lastName: "My new last name" }

PS: You can improve it by adding the possibility to update specific field in a nested objects...



Related Topics



Leave a reply



Submit