Spring MVC + Hibernate: стратегии проверки данных

Мы все знаем, что Spring MVC хорошо интегрируется с Hibernate Validator и JSR-303 в целом. Но Hibernate Validator, как сказал кто-то, является чем-то только для Bean валидации, а это значит, что более сложные проверки должны быть перенесены на уровень данных. Примеры таких валидаций: уникальность бизнес-ключа, зависимость внутри записей (что обычно указывает на проблемы проектирования БД, но все мы живем в несовершенном мире). Даже простые проверки, такие как длина строкового поля, могут управляться некоторым значением DB, что делает невозможным использование Hibernate Validator.

Итак, мой вопрос: есть ли что-то Spring или Hibernate или JSR предлагает выполнить такие сложные проверки? Есть ли определенный шаблон или часть технологии для выполнения такой проверки в стандартной настройке Controller-Service-Repository на основе Spring и Hibernate?

ОБНОВЛЕНИЕ: Позвольте мне уточнить. Например, существует форма, которая отправляет запрос сохранения AJAX в контроллер save. Если возникает какая-либо ошибка проверки - либо простая, либо "сложная" - мы должны вернуться в браузер с помощью некоторого json, указывающего на проблемное поле и связанную с ним ошибку. Для простых ошибок я могу извлечь поле (если есть) и сообщение об ошибке из BindingResult. Какую инфраструктуру (возможно, конкретные, а не специальные исключения?) Вы бы предложили для "сложных" ошибок? Использование обработчика исключений не кажется мне хорошей идеей, потому что разделение одного процесса валидации между методом save и @ExceptionHandler делает вещи сложными. В настоящее время я использую какое-то специальное исключение (например, ValidationException):

public @ResponseBody Result save(@Valid Entity entity, BindingResult errors) {
    Result r = new Result();
    if (errors.hasErrors()) {
        r.setStatus(Result.VALIDATION_ERROR);     
        // ...   
    } else {
        try {
            dao.save(entity);
            r.setStatus(Result.SUCCESS);
        } except (ValidationException e) {
            r.setStatus(Result.VALIDATION_ERROR);
            r.setText(e.getMessage());
        }
    }
    return r;
}

Можете ли вы предложить более оптимальный подход?

Ответ 1

Да, есть старый добрый шаблон Java Выброс исключений.
Spring MVC очень хорошо его интегрирует (для примеров кода вы можете сразу перейти ко второй части моего ответа).

То, что вы называете "сложными проверками", на самом деле является исключением: ошибка единства бизнес-ключа, ошибки низкого уровня или БД и т.д.


Напоминание: что такое проверка в Spring MVC?

Валидация должна произойти на уровне презентации. В основном это касается проверки представленных полей формы.

Мы могли бы классифицировать их на два вида:

1) Световая валидация (с проверкой JSR-303/Hibernate): проверка того, что отправленное поле имеет заданный @Size/@Length, что это @NotNull или @NotEmpty/@NotBlank, проверяя, что он имеет формат @Email и т.д.

2) Тяжелая проверка или комплексная проверка - это больше о частных случаях проверки полей, таких как проверка поперечного поля:

  • Пример 1: Форма имеет fieldA, fieldB и fieldC. По отдельности каждое поле может быть пустым, но по крайней мере один из них не должен быть пустым.
  • Пример 2: если поле userAge имеет значение менее 18, поле responsibleUser не должно быть нулевым, а responsibleUser возраст должен быть старше 21.

Эти проверки могут быть реализованы с помощью Spring реализаций валидатора или пользовательских аннотаций/ограничений.

Теперь я понимаю, что со всеми этими возможностями проверки, плюс тот факт, что Spring вообще не навязчив и позволяет делать все, что угодно (к лучшему или к худшему), возникает соблазн использовать "валидационный молот" "для чего-то, что смутно связано с обработкой ошибок". И это сработает: только с проверкой, вы проверяете каждую возможную проблему в своих валидаторах/аннотации (и вряд ли производите исключение в нижних слоях). Это плохо, потому что вы молитесь, чтобы вы подумали обо всех случаях. Вы не используете исключения Java, которые позволят вам упростить вашу логику и уменьшить вероятность ошибки, забыв проверить, что что-то было ошибкой.

Итак, в мире Spring MVC не следует проверять достоверность (то есть проверку подлинности UI) для более низких исключений, таких как исключения службы или Исключения БД (ключевое единство и т.д.).


Как обрабатывать исключения в Spring MVC удобным способом?

Некоторые люди думают: "О боже, поэтому в моем контроллере мне нужно будет проверять все возможные проверенные исключения один за другим и думать о ошибке сообщения для каждого из них? НЕТ ПУТЬ!". Я один из тех людей.: -)

В большинстве случаев просто используйте некоторый общий проверенный класс исключений, который расширит все ваши исключения. Затем просто обработайте его в своем Spring MVC-контроллере с @ExceptionHandler и общим сообщением об ошибке.

Пример кода:

public class MyAppTechnicalException extends Exception { ... }

и

@Controller
public class MyController {

    ...

    @RequestMapping(...)
    public void createMyObject(...) throws MyAppTechnicalException {
        ...
        someServiceThanCanThrowMyAppTechnicalException.create(...);
        ...
    }

    ...

    @ExceptionHandler(MyAppTechnicalException.class)
    public String handleMyAppTechnicalException(MyAppTechnicalException e, Model model) {

        // Compute your generic error message/code with e.
        // Or just use a generic error/code, in which case you can remove e from the parameters
        String genericErrorMessage = "Some technical exception has occured blah blah blah" ;

        // There are many other ways to pass an error to the view, but you get the idea
        model.addAttribute("myErrors", genericErrorMessage);

        return "myView";
    }

}

Просто, быстро, легко и чисто!

В те моменты, когда вам нужно отображать сообщения об ошибках для некоторых определенных исключений или когда у вас не может быть общего исключения верхнего уровня из-за плохо разработанной устаревшей системы, которую вы не можете изменить, просто добавьте другие @ExceptionHandler s.
Еще один трюк: для менее загроможденного кода вы можете обрабатывать несколько исключений с помощью

@ExceptionHandler({MyException1.class, MyException2.class, ...})
public String yourMethod(Exception e, Model model) {
    ...
}

Нижняя строка: когда использовать проверку? когда использовать исключения?

  • Ошибки из UI = validation = средства проверки (аннотации JSR-303, пользовательские аннотации, Spring валидатор)
  • Ошибки из нижних слоев = исключения

Когда я говорю "Ошибки от пользовательского интерфейса", я имею в виду "пользователь ввел что-то не так в его форме".

Ссылки: