您当前的位置: 首页 >  hibernate

石头wang

暂无认证

  • 0浏览

    0关注

    295博文

    0收益

  • 0浏览

    0点赞

    0打赏

    0留言

私信
关注
热门博文

jsr303,hibernate validator的注解使用教程和爬坑(一篇懂)

石头wang 发布时间:2019-12-25 21:22:35 ,浏览量:0

一、前言

本文的目的是摸清jsr303注解的坑。

我对jsr303的态度是:能不用就不用。

原因: 1、坑多。不是所有开发人员都会花时间去摸细节 2、满足不了复杂校验。花拳绣腿,不能满足实际开发中复杂的校验场景,与其部分校验用jsr303,部分自写,还不如全部自写。

本文基于 6.0.17.Final 进行研究。其他版本是否会跟我的版本有出入? 未知(不过推测不可能有大的变化,不然以前用这个注解在升级后校验规则的行为变了,那就坑了)

二、使用注意
  • @Valid 和 @Validated,用@Valid,别用hibernate的注解,用javax的

    有些开发偶尔用@Valid,偶尔用@Validated,一个是javax的,一个是spring的注解。别这样,虽然@Validated有它独特的group功能,但是
    
  • 如下注意点

    // 不用@valid不检查
    @PostMapping("/test1")
    public String test1(T01_NoMessage dto) {
    	return "ok";
    }
    
    // 用了@Valid才检查
    @PostMapping("/test1")
    public String test1(@Valid T01_NoMessage dto) {
    	return "ok";
    }
    
    // 传JSON 可以生效
    @PostMapping("/2/test0")
    public Object test0(@Valid @RequestBody T50_Json dto) {
    	return dto;
    }
    
    // 不是dto类不生效(其他奇奇怪怪的入参不生效)
    @PostMapping("/test1")
    public String test1(@Valid List dto) {
    	return "ok";
    }
    
    // 不生效
    @PostMapping("/3/test1")
    public Object test1(@Valid @NotBlank String name) {
    	return "name:" + name;
    }
    
    
    // 生效:除了dto外还有其他字段、路径参数
    @PostMapping("/3/test4/{id}")
    public Object test4(
    	@PathVariable("id") int id,
    	String other,
    	@Valid T66_NotOnly dto
    ) {
    	return dto;
    }
    
    // 生效:传JSON,除了dto外,还有other。(PS:这个other怎么传参的? 通过`?other=xxx`即可)
    @PostMapping("/3/test5")
    public Object test5(
    	String other,
    	@Valid @RequestBody T66_NotOnly dto
    ) {
    	return dto;
    }
    
  • 嵌套类必须也用@Valid,否则不生效。详细的嵌套问题在后面单独讨论

    // A类中有B类,这就叫做内嵌
    
  • 正则表达式单和双斜杠都可以

    // 可以,两个等价的(建议用一个斜杠的,因为感觉比较正统)
    @Pattern(regexp = "^[\u4e00-\u9fa5a-zA-Z0-9_]{4,64}$")
    @Pattern(regexp = "^[\\u4e00-\\u9fa5a-zA-Z0-9_]{4,64}$")
    
  • Mockmvc测试controller的方法,jsr303正常生效;但是如果仅仅方法调用,不生效

    // 被校验
    @Test
    public void test2() throws Exception {
    	mockMvc.perform(MockMvcRequestBuilders.post("/3/test9").param("name", "stone")).andDo(MockMvcResultHandlers.print());
    }
    
    // 不被校验
    @Test
    public void test() {
    T69_SpringTest dto = new T69_SpringTest();
    	dto.setName(null);
    	t03_TestController.test9(dto);
    }
    
    // IDEA 的bug:IDEA自动补全时,会在变量前面生成@Valid注解
    @Test
    public void test() {
      // 这里的 @Valid 是代码自动补全的(IDEA 提示 `Create local variable 'dto'`),为什么会提示这个?
      // 实际上这个 @Valid 的注解,并不会触发拦截。实际上可能是 IDEA 的一个bug
      @Valid T69_SpringTest dto = new T69_SpringTest();
      //dto.setName("");
      dto.setName(null);
      t03_TestController.test9(dto);
    }
    
嵌套问题

注意,x-www-form-urlencorded的传参方式的dto是没有内嵌结构的,因为它是键值对传参,而键值对传参非常扁平,不会有嵌套结构。前端根本是不可能传这么复杂的、有层次结构的参数的。比如对于

public class A{
  private String name;
  private B b;
  private List cList;
}

@PostMapping("/test")
public void post(@Valid A a) {
....
}

要怎么传参?
http://xxx?name=stone&b.name=cassie&cList.name=Pg1&cList.name=Pg2

这样吗? 没见过这么奇怪的传参

所以针对传JSON才有嵌套的说法

嵌套是什么?

嵌套是类中有类。比如A类有成员变量B类,或者B类的集合类,如

public class A {
  private B b;
  private List cList;
}

以下是爬坑,注意点

  • 如何校验嵌套的类

    // 只用@NotNull只能管必填,但不会校验b或cList
    public class A {
      @NotNull
      private B b;
      @NotNull
      private List cList;
    }
    
    // 之用@Valid只管校验,就是传了就校验,不传也行(因为允许null)
    public class A {
      @Valid
      private B b;
      @Valid
      private List cList;
    }
    
    // 如果要校验 "必填且必须校验",则@Notnull和@Valid一起用
    public class A {
      @NotNull
      @Valid
      private B b;
      @NotNull
      @Valid
      private List cList;
    }
    
    顺序

    Jsr303 注解了Controller的方法后,当发起http请求,如果检查不通过,会进入Controller方法吗? 会进入@ControllerAdvice的捕获方法吗?

  • 检测是在调用Controller方法之前,若不通过,是不会进入的。即打在controller方法的断点是不会执行的

  • jsr303检查抛出异常, 是能够在@ControllerAdvice里捕获的

    (打消疑惑:既然检查没进入Controller方法,那抛出异常时也就不会进入@ControllerAdvice的捕获方法里)

三、常用JSR303注解速查 注解可以用在什么类型的字段上作用@NotBlank只接受CharSequence必填。非空串 “”,非trim后是空串的如 " "@NotEmpty只接受CharSequence,Collection,Map,Array必填。字串类型时不能是空串 “”,但可以是 " ";集合类型时必须size大于0@Notnullany types(即接受所有类型)必填@Max只接受BigDecimal、BigInteger、byte、short、int、long、Byte、Short、Integer、Long;不支持double/float以及它的包装类不必填,但填了就得符合@Min同@Max不必填,但填了就得符合@Size只接受CharSequence,Collection,Map,Array不必填,但填了就得符合@Pattern只接受CharSequence不必填,但填了就得符合@Email只接受CharSequence不必填,但填了就得符合@Future一系列日期的格式,详见补充1不必填,但填了就得符合@FutureOrPresent同@Future不必填,但填了就得符合@Past同@Future不必填,但填了就得符合@PastOrPresent同@Future不必填,但填了就得符合
  • 补充1:

    * java.util.Date
    * java.util.Calendar
    * java.time.Instant
    * java.time.LocalDate
    * java.time.LocalDateTime
    * java.time.LocalTime
    * java.time.MonthDay
    * java.time.OffsetDateTime
    * java.time.OffsetTime
    * java.time.Year
    * java.time.YearMonth
    * java.time.ZonedDateTime
    * java.time.chrono.HijrahDate
    * java.time.chrono.JapaneseDate
    * java.time.chrono.MinguoDate
    * java.time.chrono.ThaiBuddhistDate
    
四、一次生产爬坑和思考

记录一次生产中使用jsr303的疑惑和爬坑事件。

有一次产品说有个字段以前必填的,要改成选填。代码如下

@PostMapping("/2/test66")
public Object test66(@Valid @RequestBody T200_ProdSizeProblem dto) {
  return dto;
}


@Data
public class T200_ProdSizeProblem {
    @NotBlank
    private String name;

    @Valid
    private List subList;
}


@Data
public class T200_ProdSizeProblemSub {
    @NotBlank(message = "参数列描述不能为空")
    @Size(min = 1, max = 50, message = "最大长度为50")
    private String descr;
}

产品要的需求就是 descr 字段可选,于是我就把 @NotBlank(message = "参数列描述不能为空") 去掉,因为对于 @Size,我是知道它是 “非必填,但是如果填了长度就得符合”。所以我想你可以不填,但是填了就得按照我的长度要求,这样比较好。

但是出bug啦,界面上不填描述,但还是收到后端爆出的错误。

为什么啦? 之前不是说@Size 可以不填,但是填了才会进行size的校验的吗?

一排查,原来是描述不填的时候,desc还是传了空串。

作为后端很无奈,又不能要求前端同学 “在用户不填描述时不要出现descr字段,别传空串”,因为前端这部分逻辑比较复杂了所以不要求前端改,所以只能后端去掉@Size校验

PS:上述代码暴露出一个问题,以前的程序员根本就不知道要加上@NotNull,这个字段是一定要传的,并且size肯定会大于0,所以改成

@Data
public class T200_ProdSizeProblem {
    @NotBlank
    private String name;

  	@NotEmpty
    @Valid
    private List subList;
}

这就是我说的,很多同事根本就不清楚这些jsr303注释背后的细节和坑 !!!

关注
打赏
1663722529
查看更多评论
立即登录/注册

微信扫码登录

0.0427s