java - 如何区分 Spring Rest Controller 中部分更新的 null 值和未提供的值

标签 java json rest spring-mvc jackson

在 Spring Rest Controller 中使用 PUT 请求方法部分更新实体时,我试图区分空值和未提供的值。

以以下实体为例:

@Entity
private class Person {

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

    /* let's assume the following attributes may be null */
    private String firstName;
    private String lastName;

    /* getters and setters ... */
}

我的个人存储库(Spring Data):

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

我使用的 DTO:

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

    /* getters and setters ... */
}

我的 Spring RestController:

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

    @Autowired
    private PersonRepository people;

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

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

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

        return ResponseEntity.ok(p);
    }
}

请求缺少属性

{"firstName": "John"}

预期行为:更新 firstName="John"(保持 lastName 不变)。

带有空属性的请求

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

预期行为:更新 firstName="John" 并设置 lastName=null

我无法区分这两种情况,因为 jackson 总是将 DTO 中的 lastName 设置为 null

注意: 我知道 REST 最佳实践 (RFC 6902) 建议使用 PATCH 而不是 PUT 进行部分更新,但在我的特定场景中我需要使用 PUT。

最佳答案

另一种选择是使用 java.util.Optional。

import com.fasterxml.jackson.annotation.JsonInclude;
import java.util.Optional;

@JsonInclude(JsonInclude.Include.NON_NULL)
private class PersonDTO {
    private Optional<String> firstName;
    private Optional<String> lastName;
    /* getters and setters ... */
}

如果未设置 firstName,则值为 null,并且会被 @JsonInclude 注释忽略。否则,如果在请求对象中隐式设置,firstName 不会为空,但 firstName.get() 会。我发现这个浏览链接到 little lower down in a different comment 的解决方案@laffuste (garretwilson 最初的评论说它不起作用)。

你也可以使用 Jackson 的 ObjectMapper 将 DTO 映射到 Entity,它会忽略未在请求对象中传递的属性:

import com.fasterxml.jackson.databind.ObjectMapper;

class PersonController {
    // ...
    @Autowired
    ObjectMapper objectMapper

    @Transactional
    @RequestMapping(path = "/{personId}", method = RequestMethod.PUT)
    public ResponseEntity<?> update(
            @PathVariable String personId,
            @RequestBody PersonDTO dto
    ) {
        Person p = people.findOne(personId);
        objectMapper.updateValue(p, dto);
        personRepository.save(p);
        // return ...
    }
}

使用 java.util.Optional 验证 DTO 也有些不同。 It's documented here ,但我花了一段时间才找到:

// ...
import javax.validation.constraints.NotNull;
import javax.validation.constraints.NotBlank;
import javax.validation.constraints.Pattern;
// ...
private class PersonDTO {
    private Optional<@NotNull String> firstName;
    private Optional<@NotBlank @Pattern(regexp = "...") String> lastName;
    /* getters and setters ... */
}

在这种情况下,firstName 可能根本没有设置,但如果设置了,则如果 PersonDTO 已验证,则可能不会设置为 null。

//...
import javax.validation.Valid;
//...
public ResponseEntity<?> update(
        @PathVariable String personId,
        @RequestBody @Valid PersonDTO dto
) {
    // ...
}

同样值得一提的是,Optional 的使用似乎引起了激烈的争论,并且在编写 Lombok 的维护者时不会支持它(参见 this question for example)。这意味着在具有约束的可选字段的类上使用 lombok.Data/lombok.Setter 不起作用(它尝试创建约束完整的 setter ),因此使用 @Setter/@Data 会导致抛出异常,因为setter 和成员变量设置了约束。编写不带 Optional 参数的 Setter 似乎也是更好的形式,例如:

//...
import lombok.Getter;
//...
@Getter
private class PersonDTO {
    private Optional<@NotNull String> firstName;
    private Optional<@NotBlank @Pattern(regexp = "...") String> lastName;

    public void setFirstName(String firstName) {
        this.firstName = Optional.ofNullable(firstName);
    }
    // etc...
}

关于java - 如何区分 Spring Rest Controller 中部分更新的 null 值和未提供的值,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/38424383/

相关文章:

java - 在java中解析序列化的json有效负载

json - 一对多关系中的外键始终为空 - Spring Boot Data with JPA

java - 通过在java中读取mySQL表创建JSON文件

java - 如何执行 if 语句 3 次?

java - Spring页面重定向好习惯

security - 保护 API : SSL & HTTP Basic Authentication vs Signature

android - 将 Retrofit RestAdapter.LogLevel 设置为 FULL 以外的任何值都会导致 Empty Response Body

java - 如何在 Lua 中捕获异常?我正在使用 LuaJava

python - 类型 'KeyPoint'的对象不是JSON可序列化的opencv

javascript - 将动态键值添加到 JSON 对象