๋ฐ์ํ
@Valid
์ BindingResult
(๋๋ Errors
)
- BindingResult ๋ ํญ์ @Valid ๋ฐ๋ก ๋ค์ ์ธ์๋ก ์ฌ์ฉํด์ผ ํจ (์คํ๋ง MVC)
- @NotNull, @NotEmpty, @Min, @Max ... ์ฌ์ฉํด์ ์ ๋ ฅ๊ฐ ๋ฐ์ธ๋ฉ ํ ๋ ์๋ฌ ํ์ธ์ด ๊ฐ๋ฅํ๋ค
๋๋ฉ์ธ Validator ๋ง๋ค๊ธฐ
-
Validator ์ธํฐํ์ด์ค ์์ด ๋ง๋ค์ด๋ ์๊ด์๋ค.
@Component public class EventValidator { public void validate(EventDto eventDto, Errors errors) { if (eventDto.getBasePrice() > eventDto.getMaxPrice() && eventDto.getMaxPrice() != 0) { errors.rejectValue("basePrice","wrongValue", "BasePrice is wrong"); errors.rejectValue("maxPrice","wrongValue", "MaxPrice is wrong"); } // errors ์๋ค๊ฐ rejectValue ๋ก ์๋ฌ๋ฉ์ธ์ง๋ฅผ ๋ฃ์ด์ฃผ๋๊ฒ ๋ฟ์ด๋ค. ๊ฐ๋จํจ LocalDateTime endEventDateTime = eventDto.getEndEventDateTime(); LocalDateTime closeEnrollmentDateTime = eventDto.getCloseEnrollmentDateTime(); LocalDateTime beginEventDateTime = eventDto.getBeginEventDateTime(); if (endEventDateTime.isBefore(beginEventDateTime) || endEventDateTime.isBefore(closeEnrollmentDateTime) || endEventDateTime.isBefore(eventDto.getBeginEnrollmentDateTime())) { errors.rejectValue("endEventDateTime", "wrongValue", "endEventDataTime is wrong"); } // TODO beginEventDataTime if (beginEventDateTime.isBefore(closeEnrollmentDateTime) || beginEventDateTime.isBefore(eventDto.getBeginEnrollmentDateTime())) { errors.rejectValue("beginEventDateTime", "wrongValue", "beginEventDateTime is wrong"); } // TODO CloseEnrollmentDateTime if (closeEnrollmentDateTime.isBefore(eventDto.getBeginEnrollmentDateTime())) { errors.rejectValue("closeEnrollmentDateTime","wrongValue", "closeEnrollmentDateTime is wrong" ); } } }
Controller ์์ ๊ฒ์ฆ
@Controller
@RequestMapping(value = "/api/events", produces = MediaTypes.HAL\_JSON\_VALUE)
public class EventController {
private final EventRepository eventRepository;
private final ModelMapper modelMapper;
//validator๋ฅผ ์์ฑ์๋ฅผ ํตํด ๋น์ ์ฃผ์
๋ฐ๊ณ
private final EventValidator eventValidator;
//์์ฑ์๊ฐ ํ๋๋ง์๊ณ , ๋ฐ์์ฌ ํ์
์ด ๋น์ผ๋ก ๋ฑ๋ก๋์ด์์ผ๋ฉด autowired ์๋ต ๊ฐ๋ฅ
public EventController(EventRepository eventRepository, ModelMapper modelMapper, EventValidator eventValidator) {
this.eventRepository = eventRepository;
this.modelMapper = modelMapper;
this.eventValidator = eventValidator;
}
// @Valid ๋ฅผ ๋ถ์ฌ์ฃผ๋ฉด, EventDto์ Entity์ ๋ถ์ด์๋ ์ ๋
ธํ
์ด์
์ ๊ธฐ๋ฐ์ผ๋ก ๊ฒ์ฆ์ ํด์ค๋ค
// ์๋ฌ ๋ฐ์์ Errors์๋ค๊ฐ ์๋ฌ์ ์ ๋ณด๋ฅผ ๋ด์์ค๋ค.
@PostMapping
public ResponseEntity createEvent(@RequestBody @Valid EventDto eventDto, Errors errors) {
//@Valid ์ค๋ฅ๊ฐ ๋๋ค๋ฉด, ์์กด์ฑ ์ถ๊ฐ : spring-boot-starter-validation ์ ํด์ค๋ค.
if (errors.hasErrors()) {
return ResponseEntity.badRequest().build();
}
//validator๋ก ๊ฒ์ฆ -> errors์ error๋ฅผ ๋ฃ์ด์ค๋ค. ์๋ฌ ๋ฐ์์ -> badRequest ๋ฐ์!
eventValidator.validate(eventDto, errors);
if (errors.hasErrors()) {
return ResponseEntity.badRequest().build();
}
Event event = modelMapper.map(eventDto, Event.class);
Event newEvent = this.eventRepository.save(event);
//link๋ฅผ ์์ฑํ ๋,
//HATEOAS๊ฐ ์ ๊ณตํ๋ linkTo(), methodOn()์ ์ฌ์ฉ , ์ง๊ธ์ ํด๋์ค๋ ๋ฒจ์ RequestMapping์ด ๊ฑธ๋ ธ๊ธฐ๋๋ฌธ์ methodOn ์ฌ์ฉ X
URI createUri = linkTo(EventController.class).slash(newEvent.getId()).toUri();
return ResponseEntity.created(createUri).body(event); //201์๋ต์ Uri์ ๋ด์์ ๋ฆฌํด์ํจ๋ค.
}
}
ํ ์คํธ ์ค๋ช ์ฉ ์ ๋ ธํ ์ด์ ๋ง๋ค๊ธฐ
- @Target , @Retention
@Target(value = ElementType.METHOD)
@Retention(RetentionPolicy.SOURCE)
public @interface TestDescription {
String value();
}
์์ฑํ๊ณ ๊ฐ ํ ์คํธ ๋ง๋ค ์ค๋ช ์ ์ ์ด์ฃผ๋ฉด ๋๋ค. ๊ทธ๋ฌ๋ฉด ํ ์คํธ๋ฅผ ์คํํ ๋, ๋ฉ์๋ ์ด๋ฆ๋ง๊ณ
๋ด๊ฐ @TestDescription์ ๋ฃ์ด์ค ๋ฌธ์์ด์ด ์ถ๋ ฅ์ด ๋๋๋ฐ.. ๋ ์๋จ
ํ ์คํธ ์ฝ๋ ์ถ๊ฐ
- ๋๋๋ ์๊ฐ์ด ์์ ์๊ฐ๋ณด๋ค ๋น ๋ฅด๊ฒ ๊ฐ์ ๋ฃ์ด์ฃผ๊ณ , validator๊ฐ badRequest๋ฅผ ์ ๋ณด๋ด๋์ง ํ์ธ!
@Test
@TestDescription("์
๋ ฅ ๊ฐ์ด ์๋ชป๋ ๊ฒฝ์ฐ์ ์๋ฌ๊ฐ ๋ฐ์ํ๋ ํ
์คํธ")
public void createEvent_Bad_Request_Wrong_Input() throws Exception {
EventDto eventDto = EventDto.builder()
.name("Spring")
.description("REST API Development with Spring")
.beginEnrollmentDateTime(LocalDateTime.of(2020, 9, 8, 2, 45)) //์์ ๋ ์ง๊ฐ ๋๋๋ ๋ ์ง๋ณด๋ค ๋น ๋ฆ!
.closeEnrollmentDateTime(LocalDateTime.of(2020, 9, 7, 2, 45))
.beginEventDateTime(LocalDateTime.of(2020, 9, 10, 2, 45))
.endEventDateTime(LocalDateTime.of(2020, 9, 9, 2, 45))
.basePrice(10000) //maxPrice ๋ณด๋ค ํผ
.maxPrice(200)
.limitOfEnrollment(100)
.location("Daejoen")
.build();
mockMvc.perform(post("/api/events")
.contentType(MediaTypes.HAL_JSON_VALUE)
.content(objectMapper.writeValueAsString(eventDto))
)
.andDo(print())
.andExpect(status().isBadRequest())
;
}
๋ฐ์ํ