如何在部分更新 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 的验证器将在该阶段执行。我们无法针对您的情况执行此操作,因此您可以像上面那样手动触发验证。
众所周知,实体的部分更新存在很大问题。由于从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 的验证器将在该阶段执行。我们无法针对您的情况执行此操作,因此您可以像上面那样手动触发验证。