10

In my database I have a document that looks like this

{
    "_id" : ObjectId("5864ddd8e38112fd70b89893"),
    "_class" : "com.apic.models.UserReg",
    "name" : "Bijay",
    "email" : "[email protected]",
    "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?

3
  • Yes, by using updateFirst (or updateMulti) Commented Dec 29, 2016 at 10:39
  • @OriDar I am very new to Spring. Can you give an example? Commented Dec 29, 2016 at 10:42
  • 1
    you should use mongorepository for basic crud scenarios. Commented Dec 29, 2016 at 11:23

4 Answers 4

10

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);
Sign up to request clarification or add additional context in comments.

Comments

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);

1 Comment

there is an nested array inside my document I want to skip updating that array, how can I skip that part
4

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);

2 Comments

This should be the top answer
If the document can also be a new one, you can also use mongoOperations.upsert instead of mongoOperations.findAndModify (the parameters are the same for both methods).
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

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.