9

In my database I have a document that looks like this

{
    "_id" : ObjectId("5864ddd8e38112fd70b89893"),
    "_class" : "com.apic.models.UserReg",
    "name" : "Bijay",
    "email" : "apic.apps@gmail.com",
    "psd" : "16d932a5a3da90cc6afd831016b5a6821f0badf7e2c624159205924433613c3a",
    "activationToken" : "fe8376ea2dbdf61ebc0f11a2361d741ba3178362d5bf876cf47e6a126bc5b39c",
    "verified" : false
}

I also have a bean that looks like this

public class User {
  @Id
  private int id;
  private String name;
  private String email;

  // getter/setter methods

}

So when I try to call save() method of MongoOperations, it replaces all missing properties like psd, verified and activationToken.

mongoOperations.save(user, COLLECTION);

Is there any way where I can update only the existing properties in my models class and leave others as it is?

Ondra Žižka
  • 43,948
  • 41
  • 217
  • 277
BiJ
  • 1,639
  • 5
  • 24
  • 55
  • Yes, by using [updateFirst](http://docs.spring.io/spring-data/data-mongo/docs/current/api/org/springframework/data/mongodb/core/MongoOperations.html#updateFirst-org.springframework.data.mongodb.core.query.Query-org.springframework.data.mongodb.core.query.Update-java.lang.Class-) (or updateMulti) – Ori Dar Dec 29 '16 at 10:39
  • @OriDar I am very new to Spring. Can you give an example? – BiJ Dec 29 '16 at 10:42
  • 1
    you should use mongorepository for basic crud scenarios. – s7vr Dec 29 '16 at 11:23

4 Answers4

10

If you want to set an arbitrary number of fields (but not overwrite others), you can add a function to your model that turns it into a Map. If you're in Spring obviously Jackson is a good choice for this.

From there you can remove all null values and then add each field that does have a value to a $set operation.

Map<String, Object> objectMap = user.toMap();
    objectMap.values().removeIf(Objects::isNull);
    Update update = new Update();
    objectMap.forEach(update::set);

    return mongoOperations.findAndModify(
            Query.query(Criteria.where("_id").is(user.getId())), update, User.class);
Community
  • 1
  • 1
Rossiar
  • 2,416
  • 2
  • 23
  • 32
  • there is an nested array inside my document I want to skip updating that array, how can I skip that part – Rookie007 Jul 13 '21 at 13:34
9

Yes you can call selective updates

Query query = new Query(new Criteria("id").is(user.getId()));
Update update = new Update().set("name", user.getName()).set("email", user.getEmail());
mongoOperations.updateFirst(query, update, COLLECTION);
Rahul Kumar
  • 2,781
  • 1
  • 21
  • 29
3

Based on @Rossiar's answer, we can use MongoConverter rather than Jackson.

Document document = new Document();
mongoOperations.getConverter().write(user, document);
Update update = new Update();
document.forEach(update::set);
return mongoOperations.findAndModify(
            Query.query(Criteria.where("_id").is(user.getId())), update, User.class);
bubbles
  • 2,597
  • 1
  • 15
  • 40
0

Recently I had to cope to the problem above of "Is there any way where I can update only the existing properties in my models class and leave others as it is?"

The aforementioned accepted answer will work fine with the drawback of having to programmatically create each one of the update terms. If you have nested classes you would end having to work with dot notation and other complex stuff.

My first intuition was to use the $set Mongo operation, create a Document from a Json that contains only the updatable fields + key, and then apply it to the collection. Although it works for a structure like the one in the example of this question, it will fail with nested classes.

The solution was to use an enhanced version of the Apache BeanUtils copyProperties. The idea is:

  • Get an actual full record from the db
  • Populate the update Pojo with the key and updatable data. The remaining fields should be null.
  • Copy from update Pojo to actual Pojo using the enhanced version.
  • Create a Document from the Pojo (code bellow using SpringMongo)
  • Call update
class NullAwareBeanUtilsBean extends BeanUtilsBean {
    
    
    @Override
    public void copyProperty(Object dest, String name, Object value)
            throws IllegalAccessException, InvocationTargetException {
        if (value == null)
            return;
        else if(value instanceof NonNullCopy) {
            Class<?> destClazz = value.getClass();
                Class<?> origClazz = dest.getClass();
                String className = destClazz.getSimpleName();
        
                for(Method m : origClazz.getDeclaredMethods()) {
                    if(m.getReturnType().equals(destClazz)) {
                        copyProperties(m.invoke(dest, Collections.EMPTY_LIST.toArray()),value);
                    }                       
                }
                return;
        }

        super.copyProperty(dest, name, value);
    }

       
}
            Document document = new Document();
            MongoOperations.getConverter().write(Pojo, document);
            Update update = new Update();
            document.forEach(update::set);
            dao.updateFirst(...

A complete discussion of this approach you can find at Copy non-null properties from one object to another using BeanUtils or similar