SpringBoot @PutMappingについて

@PutMappingは、データを修正/更新するために使用されます。

```java:
package com.informanaging.project.demo.controller;

import com.informanaging.project.demo.domain.Person;
import com.informanaging.project.demo.repository.PersonRepository;
import com.informanaging.project.demo.service.PersonService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpStatus;
import org.springframework.web.bind.annotation.*;

@RequestMapping(value = "/api/person") //scopeはクラス全体なので、postPerson()は("/api/person")のURIに対応
@RestController // REST API Controllerを使用しますよとの宣言
@Slf4j
public class PersonController {
    @Autowired
    private PersonService personService;

    @Autowired
    private PersonRepository personRepository;


    @GetMapping("/{id}")
    public Person getPerson(@PathVariable Long id) {
        return personService.getPerson(id);
    }

    @PostMapping
    @ResponseStatus(HttpStatus.OK)
    public void postPerson(@RequestBody Person person) {
        personService.put(person);
        log.info("person -> {}", personRepository.findAll());
    }

    // JSON bodyを受け取るため、@RequestBodyを使用
    @PutMapping("/{id}")
    public void modifyPerson(@PathVariable Long id, @RequestBody  Person person){
        personService.modify(id, person);

        log.info("person -> {}", personRepository.findAll());
    }
}
```
クライアントから「/api/person/1」といったRequestが送られると、@PutMapping("/{id}")のidは「１」になります。
「@RequestBody Person person」はcontent bodyであるPersonはJSONと言う宣言です。
idが「{id}」の人に対して、@RequestBodyにて受け取ったpersonのデータで更新されます。
```java:PersonService.java
// 省略
@Service
@Slf4j
public class PersonService {

    @Autowired
    private PersonRepository personRepository;

    @Transactional
    public void modify(Long id, Person person) {
        Person personAtDb = personRepository.findById(id).orElseThrow(() -> new RuntimeException("id is not existed")); 
        personAtDb.setName(person.getName());
        personAtDb.setPhoneNumber(person.getPhoneNumber());
        personAtDb.setJob(person.getJob());
        personAtDb.setAddress(person.getAddress());
        personAtDb.setBirthday(new Birthday(person.getBirthday()));
        personAtDb.setBloodType(person.getBloodType());
        personAtDb.setHobby(person.getHobby());
        personAtDb.setAge(person.getAge());

        personRepository.save(personAtDb);
    }
}
```
PersonのRepositoryから、findById(id)をして、Person Entityデータを取得します。
controller層から送られてきたpersonオブジェクトをsetterして、save()します。
```java
//　省略
@SpringBootTest
public class PersonControllerTest {

    @Autowired
    private PersonController personController;

    private MockMvc mockMvc;

    @Test
    void modifyPerson() throws Exception {
        mockMvc = MockMvcBuilders.standaloneSetup(personController).build();

        mockMvc.perform(
                MockMvcRequestBuilders.put("/api/person/1")
                .contentType(MediaType.APPLICATION_JSON_UTF8)
                .content("{\n" +
                                "    \"name\": \"martin2\", \n" +
                                "    \"age\": 21, \n" +
                                "    \"bloodType\": \"A\"\n" +
                                "}"))
                .andDo(print())
                .andExpect(status().isOk());
    }
}
```
PersonControllerクラスのmodifyPerson()メソッドテストコードです。
上記のテストコードを実行すると、modifyPerson()が反応をします。
（PersonControllerのクラスに定義した@RequestMappingが「/api/person」で、modifyPersonメソッドの@PutMappingが「/id」のため）
テストコードを実行すると、idが「１」の人に対して、content bodyのデータにて更新されたことがわかります。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3097775/8acea342-602f-0d9b-41ab-be3acef9ac4c.png)


<h2>問題点</h2>
しかし、上記のコードでは問題が発生しています。
以下は、modifyPerson()を実行する前のGET /api/person/1　の結果です。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3097775/4aad9d93-d043-eac1-5908-e9d09ad640bb.png)
以下は、上記のmodifyPerson()テストコードを実行した結果です。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3097775/f252a73e-edb3-2366-1754-ce45c7e7d7d4.png)

上記の差から分かるように、同じIDに対して、名前が違いますがそれをフィルタできなかったことと、ageとbloodTypeを変更したいですが、
変更対象以外は全部nullが入ってしまったことです。


<h2>改善</h2>

```java:PersonController.java
public void modifyPerson(@PathVariable Long id, @RequestBody PersonDto person)
```
Controllerに送られるcontentをDTO型にて受け取ります。
修正前のようにPerson entityにてマッピングしたら、entityを操作しちゃって内部データが変更されちゃう危険性があるし、
レイヤ同士でデータのやり取りはDTOが適切なわけです。
```java:PersonDto.java
package com.informanaging.project.demo.controller.dto;

import lombok.Data;
import java.time.LocalDate;

@Data
public class PersonDto {
    private String name;
    private int age;
    private String hobby;
    private String bloodType;
    private String address;
    private LocalDate birthday;
    private String job;
    private String phoneNumber;
}
```

```PersonServie.java
// 省略
@Service
@Slf4j
public class PersonService {

    @Autowired
    private PersonRepository personRepository;

    @Transactional
    public void modify(Long id, PersonDto personDto) {
        Person person = personRepository.findById(id).orElseThrow(() -> new RuntimeException("id is not existed"));

        if (!person.getName().equals(personDto.getName())) {
            throw new RuntimeException("名前が違います");
        }
        person.set(personDto);
        personRepository.save(person);
    }
}
```
修正前のmodify()関数は、viewのrequestをentityにてマッピングしてましたが、
現在はPersonDto型にマッピングしています。
person.set(personDto);　にて、personDtoのvalidatingを通したデータを
次のsave()にて保存します。

```Person.java
// 省略
@Entity
@NoArgsConstructor
@AllArgsConstructor
@RequiredArgsConstructor
@Data
public class Person {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY) // AUTO
    private Long id;

    @NonNull 
    @NotEmpty // Stringのため、NotEmptyを追加
    @Column(nullable = false)
    private String name;

    @NonNull
    @Min(1)
    private int age;

    private String hobby;

    @NonNull
    @NotEmpty // Stringのため、NotEmptyを追加
    @Column(nullable = false)
    private String bloodType;

    private String address;

    @Valid
    @Embedded
    private Birthday birthday;

    private String job;

    @ToString.Exclude
    private String phoneNumber;

    @OneToOne(cascade = {CascadeType.PERSIST, CascadeType.MERGE,CascadeType.REMOVE}, orphanRemoval = true, fetch = FetchType.LAZY) // persistence
    @ToString.Exclude
    private Block block; // personオブジェクトに対してブロックをしたか、してなかったかを確認するpropertyなのでOne-to-one


    public void set(PersonDto personDto) {
        // ageはintなので、ageがnullだったら、自動的に0が入ります。
        // 0の場合は、設定しないとの意味。
        if (personDto.getAge() != 0) {
            this.setAge(personDto.getAge());
        }

        if(!StringUtils.isNullOrEmpty(personDto.getHobby())) {
            this.setHobby(personDto.getHobby());
        }

        if(!StringUtils.isNullOrEmpty(personDto.getBloodType())) {
            this.setBloodType(personDto.getBloodType());
        }

        if(!StringUtils.isNullOrEmpty(personDto.getAddress())) {
            this.setAddress(personDto.getAddress());
        }

        if(!StringUtils.isNullOrEmpty(personDto.getPhoneNumber())) {
            this.setPhoneNumber(personDto.getPhoneNumber());
        }

        if(!StringUtils.isNullOrEmpty(personDto.getJob())) {
            this.setJob(personDto.getJob());
        }
    }
}
```
Domain層のPersonクラスにset(PersonDto personDto)関数を新しく設けて、Service層のmodify()から送られてきたPersonDtoをvalidatingできます！