Reputation: 6080
We are doing partial update on an Entity using Spring Boot as follows, is there a more elegant way as if the Entity contains 50+ attributes then its really painful to handle
public Foo updateFoo(@PathVariable String id, @RequestBody Foo fooInput) {
Foo foo = fooRepository.findById(id)
.orElseThrow(() -> new ResourceNotFoundException("Foo not found for this id: " + id));
if (fooInput.getCaption() != null) {
foo.setCaption(fooInput.getCaption());
}
if (fooInput.getBar() != null) {
foo.setBar(fooInput.getBar());
}
...
}
Upvotes: 1
Views: 4306
Reputation: 41
Furter to Robert Żyłan's answer, you can simplify your code a bit.
As an addition, you can use either of two here: Map<String, Object> fields
or String json
, so your service method may look like this:
@Autowired
private ObjectMapper objectMapper;
@Override
@Transactional
public Foo save(long id, Map<String, Object> fields) throws JsonMappingException {
Foo foo = fooRepository.findById(id)
.orElseThrow(() -> new ResourceNotFoundException("Foo not found for this id: " + id));
return objectMapper.updateValue(foo , fields);
}
As a second solution you could use Reflection
to map only properties that exist in a Map of values that was sent, so your service method may look like this:
@Override
@Transactional
public Foo save(long id, Map<String, Object> fields) {
Foo foo = fooRepository.findById(id)
.orElseThrow(() -> new ResourceNotFoundException("Foo not found for this id: " + id));
fields.keySet()
.forEach(k -> {
Method method = ReflectionUtils.findMethod(LocationProduct.class, "set" + StringUtils.capitalize(k));
if (method != null) {
ReflectionUtils.invokeMethod(method, foo, fields.get(k));
}
});
return foo;
}
Second solution allows you to insert some additional business logic into mapping process, might be conversions or calculations ect.
Also unlike finding reflection field Field field = ReflectionUtils.findField(Foo.class, k);
by name and than making it accessible, finding property's setter actually calls setter method that might contain additional logic to be executed and prevents from setting value to private properties.
Upvotes: 0
Reputation: 96
If the non-updated fields are not present in JSON, you can do it like that:
Foo updateFoo(@PathVariable String id, @RequestBody String json) throws IOException {
ObjectMapper mapper = new ObjectMapper();
Foo foo = fooRepository.findById(id)
.orElseThrow(() -> new ResourceNotFoundException("Foo not found for this id: " + id));
ObjectReader objectReader = mapper.readerForUpdating(foo);
foo = objectReader.readValue(json);
return foo;
}
If non-updated fields are set to null (or are not present in JSON), you can use the function below. Unfortunately, I could not ingore the null values during deserialization with Jackson annotations, so I deleted the null values myself.
Foo updateFoo(@PathVariable String id, @RequestBody ObjectNode node) throws IOException {
Iterator<String> i = node.fieldNames();
while(i.hasNext()) {
String field = i.next();
if(node.get(field).isNull()) {
node.remove(field);
}
}
ObjectMapper mapper = new ObjectMapper();
mapper.setDefaultPropertyInclusion(Include.NON_EMPTY);
Foo foo = fooRepository.findById(id)
.orElseThrow(() -> new ResourceNotFoundException("Foo not found for this id: " + id));
ObjectReader objectReader = mapper.readerForUpdating(foo);
foo = objectReader.readValue(node);
return foo;
}
Upvotes: 0
Reputation: 3945
You can use to JSON Patch library with Spring Framework. Implementing JSON Patch in a Spring Boot Application
This is an implementation of RFC 6902 (JSON Patch) and RFC 7386 (JSON Merge Patch) written in Java, which uses Jackson (2.2.x) at its core.
JSON Patch is a format for describing changes to a JSON document. It can be used to avoid sending a whole document when only a part has changed. When used in combination with the HTTP PATCH method, it allows partial updates for HTTP APIs in a standards compliant way. The patch documents are themselves JSON documents. JSON Patch is specified in RFC 6902 from the IETF.
Upvotes: 2