我正在研究一个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映射到模型并且此无效数据通过 数据访问层 并坚持数据库。换句话说, 无效数据无需任何控制即可保存到数据库中。 我想知道最好的控制方式 关系数据 用最少的代码。
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
可以使用枚举,不需要进一步检查。
对于颜色,如果在运行时不需要更改颜色,则可以类似地使用枚举,否则可以使用类。
您必须验证视图模型,在持久化之前,您还可以验证实体。
您似乎正在使用Bean验证,因为您正在使用 @Valid
控制器方法中的注释。因此,只需为模型属性添加验证约束即可。例如:
public class RegisterPersonViewModel {
...
@Min(0) @Max(1)
private Integer gender_id;
...
}
但是,如果有人发送1并且意味着颜色为绿色而不是女性则会丢失。因此,如果可能的话,对性别和其他属性使用枚举会好得多。
Bean Validation也可用于您的数据库对象(实体)。
您可以使用 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"/>
乍一看它听起来不是最小的代码,但它是如此整洁并清理领域。这是最好的解决方案。