Проверять связанные данные при сопоставлении модели представления модели базы данных в Spring MVC

Я работаю над java spring mvc-приложением и задаю важный вопрос о сопоставлении объектов модели объектов с объектами модели базы данных. В нашем приложении используется dozer mapper для этой цели.

Предположим, что у меня есть модель Person и BaseInformation. Модель BaseInformation предназначена для общих данных, которые могут использоваться во всех других моделях, например гендерные группы, цвета, единицы измерения,....

BaseInformation:

class BaseInformation{
   private Long id;
   private String category;
   private String title;
}

Это может иметь таблицу базы данных следующим образом:

Id | Category | Title 
-------------------------
1  | "gender" | "male"
2  | "gender" | "female"
3  | "color"  | "red"
4  | "color"  | "green"
...

Это часть моей модели Person:

public class Person{
     ...
     private BaseInformation gender;
     ...
}

И это часть моего RegisterPersonViewModel

public class RegisterPersonViewModel{
    ...
    private Integer gender_id;
    ...
}

В представлении зарегистрировать человека у меня есть <select>, который заполняется из BaseInfromation с категорией gender. Когда пользователь передает эту форму, запрос ajax отправляет методам контроллера следующим образом:

@RequestMapping("/person/save", method = RequestMethod.POST, produces = "application/json")
public @ResponseBody Person create(@Valid @RequestBody RegisterPersonViewModel viewModel) throws Exception {

    //Mapping viewModel to Model via dozer mapper
    //and passing generated model to service layer
}

Теперь, вот мой вопрос:

Пользователь может изменить value гендерного combobox в представлении вручную (например, установить значение цвета вместо пола) и отправить недопустимые связанные данные в метод контроллера. Отображение карты картеля бульдозераМодель модели, и эти недопустимые данные проходят через уровень доступа к данным и сохраняются в базе данных. Другими словами, Недействительные данные могут сохраняться в базе данных без какого-либо контроля. Я хочу знать, как лучше всего управлять реляционными данными с минимальным кодом.

Ответ 1

Класс BaseInformation слишком общий: пол не имеет ничего общего с цветом. Вам нужно разбить его. Это случай "One True Lookup Table" и даже упоминается в Wikipedia:

В мире баз данных разработчикам иногда возникает попытка обойти СУРБД, например, путем хранения всего в одной большой таблице с тремя столбцами, помеченными как идентификатор объекта, ключ и значение.

... который соответствует вашему идентификатору, категории и названию.

В то время как эта модель объекта-атрибута-значения позволяет разработчику выйти из структуры, навязанной базой данных SQL, она теряет все преимущества, [ 1], поскольку вся работа, которая может быть эффективно выполнена СУРБД, вместо этого принудительно применяется к приложению. Запросы становятся намного более запутанными, [2] индексы и оптимизатор запросов больше не работают эффективно, а ограничения на достоверность данных не применяются.

Часть, выделенная жирным шрифтом, описывает проблему, с которой вы хорошо справляетесь.


Вы должны переместить разные категории в свои классы и таблицы. Для пола перечисление достаточно хорошее:

public enum Gender {
    Female, Male, Unknown, Unspecified
}

И используйте его в классе Person следующим образом:

public class Person {
    ...
    private Gender gender;
    ...
}

Если вы используете привязку данных Spring для преобразования входных данных в объекты Java, могут использоваться только значения, указанные в перечислении Gender, и дальнейшие проверки не требуются.

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

Ответ 2

Вы должны проверить модель представления, и перед сохранением вы также можете проверить объект. Кажется, вы используете Bean Validation, потому что вы используете аннотацию @Valid в своем методе контроллера. Поэтому просто добавьте ограничения проверки для свойств модели. Например:

public class RegisterPersonViewModel {
    ...
    @Min(0) @Max(1)
    private Integer gender_id;
    ...
}

Но причина, если кто-то отправляет 1 и означает цвет зеленый, а не женский, вы теряетесь. Поэтому было бы гораздо лучше использовать перечисление для пола и других свойств, если это возможно.

Bean Валидация также может использоваться для ваших объектов базы данных (объектов).

Ответ 3

Вы можете использовать Hibernate Validator 5.x и API проверки достоверности 1.1 в Spring механизм проверки.
Новая версия Hibernate Validator может проверять ваш постоянный bean как аргумент метода и бросать javax.validation.ConstraintViolationException на ваш контроллер.

@Inject MyPersonService myPersonService;

@RequestMapping("/person/save", method = RequestMethod.POST, produces = "application/json")
public @ResponseBody Person create( @RequestBody RegisterPersonViewModel viewModel) throws Exception {

   Person person = ...; // map
   try{
      myPersonService.persist(person);
   }catch (ConstraintViolationException cvex) {
      for (ConstraintViolation cv : cvex.getConstraintViolations()) {
         String errorMessage = cv.getMessage();
      }
   }

}

службы:

@Service
public class MyPsersonService{

   public void persist(@Valid Person person){
       // do persist here without checking related data
   }
}

pserson:

public class Person{
   ...
   @ValidCategory("gender")
   private BaseInformation gender;
   ...
}

ValidCategory:

@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target({ FIELD, METHOD, PARAMETER, ANNOTATION_TYPE })
@Constraint(validatedBy = CategoryValidator.class)
public @interface ValidCategory {

   String message() default "{info.invalid}";
   Class<?>[] groups() default { };
   Class<? extends Payload>[] payload() default { };
   String value(); // the category name goes here

   @Target({ FIELD, METHOD, PARAMETER, ANNOTATION_TYPE })
   @Retention(RUNTIME)
   @Documented
   @interface List {
      ValidCategory[] value();
   }
}

CategoryValidator:

import javax.validation.ConstraintValidator;
import javax.validation.ConstraintValidatorContext;

public class CategoryValidator implements ConstraintValidator<ValidCategory, BaseInformation> {

   private String category;
   @Override
   public void initialize(ValidCategory validCat) {
   this.category = validCat.value();
   }

   @Override
   public boolean isValid(BaseInformation baseInfo, ConstraintValidatorContext cvc) {
      if(!this.category.equals(baseInfo.getCategory()){
         addError(cvc,"you've entered invalid category!");
         return false;
      }else{
         return true;
      }
   }

   private void addError(ConstraintValidatorContext cvc, String m){
     cvc.buildConstraintViolationWithTemplate(m).addConstraintViolation();
   }
}

определите два beans в applicationContext. Spring автоматически обнаружит их (еще один вопрос).

<bean class="org.springframework.validation.beanvalidation.LocalValidatorFactoryBean"/>
<bean class="org.springframework.validation.beanvalidation.MethodValidationPostProcessor"/>

На первый взгляд это не звучит минимальный код, но он настолько опрятен и очищает домен. Это лучшее решение.