SpringBoot の Validation 簡易メモ

SpringBoot で Validation 使った時のの簡易メモ
良いやり方は模索中。。。

# 実行環境
- Windows11
- Pleiades 2024 Full Edition
- Maven v3.9.6
- SpringFramework Boot v3.3.0
- Java v21
- spring-boot-starter-thymeleaf
- spring-boot-starter-web
- spring-boot-starter-validation

# プロジェクト作成
- Spring Boot DevTools
- Spring Web
- Lombok
- Thymeleaf
- Validation

Validation で追加された依存関係は以下
```xml:pom.xml
		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-validation</artifactId>
		</dependency>
```

# バリデーションするDTO作成

入力フォームで使う変数を持ったオブジェクト
クラス変数にバリデーションをアノテーションで追加

```java:簡単な使用例
@Data
@AllArgsConstructor
@NoArgsConstructor
public class XxxDto {

	@NotNull
	private Integer val1;
	@NotBlank
	private String val2;
}
```

# コントローラでバリデーション適用

コントローラの引数にアノテーション `@Validated` とバリデーション結果をとれる `BindingResult` を追加する
引数の順番は必ず `@Validated` つけたやつの後に `BindingResult` にする
この順番以外だとエラーになるので注意！

```java:簡単な使用例
	@PostMapping("/url_samp")
	public String sample(Model model, @Validated @ModelAttribute XxxDto dto, BindingResult result) {
        // バリデーション確認
        if (result.hasErrors()) {
            // とりあえずエラー内容出力
            result.getFieldErrors().forEach(e -> System.out.println(e.getField() + ":" + e.getDefaultMessage()));
            // 結果を画面に設定
            model.addAttribute("errorResult", result);
            return "sampl";
        }
        // バリデーションOKの時の処理
        // ・
        // ・
        return "sampl";
    }
```

# html でエラー内容表示

一旦、簡易にバリデーション結果から直接、エラー項目とメッセージを取得して表示

```html:簡単な使用例
		<div th:if="${errorResult}">
			<li th:each="err : ${errorResult.fieldErrors}" th:text="${err.field + ':' + err.defaultMessage}"></li>
		</div>
```

# メッセージを変える

メッセージプロパティの元は `hibernate-validator` に置いてある
上書きだったり新しいバリデータ作るときは `resources` の直下に以下のファイルを作る
上書きのときは元々のキーと合わせる必要あり
- ValidationMessages.properties
- ValidationMessages_ja.properties

```properties:簡単な例 ValidationMessages_ja.properties
jakarta.validation.constraints.NotBlank.message        = ないよ。。。blank
jakarta.validation.constraints.NotNull.message         = ないよ。。。null
```

# 独自でバリデーションつくる

標準にはない独自バリデーションが作れる、DBへのチェックなども作れる
クラスは以下の2つを作る
使いたいときは作ったアノテーションクラスをクラス or クラス変数 or 引数に追加するだけであとは標準と同じ！
- アノテーションクラス（クラス、クラス変数、引数につけるアノテーション）
- バリデーションクラス（チェック行うクラス）

あとメッセージの追加を忘れずに
- ValidationMessages.properties
- ValidationMessages_ja.properties


```java:アノテーションクラス SampleValue
package com.example.demo.validator;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

import jakarta.validation.Constraint;
import jakarta.validation.Payload;

// バリデーションクラスを設定
@Constraint(validatedBy = SampleValueValidator.class)
@Target({ ElementType.FIELD, ElementType.METHOD, ElementType.PARAMETER,  ElementType.ANNOTATION_TYPE })
@Retention(RetentionPolicy.RUNTIME)
public @interface SampleValue {
    // メッセージプロパティに設定するキーを使う
	String message() default "{sample.value.message}";

	Class<?>[] groups() default {};

	Class<? extends Payload>[] payload() default {};
}
```

```java:バリデーションクラス SampleValueValidator
package com.example.demo.validator;

import java.util.List;
import java.util.Map;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.jdbc.core.JdbcTemplate;

import jakarta.validation.ConstraintValidator;
import jakarta.validation.ConstraintValidatorContext;

// ConstraintValidator<アノテーションクラス設定, バリデーションする型設定> の実態クラスにする
public class SampleValueValidator implements ConstraintValidator<SampleValue, String> {

    // バリデーションの実装, バリデーションエラーの時は false を返す
    @Override
    public boolean isValid(String value, ConstraintValidatorContext context) {
        return "Sample".equals(value);
    }
}
```

```properties:ValidationMessages.properties
sample.value.message = Let's make it Sample
```

```properties:ValidationMessages_ja.properties
sample.value.message = Sample にしましょう
```

# バリデーションの実行制御(コントローラごとに分けるパターン)

入力フォームで使うオブジェクトのクラス変数に追加したバリデーションでアクションごとに実行制御できる
実行制御するときはアノテーションクラスの `groups` を使う
入力フォームで使うオブジェクトのクラス変数のアノテーションのところに `groups={ クラス... }` を設定
コントローラの引数のアノテーションのところに `@Validated({ クラス... })` を設定
クラスを合わせる必要がある(クラス自体は作っても良いし、すでにあるやつでもOK、アクションを制御できる感じのやつを使う)

```java:簡単な使用例 入力フォームで使うオブジェクト
@Data
@AllArgsConstructor
@NoArgsConstructor
public class XxxDto {
    // groups = { クラス... } ここに設定したクラスをコントローラ側で設定するとバリデーション実行
	@NotNull(groups = { ClasA.class })
	private Integer val1;
	@NotBlank
	private String val2;
}
```

```java:簡単な使用例 コントローラ
    // Validated({クラス...}) 上のオブジェクトで設定したクラスを設定
    // Default.class を設定しておくと groups の設定がないバリデーションも実行できる
    // Validated でクラスを設定しない時は Default.class を設定してることになる
	@PostMapping("/url_samp")
	public String sample(Model model, @Validated({ Default.class, ClasA.class }) @ModelAttribute XxxDto dto, BindingResult result) {
        // バリデーション確認
        if (result.hasErrors()) {
            // とりあえずエラー内容出力
            result.getFieldErrors().forEach(e -> System.out.println(e.getField() + ":" + e.getDefaultMessage()));
            // 結果を画面に設定
            model.addAttribute("errorResult", result);
            return "sampl";
        }
        // バリデーションOKの時の処理
        // ・
        // ・
        return "sampl";
    }
```