Danilo Cianciulli Danilo Cianciulli - 4 months ago 91
Java Question

How to distinguish between null and not provided values for partial updates in Spring Rest Controller

I'm trying to distinguish between null values and not provided values when partially updating an entity with PUT request method in Spring Rest Controller.

My scenario is pretty complex, but I will try to explain it with a dummy example, as follows.

Consider the following entity that I will use as an example to explain my problem:

@Entity
private class Person {

@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;

/* in this example, let's assume that
the following attributes may be null */

private String firstName;
private String lastName;

/* getters and setters ... */
}


The following would be my Person repository made with Spring Data

@Repository
public interface PersonRepository extends CrudRepository<Person, Long> {
}


The DTO that I use to update my entity would be something like this:

private class PersonDTO {
private String firstName;
private String lastName;

/* getters and setters ... */
}


In my real scenario, entity and DTO are much more complex with several JSR-303 annotations for validation, but they do not make the difference for my problem, so I've omitted them.

My Spring RestController would be the following:

@RestController
@RequestMapping("/api/people")
public class PersonController {

@Autowired
private PersonRepository people;

/* other CRUD operations,
such as create, get, and delete */

@Transactional
@RequestMapping(path = "/{personId}", method = RequestMethod.PUT)
public ResponseEntity<?> update(
@PathVariable String personId,
@RequestBody PersonDTO dto) {

// first: I get the entity by ID
Person p = people.findOne(personId); // we assume it exists

// second: I update only entity attributes
// that have been defined
if(/* dto.getFirstName is defined */)
p.setFirstName = dto.getFirstName;

if(/* dto.getLastName is defined */)
p.setLastName = dto.getLastName;

// finally, I return the updated entity
return ResponseEntity.ok(p);
}
}


Now, my problem is about how to distinguish between
null
and not defined values, since
null
values for the Person entity are allowed.
As an example, consider the following JSON request body:

{"firstName": "John"}


This means: update
firstName
with the value "John", but leave
lastName
unchanged.

But if the request body is

{"firstName": "John", "lastName": null}


this means: update
firstName
with the value "John", and set
lastName
to
null
.

In the
update
method of the
PersonController
, I cannot distinguish between these two cases, since the
lastName
attribute in the DTO, in both cases, would be set to
null
by the Jackson mapping process.

I know that REST best practices say that the PUT request method must be used only to replace an entity by providing the full json representation of the object, and use the PATCH request method for partial updates according to the RFC 6902. However, in my particular scenario, I need to perform partial updates with PUT method.

Any help would be highly appreciated.

Regards

Answer

Actually,if ignore the validation,you can solve your problem like this.

   public class BusDto {
       private Map<String, Object> changedAttrs = new HashMap<>();

       /* getter and setter */
   }
  • First, write a super class for your dto,like BusDto.
  • Second, change your dto to extend the super class, and change the dto's set method,to put the attribute name and value to the changedAttrs(beacause the spring would invoke the set when the attribute has value no matter null or not null).
  • Third,traversal the map.