如何在部分更新 PATCH Spring Boot MVC 时自动添加 Bean Validation

How to automatically add Bean Validation when partially updating PATCH Spring Boot MVC

众所周知,实体的部分更新存在很大问题。由于从json 字符串自动转换为实体,所以所有未传输的字段将被标记为空。结果,我们不想重置的字段将被重置。

我来展示经典方案:

@RestController
@RequestMapping(EmployeeController.PATH)
public class EmployeeController {

    public final static String PATH = "/employees";

    @Autowired
    private Service service;

    @PatchMapping("/{id}")
    public Employee update(@RequestBody Employee employee, @PathVariable Long id) {
        return service.update(id, employee);
    }
}

@Service
public class Service {

    @Autowired
    private EmployeeRepository repository;

    @Override
    public Employee update(Long id, Employee entity) {
        Optional<T> optionalEntityFromDB = repository.findById(id);
        return optionalEntityFromDB
                .map(e -> saveAndReturnSavedEntity(entity, e))
                .orElseThrow(RuntimeException::new);
    }

    private T saveAndReturnSavedEntity(Employee entity, Employee entityFromDB) {
        entity.setId(entityFromDB.getId());
        return repository.save(entity);
    }
}

@Repository
public interface EmployeeRepository extends JpaRepository<Employee, Long> {

}

正如我已经说过的,在当前的实现中,我们将无法以任何方式执行部分更新。即不可能在json行只发送一个字段的更新;所有字段都将被更新,并且为 null(传递除外)。

这个问题的解决方法是需要手动进行字符串json到实体的转换。也就是说,不要使用 Spring Boot 中的所有魔法(这很可悲)。

我还将举例说明如何在 json 级别使用合并来实现:

@RestController
@RequestMapping(EmployeeRawJsonController.PATH)
public class EmployeeRawJsonController {

    public final static String PATH = "/raw-json-employees";

    @Autowired
    private EmployeeRawJsonService service;

    @PatchMapping("/{id}")
    public Employee update(@RequestBody String json, @PathVariable Long id) {
        return service.update(id, json);
    }
}

@Service
public class EmployeeRawJsonService {

    @Autowired
    private EmployeeRepository employeeRepository;

    public Employee update(Long id, String json) {
        Optional<Employee> optionalEmployee = employeeRepository.findById(id);
        return optionalEmployee
                .map(e -> getUpdatedFromJson(e, json))
                .orElseThrow(RuntimeException::new);
    }

    private Employee getUpdatedFromJson(Employee employee, String json) {
        Long id = employee.getId();

        updateFromJson(employee, json);

        employee.setId(id);
        return employeeRepository.save(employee);
    }

    private void updateFromJson(Employee employee, String json) {
        try {
            new ObjectMapper().readerForUpdating(employee).readValue(json);
        } catch (IOException e) {
            throw new RuntimeException("Cannot update from json", e);
        }
    }
}

@Repository
public interface EmployeeRepository extends JpaRepository<Employee, Long> {

}

使用此解决方案,我们消除了与部分更新相关的问题。

但是这里出现了另一个问题,我们正在失去自动添加的 bean 验证。

也就是第一种情况,validation加一个注解@Valid:

@PatchMapping("/{id}")
public Employee update(@RequestBody @Valid Employee employee, @PathVariable Long id) {
    return service.update(id, employee);
}

但是我们在执行手动反序列化时不能这样做。

我的问题是,有什么方法可以为第二种情况启用自动验证吗? 或者也许还有其他解决方案可以让您使用 Spring Boot magic 进行 Bean 验证。

您需要的不是正常验证,它可以通过手动验证器实现 call.Let 现在走手动路线并以编程方式设置:

ValidatorFactory factory = Validation.buildDefaultValidatorFactory();
Validator validator = factory.getValidator();
Set<ConstraintViolation<User>> violations = validator.validate(object);
for (ConstraintViolation<User> violation : violations) {
log.error(violation.getMessage()); 
}

要验证一个bean,我们必须首先有一个Validator对象,它是使用ValidatorFactory构造的。

正常验证 Spring 使用@Valid 注释指定的控制器在 DataBinding 阶段自动触发,当请求是 served.All 注册到 DataBinder 的验证器将在该阶段执行。我们无法针对您的情况执行此操作,因此您可以像上面那样手动触发验证。