<!-- ########################## rest api 추가 적인 라이브러리들 ##################### -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-hateoas</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.restdocs</groupId>
<artifactId>spring-restdocs-mockmvc</artifactId>
<scope>test</scope>
</dependency>
<!-- DTO <-> 도메인 간 값 복사 할 용도로 사용 -->
<dependency>
<groupId>org.modelmapper</groupId>
<artifactId>modelmapper</artifactId>
<version>2.3.1</version>
</dependency>
<!-- DTO에서 입력값 체크 용도로 활용 , javax.validation-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-validation</artifactId>
<version>2.5.4</version>
</dependency>
주요 클래스
@Configuration
public class WebConfiguration {
/**
* Dto <-> domain간 값 복사 용도로 modelmapper 빈으로 등록
*/
@Bean
public ModelMapper modelMapper(){
return new ModelMapper();
}
}
@Controller
@RequestMapping(value="/api/events" , produces = MediaTypes.HAL_JSON_VALUE)
public class EventContoller {
@Autowired
EventRepository eventRepository;
@Autowired
ModelMapper modelMapper; //DTO <-> 도메인 객체간에 값 복사
/**
* 이벤트 생성
* @param eventDto
* @return ReponseEntity
*/
@PostMapping
public ResponseEntity createEvent(@RequestBody EventDto eventDto) {
/**
* modelmapper를 이용해서 dto의 값을 domain객체에 값 복사 해 준다.
*/
Event event = modelMapper.map(eventDto,Event.class);
Event newEvent = eventRepository.save(event);
URI uri = linkTo(EventContoller.class).slash(newEvent.getId()).toUri();
return ResponseEntity.created(uri).body(event); // ResponseEntity를 사용하여 header 정보 등록
}
}
● 왜 @EqualsAndHasCode에서 of를 사용하는가
● 왜 @Builder를 사용할 때 @AllArgsConstructor가 필요한가
● @Data를 쓰지 않는 이유
● 애노테이션 줄일 수 없나
-> Entity 어노테이션 선언 시 아래와 같이 사용 해야 한다.
사용 이유는 시간 날 때 좀 더 공부 하기로.....
@Builder @AllArgsConstructor @NoArgsConstructor
@Getter @Setter @EqualsAndHashCode(of ={ "id" ,"name"})
@Entity
public class Event {
@Id @GeneratedValue
private Integer id;
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;
private boolean offline;
private boolean free;
@Enumerated(EnumType.STRING)
private EventStatus eventStatus = EventStatus.DRAFT;
}
DTO 객체 헨들러에서 파라미터 바인딩 할 객체
javaBean spec에 만족 해야지만
serialization , deserialization 모두 가능 하다. <<<< 중요
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class EventDto {
@NotEmpty
private String name;
@NotEmpty
private String description;
@NotNull
private LocalDateTime beginEnrollmentDateTime;
@NotNull
private LocalDateTime closeEnrollmentDateTime;
@NotNull
private LocalDateTime beginEventDateTime;
@NotNull
private LocalDateTime endEventDateTime;
private String location; // (optional) 이게 없으면 온라인 모임
@Min(0)
private int basePrice; // (optional)
@Min(0)
private int maxPrice; // (optional)
@Min(0)
private int limitOfEnrollment;
}
강사는 MVC 테스트 할 경우에 아래 형태로 테스트를 권장 한다고 함
@ExtendWith(SpringExtension.class)
@SpringBootTest
@AutoConfigureMockMvc
public class EventContollerTest {
@Autowired
MockMvc mockMvc; //웹서버를 띄우지 않는다.
@Autowired
ObjectMapper objectMapper; //객체를 json string으로 변환
@Test
public void creatEvent() throws Exception {
Event event = Event.builder()
.id(100)
.name("Spring")
.description("REST API Development with Spring")
.beginEnrollmentDateTime(LocalDateTime.of(2018, 11, 23, 14, 21))
.closeEnrollmentDateTime(LocalDateTime.of(2018, 11, 24, 14, 21))
.beginEventDateTime(LocalDateTime.of(2018, 11, 25, 14, 21))
.endEventDateTime(LocalDateTime.of(2018, 11, 26, 14, 21))
.basePrice(100)
.maxPrice(200)
.limitOfEnrollment(100)
.location("강남역 D2 스타텁 팩토리")
.free(true)
.offline(false)
.build();
mockMvc.perform(post("/api/events")
.contentType(MediaType.APPLICATION_JSON_UTF8) // 해당 요청에 JSON 본문을 보내고 있다.
.accept(MediaTypes.HAL_JSON) // accept-header 정의, hal_json을 받고 싶다는 선언
.content(objectMapper.writeValueAsString(event))
)
.andDo(print())
.andExpect(status().isCreated())
.andExpect(jsonPath("id").exists())
.andExpect(header().exists(HttpHeaders.LOCATION))
.andExpect(header().string(HttpHeaders.CONTENT_TYPE,MediaTypes.HAL_JSON_VALUE))
.andExpect(jsonPath("id").value(Matchers.not(100)))
.andExpect(jsonPath("free").value(Matchers.not(true)));
}
}
public interface EventRepository extends JpaRepository<Event,Long> {
}
ResponseEntity가 뭘까? @ResponseBody와 차이점
결론 : ResponseEntity는 응답을 보낼 때 헤더 및 상태 코드를 직접 다룰 때 사용한다.@ResponseBody
@ResponseBody 는 핸들러 메소드에 붙일 수 있는 어노테이션으로 컨트롤러가 데이터를 반환할 때
HttpMessageConverter 를 사용해서 자바 객체를 응답 본문(body) 메시지를 만들어 반환할 수 있게 도와준다.
@ReseController 를 사용하면 자동으로 모든 핸들러 메소드에 @ResponseBody 가 적용된다.
ResponseEntity
ResponseEntity는 반환값에 상태 코드와 응답 메시지를 주고 싶을 때 사용한다.
ResponseEntity.ok( ),
ResponseEntity.badRequest( ).build( ), ResponseEntity.created( ) 등을 사용할 수 있다.
@RestController
@RequestMapping("/api/events")
public class EventApi {
@PostMapping
public ReseponseEntity<Event> createEvent(@RequestBody @Valid Event event,
BindingResult bindingResult) {
//save event to DB
if(bindingResult.hasErrors()) {
//error 가 있다면 BadRequest를 보낸다.
return ResponseEntity.badRequest().build();
}
return ResponseEntity.ok(event); //상태 코드 200으로 event를 응답 본문에 담아 보낸다.
//아래 코드는 상태 코드를 CREATED(201)로 설정하여 보낸다.
//return new ResponseEntity<Event>(event, HttpStatus.CREATED);
}
}
ResponseEntity는 응답 헤더, 본문 양식을 맞추어 반환하므로 @ResponseBody가 필요 없다.
따라서 @RestController 대신 @Controller를 등록해도 잘 동작한다.
@ResponseBody와 @ResponseEntity의 차이?
결과적으로 같은 기능을 하지만 구현 방법이 조금 틀리다.
@ResponseBody의 경우 파라미터로 객체를 받아 header를 변경하고,
ResponseEntity의 경우 클래스 객체를 생성 후 객체에서 header 값을 변경한다.
ResponseEntity는 응답을 보낼 때 헤더 및 상태 코드를 직접 다룰 때 사용한다.