親バカエンジニアのナレッジ帳

webのエンジニアをやっており、日頃の開発で詰まったことや書き残しておきたいことを載せています。

Spring BootでBean Validation (1) @GroupSequenceで順番にチェック


Spring Bootでは(Spring Boot以外にもBean Validation使ってるやつ全部ですが)、Serializableクラスに対して
「org.hibernate.validator.constraints」や「javax.validation.constraints」を使って、
@NotEmptyや@Sizeといったようにアノテーションを使うだけでバリデーションを行うことができます。

入力値を見て、アノテーションの制約に違反していれば勝手にエラーを検知してくれるのですが、
(処理を途中で止めたりエラー内容を画面に表示させるのは手動でやらなければいけません。)
1つのフィールドに対して複数のアノテーションを付与した場合はすべてのエラーチェックが走り、
エラーメッセージも複数出力される可能性があります。

例えば、以下のようなコードがあった場合、

@NotEmpty
@Size(min = 1)
private String var;

フォームでnameがvarの入力欄に何も入れずにPOSTをした場合、
「@NotEmpty」と「@Size(min = 1)」の両方の制約に違反しているために、

・入力値が空ですよ、何か入力してください。
・入力値は1文字以上入れないとダメですよ、1文字以上入力してください。

的な2つのエラーメッセージが出力されます。
しかもチェックされる順番が上から(つまりは@NotEmpty → @Size(min = 1))ではなくランダムな順番でチェックされます。
アノテーションの順番と条件を考えて設定すれば、1つだけしか出ないような設定方法もあると思いますが、
できれば1つずつ上からチェックできるようにした方が楽ですよね。
そんな時は@GroupSequenceというアノテーションを使えばいいのです。

バリデーションを行うクラス内に以下のようにinterfaceを設定します。
※名前は任意です。今回はわかりやすいようにGroupの後に数字を入れてます。

public interface Group1 {}
public interface Group2 {}
public interface Group3 {}

@GroupSequence({Group1.class,Group2.class,Group3.class})
public interface All {}

そしてアノテーションのパラメータにgroupsを追加し、処理をする順番にインターフェース名を入力します。
※アノテーションクラスを自作した場合は、groups()のメンバ関数を定義することを忘れないでください。

@NotEmpty(groups = Group1.class)
@Size(min = 1, groups = Group2.class)
@URL(groups = Group3.class)
 ・
 ・
 ・

また、バリデーションを行う時はコントローラーのPOSTメソッド内に

@Validated TestForm form

のような記述をしているはずですが、
その箇所を

@Validated(TestForm.All.class) TestForm form

のようにしてください。
これでAllのインターフェースが呼ばれ、
Group1〜Group3まで順番に処理がされます。

ところで、
毎回SerializableクラスにGroup1とかみたいなインターフェースを書くのかよ、
と思われた方がいると思います。

そんな時は、インターフェースの宣言だけ外出ししてしまいましょう。

package テストパッケージ;
 
public class Groups {
    public interface Group1 {}
    public interface Group2 {}
    public interface Group3 {}
}

のようなクラスを用意し、このクラスを以下のようにインポートすればどこでもこのインターフェースを呼び出すことができます。

import テストパッケージ.Groups.*;

ただし、

@GroupSequence({Group1.class,Group2.class,Group3.class})
public interface All {}

の記述は外出しができませんので、Serializableクラス内に書いてください。

とまぁ、私はこんな感じでGroupSequenceを使っていますが、もっと楽な方法があるかもしれませんね。