ObjectMapper가 json 스트링을 변환 할 때 Composite 객체가 존재 할 경우
변수명을 json Object명에 사용 한다.
아래 예시public Class Resource{ private User user; private String source; }
{ "user" : { "name" : "geun", "age" : 12 }, "source" : "source" }
@JsonUnWrapped : Composite 객체에 해당 어노테이션에 선언하면
아래와 같이 json Object 명을 사용하지 않는다.public Class Resource{ @JsonUnWrapped private User user; private String source; }
{ { "name" : "geun", "age" : 12 }, "source" : "source" }
ObjectMapper : 객체 <->json 변환하는 라이브러리
ex) String json = objectMapper.writeValueAsString(eventDto) //객체를 json string으로 변환 Event event = objectMapper.readValue(json,Event.class); // json 을 객체로 변환
modelmapper: dto <-> domain 객체에 값 복사 해 주는 라이브러리
@ 중요: ModelMapper는 요청시 받은 DTO를 Entity객체에 binding 해 줄 때
조회 요청 시 Entity 객체를 DTO로 변환 시 필요 할 듯
물론 Entity를 안 거치고 DTO를 바로 사용 할 경우에는 필요하지 않겠지만......ex) Event event = modelMapper.map(eventDto,Event.class); EventDto eventDto1 = modelMapper.map(event,EventDto.class);
<!-- 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>
EventValidator : 입력값 검증 후 error 정보를 Erros객체에 상세정보 세팅
ErrorsSerializer : ObjectMapper class에 ErrorSerializer 클래스를 등록하여 Error 클래스를 serialization
할 수 있도록 한다.
Pageable : page,size,sort등 paging에 관련된 정보를 담고 있다.
PagedResourcesAssembler : 페이지 링크 정보를 생성 해 준다.
@GetMapping
public ResponseEntity queryEvents(Pageable pageable){
return ResponseEntity.ok().body(eventRepository.findAll((pageable)));
}
응답값
{ "content": [ { "id": 29, "name": "event26", "description": "test event26", "beginEnrollmentDateTime": null, "closeEnrollmentDateTime": null, "beginEventDateTime": null, "endEventDateTime": null, "location": null, "basePrice": 0, "maxPrice": 0, "limitOfEnrollment": 0, "offline": false, "free": false, "eventStatus": null }, { "id": 28, "name": "event25", "description": "test event25", "beginEnrollmentDateTime": null, "closeEnrollmentDateTime": null, "beginEventDateTime": null, "endEventDateTime": null, "location": null, "basePrice": 0, "maxPrice": 0, "limitOfEnrollment": 0, "offline": false, "free": false, "eventStatus": null }, { "id": 27, "name": "event24", "description": "test event24", "beginEnrollmentDateTime": null, "closeEnrollmentDateTime": null, "beginEventDateTime": null, "endEventDateTime": null, "location": null, "basePrice": 0, "maxPrice": 0, "limitOfEnrollment": 0, "offline": false, "free": false, "eventStatus": null }, { "id": 26, "name": "event23", "description": "test event23", "beginEnrollmentDateTime": null, "closeEnrollmentDateTime": null, "beginEventDateTime": null, "endEventDateTime": null, "location": null, "basePrice": 0, "maxPrice": 0, "limitOfEnrollment": 0, "offline": false, "free": false, "eventStatus": null }, { "id": 25, "name": "event22", "description": "test event22", "beginEnrollmentDateTime": null, "closeEnrollmentDateTime": null, "beginEventDateTime": null, "endEventDateTime": null, "location": null, "basePrice": 0, "maxPrice": 0, "limitOfEnrollment": 0, "offline": false, "free": false, "eventStatus": null }, { "id": 24, "name": "event21", "description": "test event21", "beginEnrollmentDateTime": null, "closeEnrollmentDateTime": null, "beginEventDateTime": null, "endEventDateTime": null, "location": null, "basePrice": 0, "maxPrice": 0, "limitOfEnrollment": 0, "offline": false, "free": false, "eventStatus": null }, { "id": 23, "name": "event20", "description": "test event20", "beginEnrollmentDateTime": null, "closeEnrollmentDateTime": null, "beginEventDateTime": null, "endEventDateTime": null, "location": null, "basePrice": 0, "maxPrice": 0, "limitOfEnrollment": 0, "offline": false, "free": false, "eventStatus": null }, { "id": 5, "name": "event2", "description": "test event2", "beginEnrollmentDateTime": null, "closeEnrollmentDateTime": null, "beginEventDateTime": null, "endEventDateTime": null, "location": null, "basePrice": 0, "maxPrice": 0, "limitOfEnrollment": 0, "offline": false, "free": false, "eventStatus": null }, { "id": 22, "name": "event19", "description": "test event19", "beginEnrollmentDateTime": null, "closeEnrollmentDateTime": null, "beginEventDateTime": null, "endEventDateTime": null, "location": null, "basePrice": 0, "maxPrice": 0, "limitOfEnrollment": 0, "offline": false, "free": false, "eventStatus": null }, { "id": 21, "name": "event18", "description": "test event18", "beginEnrollmentDateTime": null, "closeEnrollmentDateTime": null, "beginEventDateTime": null, "endEventDateTime": null, "location": null, "basePrice": 0, "maxPrice": 0, "limitOfEnrollment": 0, "offline": false, "free": false, "eventStatus": null } ], "pageable": { "sort": { "sorted": true, "unsorted": false, "empty": false }, "offset": 10, "pageNumber": 1, "pageSize": 10, "paged": true, "unpaged": false }, "last": false, "totalElements": 30, "totalPages": 3, "size": 10, "number": 1, "sort": { "sorted": true, "unsorted": false, "empty": false }, "numberOfElements": 10, "first": false, "empty": false }
@GetMapping
public ResponseEntity queryEvents(Pageable pageable, PagedResourcesAssembler<Event> pagedResourcesAssembler){
Page<Event> eventPage = eventRepository.findAll((pageable));
PagedModel<EventResource> eventResources = pagedResourcesAssembler.toModel(eventPage, e -> new EventResource(e));
eventResources.add(new Link("/docs/index.html#resources-events-list").withRel("profile"));
return ResponseEntity.ok().body(eventResources);
}
응답값
{ "_embedded": { "eventList": [ { "id": 29, "name": "event26", "description": "test event26", "beginEnrollmentDateTime": null, "closeEnrollmentDateTime": null, "beginEventDateTime": null, "endEventDateTime": null, "location": null, "basePrice": 0, "maxPrice": 0, "limitOfEnrollment": 0, "offline": false, "free": false, "eventStatus": null, "_links": { "self": { "href": "http://localhost:8080/api/events/29" } } }, { "id": 28, "name": "event25", "description": "test event25", "beginEnrollmentDateTime": null, "closeEnrollmentDateTime": null, "beginEventDateTime": null, "endEventDateTime": null, "location": null, "basePrice": 0, "maxPrice": 0, "limitOfEnrollment": 0, "offline": false, "free": false, "eventStatus": null, "_links": { "self": { "href": "http://localhost:8080/api/events/28" } } }, { "id": 27, "name": "event24", "description": "test event24", "beginEnrollmentDateTime": null, "closeEnrollmentDateTime": null, "beginEventDateTime": null, "endEventDateTime": null, "location": null, "basePrice": 0, "maxPrice": 0, "limitOfEnrollment": 0, "offline": false, "free": false, "eventStatus": null, "_links": { "self": { "href": "http://localhost:8080/api/events/27" } } }, { "id": 26, "name": "event23", "description": "test event23", "beginEnrollmentDateTime": null, "closeEnrollmentDateTime": null, "beginEventDateTime": null, "endEventDateTime": null, "location": null, "basePrice": 0, "maxPrice": 0, "limitOfEnrollment": 0, "offline": false, "free": false, "eventStatus": null, "_links": { "self": { "href": "http://localhost:8080/api/events/26" } } }, { "id": 25, "name": "event22", "description": "test event22", "beginEnrollmentDateTime": null, "closeEnrollmentDateTime": null, "beginEventDateTime": null, "endEventDateTime": null, "location": null, "basePrice": 0, "maxPrice": 0, "limitOfEnrollment": 0, "offline": false, "free": false, "eventStatus": null, "_links": { "self": { "href": "http://localhost:8080/api/events/25" } } }, { "id": 24, "name": "event21", "description": "test event21", "beginEnrollmentDateTime": null, "closeEnrollmentDateTime": null, "beginEventDateTime": null, "endEventDateTime": null, "location": null, "basePrice": 0, "maxPrice": 0, "limitOfEnrollment": 0, "offline": false, "free": false, "eventStatus": null, "_links": { "self": { "href": "http://localhost:8080/api/events/24" } } }, { "id": 23, "name": "event20", "description": "test event20", "beginEnrollmentDateTime": null, "closeEnrollmentDateTime": null, "beginEventDateTime": null, "endEventDateTime": null, "location": null, "basePrice": 0, "maxPrice": 0, "limitOfEnrollment": 0, "offline": false, "free": false, "eventStatus": null, "_links": { "self": { "href": "http://localhost:8080/api/events/23" } } }, { "id": 5, "name": "event2", "description": "test event2", "beginEnrollmentDateTime": null, "closeEnrollmentDateTime": null, "beginEventDateTime": null, "endEventDateTime": null, "location": null, "basePrice": 0, "maxPrice": 0, "limitOfEnrollment": 0, "offline": false, "free": false, "eventStatus": null, "_links": { "self": { "href": "http://localhost:8080/api/events/5" } } }, { "id": 22, "name": "event19", "description": "test event19", "beginEnrollmentDateTime": null, "closeEnrollmentDateTime": null, "beginEventDateTime": null, "endEventDateTime": null, "location": null, "basePrice": 0, "maxPrice": 0, "limitOfEnrollment": 0, "offline": false, "free": false, "eventStatus": null, "_links": { "self": { "href": "http://localhost:8080/api/events/22" } } }, { "id": 21, "name": "event18", "description": "test event18", "beginEnrollmentDateTime": null, "closeEnrollmentDateTime": null, "beginEventDateTime": null, "endEventDateTime": null, "location": null, "basePrice": 0, "maxPrice": 0, "limitOfEnrollment": 0, "offline": false, "free": false, "eventStatus": null, "_links": { "self": { "href": "http://localhost:8080/api/events/21" } } } ] }, "_links": { "first": { "href": "http://localhost:8080/api/events?page=0&size=10&sort=name,desc" }, "prev": { "href": "http://localhost:8080/api/events?page=0&size=10&sort=name,desc" }, "self": { "href": "http://localhost:8080/api/events?page=1&size=10&sort=name,desc" }, "next": { "href": "http://localhost:8080/api/events?page=2&size=10&sort=name,desc" }, "last": { "href": "http://localhost:8080/api/events?page=2&size=10&sort=name,desc" } }, "page": { "size": 10, "totalElements": 30, "totalPages": 3, "number": 1 } }
test code
@Test
@TestDescription("30개의 이벤트를 10개씩 두번째 페이지 조회하기")
public void queryEvents() throws Exception{
//given
IntStream.range(0,30).forEach(i -> this.generateEvent(i));
//when
this.mockMvc.perform(get("/api/events")
.param("page","1")
.param("size","10")
.param("sort", "name,DESC")
)
.andDo(print())
.andExpect(status().isOk())
.andExpect(jsonPath("page").exists())
.andDo(document("query-events"));
//then
}
private void generateEvent(int i) {
Event event = Event.builder()
.name("event"+i)
.description("test event"+i)
.build();
eventRepository.save(event);
}
- 서버측 에러 발생 시 처리
Contoller에서 try~catch로 잡아서 응답전문 생성
service 객체에서 throws 해서 controller에서 예외처리 한다.
Response<Error,T> 생성 시 JavaBean 스펙을 준수 해야만 해야 serialization 할 수 있다. Response<Error,T> 객체 구조 -- result : N(실패 : 1111 ~ 9999) -- Error Class 활용 (에러 정보) -- result : y(성공 : 0000) -- 응답 전문
에러코드를 Propertie 파일로 빼내서 처리한다.
구체적인 부분은 정리 해 보자...
<<<< 학습 필요!!!!! >>>>
외부 설정 : application.properties 다른 설정 파일 읽어와서 bean으로 만들 순 없을까???
[숙제..............]
ClientDetailsServiceConfigurer clients 구성을 inmemory 화 되어 있는데 일반적인 구성 확인 필요