Проверять связанные данные при сопоставлении модели представления модели базы данных в 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"/>
На первый взгляд это не звучит минимальный код, но он настолько опрятен и очищает домен. Это лучшее решение.