这是我previous question的扩展。我实施了Dennis R的回答并使用了hibernate-validator
。有没有办法要求在json请求中指定一个或另一个字段,但不能同时指定两个字段?从我之前的帖子中,在Request类中,我希望用户传入id或代码,但不能同时传递。
我发现this resource对我来说可能是正确的解决方案,但我并不完全了解那里发生的事情,为什么会这样,而且坦率地说这看起来完全过于冗长。这是唯一的方法吗?
答案 0 :(得分:7)
正如我之前评论过Nicko's来自here的回答,您可以使用以下代码实现您想要的效果:
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Constraint(validatedBy = FieldMatchValidator.class)
public @interface FieldMatch {
String message() default "something is wrong!";
Class<?>[] groups() default {};
Class<? extends Payload>[] payload() default {};
/**
* @return The first field
*/
String first();
/**
* @return The second field
*/
String second();
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@interface List {
FieldMatch[] value();
}
public static class FieldMatchValidator implements ConstraintValidator<FieldMatch, Object> {
private String firstFieldName;
private String secondFieldName;
@Override
public void initialize(FieldMatch fieldMatch) {
firstFieldName = fieldMatch.first();
secondFieldName = fieldMatch.second();
}
public boolean isValid(Object object, ConstraintValidatorContext constraintContext) {
try {
final Object firstObj = getProperty(object, firstFieldName);
final Object secondObj = getProperty(object, secondFieldName);
if(firstObj == null && secondObj == null || firstObj != null && secondObj != null) {
return false;
}
} catch (final Exception ignore) {
// ignore
}
return true;
}
private Object getProperty(Object value, String fieldName) {
Field[] fields = value.getClass().getDeclaredFields();
for (Field field : fields) {
if (field.getName().equals(fieldName)) {
field.setAccessible(true);
try {
return field.get(value);
} catch (IllegalAccessException e) {
e.printStackTrace();
}
}
}
return null;
}
}
}
用法:
@FieldMatch.List({
@FieldMatch(first = "name", second = "people"),
@FieldMatch(first = "age", second = "abc")
})
public class Foo {
private String name;
private List<String> people;
private int age;
private Boolean abc;
}
唯一的区别是你不想检查内容是否相等,只是一个字段为空而另一个字段为空。
修改强>
要通过注释获取ExceptionHandler上的对象,只需将异常包装在一个自定义的对象周围,然后在抛出它时传递该对象,即:
public class CustomException extends Exception {
private String message;
private Object model;
public CustomException(String message, Object model) {
super(message);
this.model = model;
}
public Object getModel() {
return model;
}
}
有了这个,你可以简单地得到它:
@ExceptionHandler(CustomException.class)
public ModelAndView handleCustomException(CustomException ex) {
Object obj = ex.getModel();
//do whatever you have to
}
答案 1 :(得分:1)
您可以使用群组作为替代方案。例如,这是Request.java
:
public class Request {
public interface IdOrCodeValidationGroup {}
@NotNull
@NotEmpty
private String id;
@Digits(integer=4, fraction=0)
private double code;
@NotNull
@NotEmpty
private String name;
@AssertTrue(groups = IdOrCodeValidationGroup.class)
private boolean idOrCodeFilled;
public Request(String id, double code, String name) {
this.id = id;
this.code = code;
this.name = name;
}
public boolean isIdOrCodeFilled() {
if (id == null && code > 0) {
idOrCodeFilled = true;
} else if (id != null && code == 0) {
idOrCodeFilled = true;
} else idOrCodeFilled = false;
return idOrCodeFilled;
}
}
然后使用这样的验证器:
@Test
public void testValidation() {
// Of course all of this valid. No group at all.
final Request request = new Request("ID-001", 111, "Data 1");
final Set<ConstraintViolation<Request>> fails = this.validator.validate(request);
Assert.assertTrue(fails.isEmpty());
}
@Test
public void testValidationWithGroup() {
// We use "IdOrCodeValidationGroup.class" group, thus this is invalid.
Request request = new Request("ID-001", 111, "Data 1");
Set<ConstraintViolation<Request>> fails = this.validator.validate(request, IdOrCodeValidationGroup.class);
Assert.assertFalse(fails.isEmpty());
// Lets make one of constraint true; In this case, we set code = 0.
request = new Request("ID-002", 0, "Data 2");
fails = this.validator.validate(request, IdOrCodeValidationGroup.class);
// Passed!
Assert.assertFalse(fails.isEmpty());
}
Here is功能齐全的示例代码。 (别忘了结账&#39; so-36365734&#39;分支机构)。关于Bean Validation Group的this is Official Documentation。
HTH。
答案 2 :(得分:0)
我的天啊。链接的引用看起来不必要地复杂。有一个注释:
@org.hibernate.annotations.Check
我经常遇到同样的情况,我想要执行这种类型的验证,我有一个或另一个字段,或者我有两个或两个都没有...
@Entity
@org.hibernate.annotations.Check(constraints = "(field1 IS NULL OR field2 IS NULL) AND (field1 IS NOT NULL OR field2 IS NOT NULL)")
public class MyEntity{
String field1;
Double field2;
}
这将在DB中创建一个检查约束,它将强制执行约束。它将验证从Hibernate和您的代码转移到DB(这也将阻止任何在您的休眠配置之外访问您的数据库的应用程序破坏此约束)。
此注释的创建不会自动在数据库上执行约束的创建,但是如果/当您创建约束时,它也会通知hibernate它。
在Postgres中,此约束如下所示: ALTER TABLE my_entity ADD CONSTRAINT my_entity_check CHECK((field1 IS NULL或field2 IS NULL)AND(field1 IS NOT NULL或field2 IS NOT NULL));
如果您在生成确切的SQL时遇到问题,请创建注释,然后允许hibernate针对空数据库自动生成数据库模式,它将显示正确的SQL。但是使用注释,hibernate也知道约束,因此如果允许hibernate为任何自动化测试生成模式等,都可以自动生成...
答案 3 :(得分:0)
第一个重要提示:使用JSR-303验证时,您的实体应始终使用包装类型(例如Integer
,Double
等)而不是原语(例如{ 所有字段的{1}},int
等) - 请参阅here。然后,可以使用double
对要验证的实体进行注释,以将其标记为自定义验证:
RequestCheck
@RequestCheck
public final class Request {
//... Use Double, *not* double.
private Double code;
// ...
}
注释界面应如下所示:
RequestCheck
以上引用了自定义JSR-303验证器类:
/**
* Request entity validator.
*/
@Target({ElementType.TYPE })
@Retention(RetentionPolicy.RUNTIME)
@Constraint(validatedBy = RequestValidator.class)
@Documented
public @interface ImpCheck {
Class<?>[] groups() default {};
String message() default "";
Class<? extends Payload>[] payload() default {};
}
答案 4 :(得分:0)
您可以使用指定可选字段的JSON模式,然后根据模式验证传入的JSON。
可以在此答案json schema how do i require one field or another or one of two others but not all of them?
中找到有关架构外观的线索 中提供了有关如何应用JSON模式验证的方法