Spring boot Validation 分組驗證功能

Spring boot Validation 分組驗證功能

2021, Oct 21    

上篇介紹到 spring boot 簡單好用的參數驗證機制,但一般 API 常會分成 Create 跟 Update 兩種功能,兩種功能提供的參數大致上都會相同但會有一些驗證上的差異,如果要為了分開驗證去多寫一個類別也稍嫌有點麻煩且不好維護,所以 Validation 就提供了分組驗證的機制,下面介紹一下要如何來使用

沒看過前幾篇的可以點這邊:

分組驗證設定

分組驗證上呢我們要先宣告幾個不同的 interface 來用作區別,作用有點像 Enum,然後在各個 annotation 中指定到 groups 的參數中,這樣就可以指定在該 group 的時候會去做這個驗證,也可以指定多個 group 給單一個 annotation

@Data
public class UserDto {
    @NotBlank(groups = Create.class)
    @Size(max = 255, message = "{validation.user.name.length.max}")
    private String name;

    @NotBlank(groups = Create.class)
    @Null(groups = Update.class)
    private String password;

    @NotBlank(groups = Create.class)
    @Size(max = 255, message = "{validation.user.email.length.max}", groups={Create.class, Update.class})
    @Email()
    private String email;

    public interface Create{}
    public interface Update{}
}

分組驗證啟用

設定完成之後呢只要在需驗證的參數前改用 @Validated(UserDto.Create.class) 這種形式去指定這次驗證的組別就可以了,需注意 @Valid 不具備帶入 group 的功能,只能用 @Validate@Validate 本身也是 @Valid 的封裝,基本上功能是一樣的

@PostMapping("")
@ResponseStatus(HttpStatus.CREATED)
public void createUser(@RequestBody @Validated(UserDto.Create.class) UserDto userDco){
}

@PutMapping("/{userId}")
@ResponseStatus(HttpStatus.NO_CONTENT)
public void modifyUser(@PathVariable("userId") Long userId, @RequestBody @Validated(UserDto.Update.class) UserDto userDuo) {
}

預設組別驗證

在上面的例子中會發現 @Email@Size 是沒有被設定組別的,預想情況應該是每個組別都會去執行驗證,但實際測試後就會發現其實是都不執行,對於 Validation 的機制來說他們其實都屬於一個 Default 的組別,所以這邊我們可以做點更改

@Data
public class UserDto {
    ...

    public interface Create extend Default{}
    public interface Update extend Default{}
}

讓用來分組的 interface 去繼承 Default,就能讓這些組別去套用預設沒有被分組的 annotation

巢狀驗證

如同上篇也有提到的巢狀驗證,如果在分組的情況下巢狀驗證該怎麼進行呢,其實完全一樣,不需更改任何地方,只要加上 @Valid 就好,也許在嘗試的時候會發現 @Validated 其實是不允許被寫在 class 的 field 層的所以也只能用 @Valid 來標記

@Data
public class UserDto {
    @Valid
    private UserProperty property;

    public interface Create{}
    public interface Update{}
}

@Data
public class UserProperty{
    @NotBlank(groups = UserDto.Create.class)
    @Size(max = 255)
    private String name;

    @NotBlank(groups = UserDto.Create.class)
    @Null(groups = Update.class)
    private String password;

    @NotBlank(groups = UserDto.Create.class)
    @Size(max = 255, groups={UserDto.Create.class, UserDto.Update.class})
    @Email()
    private String email;
}

而當巢狀驗證設定好之後,從最外層傳入的 group 便會一路傳下去,到最後要驗證的屬性就會按照該 group 去做驗證,不過必須注意設定的 group 一定要是同一個來源,使用上其實相當直覺

@PostMapping("")
@ResponseStatus(HttpStatus.CREATED)
public void createUser(@RequestBody @Validated(UserDto.Create.class) UserDto userDco){
}

結語

這個 Validation 的機制設計上實在具有巧思,用 interface 的方式傳入而不是單傳字串更能分辨不同 dto 間的驗證,最棒的是還會有 IDE 的語法提示