validation @Maxと@Minを使って、日付データを制御する

bean validationとは、springで有効性検証のための標準、アノテーションベースの制約条件で簡単に検証できます。
bean validationが提供するアノテーションを使って日付データを除去してみようと思います。

<h3>validationを適用していない挙動</h3>
テストコード、givenPersonのsetBirthdayに存在しない日付をセットするようにして挙動を確認する。

```java:テストコード
@Test
void findByBirthdayBetween() {
     givenPerson("martin",10,"A",LocalDate.of(1991,2,21));
     givenPerson("david",9,"B",LocalDate.of(1992,7,1));
     givenPerson("dennis",8,"O",LocalDate.of(1993,1,5));
     givenPerson("sophia",7,"AB",LocalDate.of(1994,6,30));
     givenPerson("benny",6,"A",LocalDate.of(1995,8,30));

     List<Person> result = personRepository.findByMonthOfBirthday(14);

     result.forEach(System.out::println);
}

private void givenPerson(String name, int age, String bloodType, LocalDate birthday) {
    Person person = new Person(name, age,bloodType);
    person.setBirthday(new Birthday(birthday.getYear(), 14, 35));
    personRepository.save(person);
}
```

```java:生年月日DTOクラス
package com.informanaging.project.demo.domain.dto;

import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import javax.persistence.Embeddable;
import java.time.LocalDate;

@Embeddable
@NoArgsConstructor
@AllArgsConstructor
@Data
public class Birthday {
    private int yearOfBirthday;
    private int monthOfBirthday;
    private int dayOfBirthday;
}
```

```java:Personエンティティクラス
package com.informanaging.project.demo.domain;

import com.informanaging.project.demo.domain.dto.Birthday;
import lombok.*;
import javax.persistence.*;
import javax.validation.Valid;
import java.time.LocalDate;

@Entity
@NoArgsConstructor
@AllArgsConstructor
@RequiredArgsConstructor
@Data
public class Person {
    // ・・・省略
    @Embedded
    private Birthday birthday;
}
```

結果が以下。ユーザーから存在しない日付を入力してもらってもエラーにならず、動いてしまいます。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3097775/8c3fcf75-327b-1531-6ee8-d5e976fe5b43.png)

<h2>validationを適用する</h2>

Springでvalidationを使うためには、dependencyを追加する必要があります。
```java:build.gradle
 implementation 'org.springframework.boot:spring-boot-starter-validation'
```

日付の月データは1~12の間の値を持てるし、日データは1~30の間の値を持てますので、その定義をまず
やります。この場合に使えるvalidationのannotationはminとmaxがあります。
以下のように@min(数値) @max(数値)のように使うことができます。<br>
また、エンティティクラスにはbirthdayについて@Validを定義する必要があります。
```java:生年月日DTOクラス
package com.informanaging.project.demo.domain.dto;

import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import javax.persistence.Embeddable;
import java.time.LocalDate;
import javax.validation.constraints.Max;
import javax.validation.constraints.Min;

@Embeddable
@NoArgsConstructor
@AllArgsConstructor
@Data
public class Birthday {
    @Min(1)
    @Max(12)
    private int monthOfBirthday;

    @Min(1)
    @Max(31)
    private int dayOfBirthday;
    private int dayOfBirthday;
}
```
```java:Personエンティティクラス
package com.informanaging.project.demo.domain;

import com.informanaging.project.demo.domain.dto.Birthday;
import lombok.*;
import javax.persistence.*;
import javax.validation.Valid;
import java.time.LocalDate;
import javax.validation.Valid;

@Entity
@NoArgsConstructor
@AllArgsConstructor
@RequiredArgsConstructor
@Data
public class Person {
    // ・・・省略
    @Valid
    @Embedded
    private Birthday birthday;
}
```
修正後のテスト結果。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3097775/4e404039-c2d2-9448-6ef8-d61f7090c1b1.png)
しかしこれだけだと、2月30日のデータも正しいデータだとプログラムは認識してしまうので、
追加修正が必要です。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3097775/7eb4a2ab-7f1d-004f-e65e-298d8aeb95eb.png)

BirthdayDTOクラスにBirthday constructorを設けて、受け取るBirthdayデータをLocalDataにてラッピングします。
LocalDateをラッピングすると、渡されるデータが実際に存在しないデータだとエラーを吐き出します。
そのため、実はMinとMaxの対応も要らなくなります。
```java:birthdayデータをLocalDateにてラッピングする
package com.informanaging.project.demo.domain.dto;

import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import javax.persistence.Embeddable;
import java.time.LocalDate;
import javax.validation.constraints.Max;
import javax.validation.constraints.Min;

@Embeddable
@NoArgsConstructor
@AllArgsConstructor
@Data
public class Birthday {
    private int yearOfBirthday;

    @Min(1)
    @Max(12)
    private int monthOfBirthday;

    @Min(1)
    @Max(31)
    private int dayOfBirthday;

    public Birthday(LocalDate birthday) {
        this.yearOfBirthday = birthday.getYear();
        this.monthOfBirthday = birthday.getMonthValue();
        this.dayOfBirthday = birthday.getDayOfMonth();
    }
}
```

```java:テストコード、setBirthdayにセットするデータ型も修正。
    private void givenPerson(String name, int age, String bloodType, LocalDate birthday) {
        Person person = new Person(name, age,bloodType);
        person.setBirthday(new Birthday(birthday));
        personRepository.save(person);
    }
```