ํ ์คํธ ํ ๊ฒ
- ์
๋ ฅ๊ฐ์ผ๋ก ๋๊ฐ id๋ eventStatus, offline, free ์ด๋ฐ ๋ฐ์ดํฐ๊น์ง ๊ฐ์ด ์ฃผ๋ฉด?
- Bad_Request ๋ฐ์ (์ด๋ฒ ํ ์คํธ!) vs ๋ฐ๊ธฐ๋ก ํ ๊ฐ ์ด์ธ๋ ๋ฌด์
ํต์ฌ ์ฝ๋
ObjectMapper
์ปค์คํฐ๋ง์ด์ง
spring.jackson.deserialization.fail-on-unknown-properties = true
// deserialization : ์ญ์ง๋ ฌํ(JSON -> ๊ฐ์ฒด) ๋ฅผ ํ๋๋ฐ, unknown-properties๊ฐ ์์ผ๋ฉด -> fail-on ์คํจ๋ฅผ ๋์ง๋ค.
application.properties
spring.jackson.deserialization.fail-on-unknown-properties=true
#์ deserialization์ด๋๋ฉด, (Json)EventDto๋ฅผ (๊ฐ์ฒด)Event์ ๋ฃ์ด์ฃผ๋๊ฒ์ด๋ฏ๋ก ๋น์ง๋ ฌํ ์ด๋ค.
# ๊ฐ์ฒด -> Json => Serialization
# Json -> ๊ฐ์ฒด => deSerialization
EventController.class
package me.iseunghan.demoinflearnrestapi.events;
import org.modelmapper.ModelMapper;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.hateoas.MediaTypes;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import java.net.URI;
import static org.springframework.hateoas.server.mvc.WebMvcLinkBuilder.linkTo;
import static org.springframework.hateoas.server.mvc.WebMvcLinkBuilder.methodOn;
@Controller
@RequestMapping(value = "/api/events", produces = MediaTypes.HAL_JSON_VALUE)
public class EventController {
private final EventRepository eventRepository;
private final ModelMapper modelMapper;
//์์ฑ์๊ฐ ํ๋๋ง์๊ณ , ๋ฐ์์ฌ ํ์
์ด ๋น์ผ๋ก ๋ฑ๋ก๋์ด์์ผ๋ฉด autowired ์๋ต ๊ฐ๋ฅ
public EventController(EventRepository eventRepository, ModelMapper modelMapper) {
this.eventRepository = eventRepository;
this.modelMapper = modelMapper;
}
@PostMapping
public ResponseEntity createEvent(@RequestBody EventDto eventDto) {
/*Event event = Event.builder()
.name(eventDto.getName())
...
.build(); ๋ฅผ ์์ฝ๊ฒ ๋งคํํด์ฃผ๋ ModelMapper๋ฅผ ์ฌ์ฉํ๋ฉด ๋๋ค.*/
Event event = modelMapper.map(eventDto, Event.class);
Event newEvent = this.eventRepository.save(event);
//link๋ฅผ ์์ฑํ ๋,
//HATEOAS๊ฐ ์ ๊ณตํ๋ linkTo(), methodOn()์ ์ฌ์ฉ
//HATEOAS๊ฐ ์ ๊ณตํ๋ linkTo(), methodOn()์ ์ฌ์ฉ , ์ง๊ธ์ ํด๋์ค๋ ๋ฒจ์ RequestMapping์ด ๊ฑธ๋ ธ๊ธฐ๋๋ฌธ์ methodOn ์ฌ์ฉ X
URI createUri = linkTo(EventController.class).slash(newEvent.getId()).toUri();
return ResponseEntity.created(createUri).body(event); //201์๋ต์ Uri์ ๋ด์์ ๋ฆฌํด์ํจ๋ค.
}
}
EventDTO.class
package me.iseunghan.demoinflearnrestapi.events;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.time.LocalDateTime;
//id ๋๋ ์
๋ ฅ ๋ฐ์ ๋ฐ์ดํฐ๋ก ๊ณ์ฐํด์ผ ํ๋ ๊ฐ๋ค์ EventDTO๋ก ์
๋ ฅ๊ฐ์ ์ ํ์ ํ๋ค.
//Controller์์ ModelMapper๋ก EventDTO ๊ฐ์ฒด๋ฅผ ๋ฐ์์ Event๊ฐ์ฒด๋ก ๋งคํํด์ฃผ๋ ์์
์ ํ ๊ฒ์ด๋ค.
@Data @Builder @AllArgsConstructor @NoArgsConstructor
public class EventDto {
private String name;
private String description;
private LocalDateTime beginEnrollmentDateTime;
private LocalDateTime closeEnrollmentDateTime;
private LocalDateTime beginEventDateTime;
private LocalDateTime endEventDateTime;
private String location; // (optional) ์ด๊ฒ ์์ผ๋ฉด ์จ๋ผ์ธ ๋ชจ์
private int basePrice; // (optional)
private int maxPrice; // (optional)
private int limitOfEnrollment;
}
EventControllerTests.class
@RunWith(SpringRunner.class)
@SpringBootTest
@AutoConfigureMockMvc
public class EventControllerTests {
@Autowired
MockMvc mockMvc;
@Autowired
ObjectMapper objectMapper;
// TODO ๋ฐ๊ธฐ๋ก ํ ๊ฐ์ด ์๋๋ -> Bad_Request๋ก ์๋ต
//๊ทธ๋๋ก ์คํํ๋ฉด ์๋ฌ ๋ฐ์,
// properties์ : spring.jackson.deserialization.fail-on-unknown-properties=true ๋ฅผ ์ถ๊ฐํด์ฃผ๋ฉด, unknown properties๊ฐ ๋ค์ด์ค๋ฉด fail -> error ๋ฐ์!
@Test
public void createEvent_BadRequest() throws Exception {
Event event = Event.builder()
.id(100) // unknown properties
.name("Spring")
.description("REST API Development with Spring")
.beginEnrollmentDateTime(LocalDateTime.of(2020, 9, 7, 2, 45))
.closeEnrollmentDateTime(LocalDateTime.of(2020, 9, 8, 2, 45))
.beginEventDateTime(LocalDateTime.of(2020, 9, 9, 2, 45))
.endEventDateTime(LocalDateTime.of(2020, 9, 10, 2, 45))
.basePrice(100)
.maxPrice(200)
.limitOfEnrollment(100)
.location("Daejoen")
.free(true)
.offline(false)
.eventStatus(EventStatus.PUBLISHED)
.free(true) // unknown properties
.offline(false) // unknown properties
.eventStatus(EventStatus.PUBLISHED) // unknown properties
.build();
mockMvc.perform(post("/api/events/")
.contentType(MediaType.APPLICATION_JSON)//๋ณธ๋ฌธ ์์ฒญ์ json์ ๋ด์์ ๋ณด๋ด๊ณ ์๋ค๊ณ ์๋ ค์ค.
.accept(MediaTypes.HAL_JSON)//HAL_JSON์ผ๋ก ๋ฐ๋๋ค.
.content(objectMapper.writeValueAsString(event)))//์์ฒญ ๋ณธ๋ฌธ์ ๋ฃ์ด์ค๋ค. objectMapper๋ก event๋ฅผ json์ผ๋ก ๋ณํํ
//์ด ๋ถ๋ถ์์ Controller์ @RequestBody๋ก ๋๊ธฐ๋ ๊ณผ์ ์์ EventDto์ modelmappingํ ๋ unknown_properties์ธ ๊ฐ์ด ๋ค์ด์์ ํ
์คํธ๊ฐ ๊นจ์ง๊ฒ์ด๋ค.
.andDo(print())//์ด๋ค ์๋ต๊ณผ ์์ฒญ์ ๋ฐ์๋์ง ํ์ธ๊ฐ๋ฅ.
.andExpect(status().isBadRequest())//badRequest์์ฒญ์ด ๋ค์ด์๋์ง?
;
}
์ดํดํ๊ธฐ
Event
๊ฐ์ฒด์๋ค๊ฐ build();
๋ฅผ ํ๋๋ฐ ์ฌ๊ธฐ์๋ EventDTO
์๋ ์๋ ๊ฐ๋ค๋ ๊ฐ์ด ๋ค์ด๊ฐ์๋ค.(id๋ผ๋์ง, free,offline,eventStatus)
๊ทธ event๊ฐ์ฒด๋ฅผ ๊ฐ์ง๊ณ .content(objectMapper.writeValueAsString(event))
๋ก json์ผ๋ก ๋ณํํด์ -> post๋ก ์์ฒญ์ ํ ๋,Event event = modelMapper.map(eventDto, Event.class);
์ฌ๊ธฐ์ eventDto ๊ฐ์ฒด๋ฅผ Event๊ฐ์ฒด๋ก ๋งคํ์ ํ ๋,
Test์์ ๋์ด์จ JSON ๋ฐ์ดํฐ๋ ์ด๋ ๋ค,
{
"id" : 100, // X EventDTO ์๋ ์๋ ํ๋กํผํฐ
"name" : "Spring",
"description" : "...",
...
...
"eventStatus" : "EventStatus.PUBLISHED" // X EventDTO ์๋ ์๋ ํ๋กํผํฐ
}
Event ๊ฐ๋ค์ EventDto ๋ก ๊ฐ๋ค์ ์๋ ๊ฐ๋ค์ ๋ฌด์ํ๊ณ ๋งคํ์์ผ์ค๋ค. ํ์ง๋ง ์ฐ๋ฆฌ๋ ์๊น application.properties
์์
์ฐ๋ฆฌ๊ฐ ์ปค์คํฐ๋ง์ด์ง ํ๋ spring.jackson.deserialization.fail-on-unknown-properties=true
๋ก ์ธํด, unknown-properties๊ฐ ์์ผ๋ฏ๋ก, ํ
์คํธ๊ฐ ๊นจ์ง๊ฒ ๋๋ค.