Как автоматически добавить проверку бина при частичном обновлении PATCH Spring Boot MVC - PullRequest
1 голос
/ 15 мая 2019

Как все мы знаем, существует большая проблема с частичным обновлением сущности.Поскольку автоматическое преобразование строк 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> {

}

С помощью этого решения мы устраняем проблему, связанную с частичным обновлением.

Но здесь возникает другая проблема: мы теряем автоматическое добавление проверки бинов.

То естьВ первом случае проверки достаточно, чтобы добавить одну аннотацию @Valid:

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

Но мы не можем сделать то же самое, когда выполняем десериализацию вручную.

Мой вопрос: есть ли способ включить автоматическую проверку для второго случая?Или, может быть, есть другие решения, которые позволяют вам использовать магию Spring Boot для проверки бинов.

1 Ответ

1 голос
/ 17 мая 2019

Вам нужна не обычная проверка, которая может быть достигнута с помощью ручного вызова валидатора. Теперь перейдем к ручному маршруту и ​​настроим программно:

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

Чтобы проверить бин, мы должны сначала иметьобъект Validator, созданный с использованием ValidatorFactory.

Обычные проверки на контроллерах Spring, указанных с аннотациями @Valid, запускаются автоматически во время фазы привязки данных, когда подается запрос. Все валидаторы, зарегистрированные в DataBinder, будут выполняться вэтот этап.Мы не можем сделать это для вашего случая, поэтому вы можете вручную запустить проверку, как указано выше.

...