问题 在Spring MVC中将视图模型映射到数据库模型时验证相关数据


我正在研究一个java spring mvc应用程序并且有一个重要的问题 将视图模型对象映射到数据库模型对象。 我们的应用使用 推土机映射器 为了这个目的。

假设我有一个  模特和 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"
...

这是我的一部分 人物模型:

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

这是我的一部分 RegisterPersonViewModel

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

在里面 登记人 看来,我有一个 <select> 从中填补 BaseInfromation 同 性别 类别。当用户提交该表单时,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 手动视图中的性别组合框(例如设置颜色而不是性别)并发送 无效的相关数据 控制器的方法。推土机映射器将viewModel映射到模型并且此无效数据通过 数据访问层 并坚持数据库。换句话说, 无效数据无需任何控制即可保存到数据库中。 我想知道最好的控制方式 关系数据 用最少的代码。


11082
2018-03-02 10:43


起源

你的意思是 验证 通过检查相关数据? - Dariush Jafari


答案:


BaseInformation类过于通用:性别与颜色无关。你需要分解它。这是“One True Lookup Table”的一个案例,甚至还提到过 维基百科

在数据库世界中,开发人员有时会试图绕过RDBMS,例如将所有内容存储在一个包含三个标记为实体ID,键和值的列的大表中。

...对应于您的ID,类别和标题。

虽然这个实体 - 属性 - 值模型允许开发人员从SQL数据库强加的结构中脱离出来,但它会失去所有的好处, [1] 因为RDBMS可以有效完成的所有工作都被强制转换为应用程序。查询变得更加错综复杂, [2] 索引和查询优化器无法再有效地工作,并且 不强制执行数据有效性约束

粗体部分描述了你很好的问题。


您应该将不同的类别移动到它们自己的类和表中。对于性别而言,枚举就足够了:

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

并在它中使用它 Person 像这样的类:

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

如果您正在使用Spring数据绑定将输入数据转换为Java对象,则仅将指定的值转换为Java对象 Gender 可以使用枚举,不需要进一步检查。

对于颜色,如果在运行时不需要更改颜色,则可以类似地使用枚举,否则可以使用类。


6
2018-03-05 21:24



baseInformation表包含100多个类别。因此,作为您的建议,我们应该创建超过100个表而不是一个baseInformation。对我们庞大的项目来说,这将是非常困难的工作。此外,大约500个模型引用了baseInformation模型,可能会删除它们对baseInformation的引用并添加对100个替代模型的引用。这也是一项非常艰苦的工作。除了将baseInformation拆分为更多表之外,有没有解决我的问题的解决方案? - hamed
@hamed我猜这些类别中的许多不需要表示为表格,而是作为新列,如性别。拆分BaseInformation也不必一次全部发生;你可以一个接一个地切掉它的一部分,现在从性别开始。还有其他解决方案吗?当然,但你手上还有一个巨大的反模式。而且由于您的问题仅涉及性别类别,因此使用我的解决方案并且只将性别转移到自己的类和列上并不是很有效。 - Bewusstsein


您必须验证视图模型,在持久化之前,您还可以验证实体。 您似乎正在使用Bean验证,因为您正在使用 @Valid控制器方法中的注释。因此,只需为模型属性添加验证约束即可。例如:

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

但是,如果有人发送1并且意味着颜色为绿色而不是女性则会丢失。因此,如果可能的话,对性别和其他属性使用枚举会好得多。

Bean Validation也可用于您的数据库对象(实体)。


3
2018-03-09 11:09





您可以使用 Hibernate Validator 5.x. 和 验证API 1.1 在 弹簧验证机制
新版本的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();
   }
}

在applicationContext中定义两个bean。 Spring会自动检测到它们( 另一个问题)。

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

乍一看它听起来不是最小的代码,但它是如此整洁并清理领域。这是最好的解决方案。


1
2018-03-10 05:19