LoginSignup
0
0

More than 1 year has passed since last update.

SpringBoot @PutMappingについて

Last updated at Posted at 2023-02-21

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

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は「1」になります。
@RequestBody Person person」はcontent bodyであるPersonはJSONと言う宣言です。
idが「{id}」の人に対して、@RequestBodyにて受け取ったpersonのデータで更新されます。

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()します。

// 省略
@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が「1」の人に対して、content bodyのデータにて更新されたことがわかります。
image.png

問題点

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

image.png
以下は、上記のmodifyPerson()テストコードを実行した結果です。
image.png

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

改善

PersonController.java
public void modifyPerson(@PathVariable Long id, @RequestBody PersonDto person)

Controllerに送られるcontentをDTO型にて受け取ります。
修正前のようにPerson entityにてマッピングしたら、entityを操作しちゃって内部データが変更されちゃう危険性があるし、
レイヤ同士でデータのやり取りはDTOが適切なわけです。

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できます!

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0