Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Make SpringData update only changed field of POJO

I use Spring Data over MongoDB. I was able to save POJOs, update them. It's working fine. But now I want to flush in db only changed fields of POJO.

For example I have User entity. I create user and then update lastActiveDate from time to time.

@Document
class User {
    @Id
    BigInteger ID;

    String email;

    String name;

    Date lastActiveDate;
}

User user = new User();
user.setName("User");
user.setEmail("[email protected]");
repository.save(user);

User toUpdUser = repository.findOne(userId);
toUpdUser.setLastActiveDate(new Date);
repository.save(toUpdUser );

In second save I want update only lastActiveDate field not whole user because Document update can be slow on large entities. Also I want to know changeset (at least set of updated field).

For now I didn't find API to do that. Only possibility is to handle it manually in setters (store set of changed fields and store it manually) but it looks ugly and unsupportable. Another option is using of AOP on beans to achieve same result but IFAIK Spring Data does not treat POJO as Spring bean and uses constructor so after load POJO with AOP will be just POJO.

UPD: I looking for method without explicit mongo api (or mongo-like api) call. I have entities with dozens of field and client can change almost any of them. I want to store only changed fields and be able to get changeset to perform some checks. Difference beetween entity store and field update is too big - 5ms vs 0.2ms

Of course I can create my own implementation of POJO mappers with CRUD tracker support but it already exist why not try it. And I can use another framework not only SpringData.

like image 345
ainlolcat Avatar asked Sep 17 '15 17:09

ainlolcat


Video Answer


2 Answers

Spring-data doesn't support field-by-field comparison in order to figure out which fields are updated and only send those to the database. It does support only persisting non-null fields though, I find myself leveraging that when I implement PATCH or PUT web services. Create a $set object with the non-null fields, and you'll keep the other part of the document intact.

You'll need to use a somewhat undocumented feature of MongoConverters, here's an example of a PATCH-webservice in spring-mvc which also maintains a history of events in an "events" array on the document:

@RestController
public class MyWebservice {

    @Autowired
    private MongoConverter mongoConverter;

    @RequestMapping(value = "/order/{id}", method = PATCH, produces = APPLICATION_JSON_UTF8_VALUE, consumes = APPLICATION_JSON_UTF8_VALUE)
    public Order updateOrder(@PathVariable("id") String id, @RequestBody Order order) throws JsonProcessingException {
        order.setId(id);
        DBObject update = getDbObject(order);
        mongoTemplate.updateFirst(query(where("id").is(id)), Update.fromDBObject(new BasicDBObject("$set", update)).push("events", order), Order.class);
        return mongoTemplate.findOne(query(where("id").is(id)), Order.class);
    }

    private DBObject getDbObject(Object o) {
        BasicDBObject basicDBObject = new BasicDBObject();
        mongoConverter.write(o, basicDBObject);
        return basicDBObject;
    }
}

MongoConverter will only write non-null values to BasicDBObject.

like image 157
gogstad Avatar answered Sep 17 '22 16:09

gogstad


You can use Jackson API to determine what is changed and persist POJO. Here is the code below

public class TestJacksonUpdate {

class Person implements Serializable {
    private static final long serialVersionUID = -7207591780123645266L;
    public String code = "1000";
    public String firstNm = "John";
    public String lastNm;
    public Integer age;
    public String comments = "Old Comments";

    @Override
    public String toString() {
        return "Person [code=" + code + ", firstNm=" + firstNm + ", lastNm=" + lastNm + ", age=" + age
                + ", comments=" + comments + "]";
    }
}

public static void main(String[] args) throws JsonProcessingException, IOException {
    TestJacksonUpdate o = new TestJacksonUpdate();

    String input = "{\"code\":\"10000\",\"lastNm\":\"Smith\",\"comments\":\"Jackson Update WOW\"}";
    Person persist = o.new Person();

    System.out.println("persist: " + persist);

    ObjectMapper mapper = new ObjectMapper();
    Person finalPerson = mapper.readerForUpdating(persist).readValue(input);

    System.out.println("Final: " + finalPerson);
    // repository.save(finalPerson);    } }
like image 43
Anand Avatar answered Sep 21 '22 16:09

Anand