안녕하세요
앵과장입니다.
Rest API 개발하다보면
Get방식으로 구현하다가
생각보다 많은 파라메타를 전달해야하는 상황이 발생할수 있습니다.
이때 GET 방식 말고 POST로 방향을전환하게 되는데
나는 데이터를 조회할때는 GET Method를 사용하고 싶다 근데 파라메타를 일일이 나열하고싶지 않다 라고하시는
개발자 분들을 위해서 해당 내용을 정리하고자합니다.
GET Method 구현시 아래처럼 파라메타가 많아졌을때 어찌해야하나요?
구현하다보니 swagger ui api 에 최대한 파라메타를 노출시키려고 하다보니 아래처럼 구성이 된것을 확인하게되었는데
swagger 에는 노출이 되었는데 코드레벨에서 봤을때는 나중에 파라메타 수정도 용이하지 않을꺼 같고
Controller class 코드가 가독성있는 형태는 아닌것같습니다.
@Setter 는 DTO에서 최대한 사용하지 않고싶구
이걸 POST 방식으로 바꾸자니 왠지 지는것같구 method 조회기능은 GET인데 ....
항상 유연하게 대처하는 사람이 저는 여기서 POST로 바꾸긴했는데 그래도 GET 으로 처리할수 있는 방법이 없을까 해서 찾아보니
방법이 있는것을 확인하였습니다
@Operation(
summary = "기업 기타 정보 조회",
description = "기업 기타 정보 조회 기능을 제공합니다.",
parameters = {
@Parameter(name = "pageIndex", description = "페이지 번호", example = ""),
@Parameter(name = "pageRowSize", description = "페이지 열 개수", example = ""),
@Parameter(name = "searchStartDatetime", description = "검색기간 시작일(yyyy-MM-dd HH:mm)", example = "2021-01-01 13:00"),
@Parameter(name = "searchEndDatetime", description = "검색기간 종료일(yyyy-MM-dd HH:mm)", example = "2021-01-01 13:59"),
@Parameter(name = "searchKeywordType", description = "검색 키워드 타입(2 : 아이디 | 1: 공고번호", example = ""),
@Parameter(name = "searchKeyword", description = "검색 키워드", example = ""),
@Parameter(name = "jobType", description = "직종구분", example = ""),
@Parameter(name = "jobTypeDetail", description = "직종구분 상세", example = ""),
@Parameter(name = "noHasLogo", description = "로고 ", example = ""),
@Parameter(name = "noHasPhoto", description = "이미지 ", example = ""),
@Parameter(name = "noHasHomepage", description = "홈페이지 ", example = ""),
@Parameter(name = "isHistory", description = "기업정보 내역 여부", example = "")
}
)
@GetMapping("/admin/management/company/etc-information")
public ResponseEntity<CompanyPhotoCollectionResponse> getCompanyInformations(
@RequestParam(value = "pageIndex", required = false, defaultValue = "1") int pageIndex,
@RequestParam(value = "pageRowSize", required = false, defaultValue = "10") int pageRowSize,
@RequestParam(value = "searchStartDatetime", required = false, defaultValue = "") String searchStartDate,
@RequestParam(value = "searchEndDatetime", required = false, defaultValue = "") String searchEndDate,
@RequestParam(value = "searchKeywordType", required = false, defaultValue = "") SearchKeywordType searchKeywordType,
@RequestParam(value = "searchKeyword", required = false, defaultValue = "") String searchKeyword,
@RequestParam(value = "jobType", required = false, defaultValue = "") String jobType,
@RequestParam(value = "jobTypeDetail", required = false, defaultValue = "") String jobTypeDetail,
@RequestParam(value = "noHasLogo", required = false, defaultValue = "") boolean noHasLogo,
@RequestParam(value = "noHasPhoto", required = false, defaultValue = "") boolean noHasPhoto,
@RequestParam(value = "noHasHomepage", required = false, defaultValue = "") boolean noHasHomepage,
@RequestParam(value = "isHistory", required = false, defaultValue = "") boolean isHistory
) {
return null;
}
@ParameterObject 그리고 @AllArgsConstructor 2가지로 처리하는 방법
우선 Request DTO를 선언합니다.
아래처럼 @ParameterObject , @AllArgsConstructor, @Getter ,@Builder 를 사용합니다.
주의사항 : @NoArgsConstructor 은 사용하지 않습니다.
(해당 annotation 을 사용하게되면 Api호출시 정상적으로 데이터가 전달되지 않는것이 확인되었습니다.)
@Getter
@Builder
@AllArgsConstructor
@ToString
@ParameterObject
public class MockReport {
@JsonInclude(JsonInclude.Include.NON_NULL)
@Parameter(description = "부적합/마감 타입 ")
private RepotyType repotyType;
@JsonInclude(JsonInclude.Include.NON_NULL)
@Parameter(description = "채용정보타입")
private ReasonType reasonType;
@JsonInclude(JsonInclude.Include.NON_NULL)
@Parameter(description = "첨부파일")
private String attachFile;
@DateTimeFormat(pattern = "yyyy-MM-dd'T'HH:mm:ss")
@JsonInclude(JsonInclude.Include.NON_NULL)
@Parameter(description = "마감일")
private LocalDateTime deadlineDateTime;
}
아래처럼 ModelAttriubute 를사용하게되면 보통은 @Setter 가 DTO에 존재해야하지만
@ParameterObject 그리고 @AllArgsConstructor 을 사용하게되면 @Setter 없이 데이터를 전달받을수 있습니다.
@Operation(
summary = "정보조회",
description = "정보조회"
)
@GetMapping("/recruit/report")
public ResponseEntity<ReportResponse> report(@ModelAttribute MockReport report){
log.info("report : {}",report.toString());
return null;
}
테스트 코드를 만들어서 검증 해보도록 하겠습니다.
...
..
.
.
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.web.servlet.MockMvc;
import org.springframework.test.web.servlet.setup.MockMvcBuilders;
import org.springframework.util.LinkedMultiValueMap;
import org.springframework.util.MultiValueMap;
import org.springframework.web.context.WebApplicationContext;
import org.springframework.web.filter.CharacterEncodingFilter;
@SpringBootTest
@AutoConfigureMockMvc
class RecruitmentViewControllerTest {
@Autowired
protected MockMvc mockMvc;
@Autowired
private WebApplicationContext ctx;
@BeforeEach
void setUp() {
this.mockMvc = MockMvcBuilders.webAppContextSetup(ctx)
.addFilter(new CharacterEncodingFilter("UTF-8",true))
.alwaysDo(print())
.build();
}
@AfterEach
void tearDown() {
}
@Test
void 테스트조회() throws Exception {
MultiValueMap<String,String> params = new LinkedMultiValueMap<>();
params.add("repotyType","0");
params.add("reasonType","1");
params.add("attachFile","123");
params.add("deadlineDateTime","2022-01-01T12:01:01");
mockMvc.perform(get("/recruit/report")
.params(params)
)
.andDo(print());
}
}
테스트코드를 실행해보면 @ModelAttribute 에 Get방식인데 @Setter 없이 데이터 바인드 된것을 확인할수 있습니다.
[INFO ,] 2022-06-23 10:11:11.831 [Test worker][RecruitViewController.java] [RecruitViewController.report] - report : MockReport(repotyType=REPORT_TYPE_01, reasonType=REPORT_TYPE_02, attachFile=123, deadlineDateTime=2022-01-01T12:01:01)
[INFO ,] 2022-06-23 10:11:11.831 [Test worker][RecruitViewController.java] [RecruitViewController.report] - report : MockReport(repotyType=REPORT_TYPE_01, reasonType=REPORT_TYPE_02, attachFile=123, deadlineDateTime=2022-01-01T12:01:01)
[DEBUG ,] 2022-06-23 10:11:11.847 [Test worker][AbstractMessageConverterMethodProcessor.java] [AbstractMessageConverterMethodProcessor.writeWithMessageConverters] - Using 'application/json', given [*/*] and supported [application/json, application/*+json, application/json, application/*+json, application/cbor]
[DEBUG ,] 2022-06-23 10:11:11.848 [Test worker][AbstractMessageConverterMethodProcessor.java] [AbstractMessageConverterMethodProcessor.writeWithMessageConverters] - Nothing to write: null body
[DEBUG ,] 2022-06-23 10:11:11.851 [Test worker][FrameworkServlet.java] [FrameworkServlet.logResult] - Completed 200 OK
MockHttpServletRequest:
HTTP Method = GET
Request URI = /recruit/report
Parameters = {repotyType=[REPORT_TYPE_01], reasonType=[REPORT_TYPE_02], attachFile=[123], deadlineDateTime=[2022-01-01T12:01:01]}
Headers = []
Body = null
Session Attrs = {}
하지만 DTO에 @NoArgsConstructor 을 추가하게되면 null로 전달받는것을 확인할수 있습니다.
[INFO ,] 2022-06-23 10:03:10.282 [Test worker][RecruitViewController.java] [RecruitViewController.report] - report : MockReport(repotyType=null, reasonType=null, attachFile=null, deadlineDateTime=null)
[DEBUG ,] 2022-06-23 09:51:35.048 [Test worker][LogFormatUtils.java] [LogFormatUtils.traceDebug] - GET "/recruit/report", parameters={masked}
[DEBUG ,] 2022-06-23 09:51:35.060 [Test worker][AbstractHandlerMapping.java] [AbstractHandlerMapping.getHandler] - Mapped to albamon.general.recruitment.view.RecruitViewController#report(MockReport)
[INFO ,] 2022-06-23 09:51:35.187 [Test worker][RecruitViewController.java] [RecruitViewController.report] - report : MockReport(repotyType=null, reasonType=null, attachFile=null, deadlineDateTime=null)
[DEBUG ,] 2022-06-23 09:51:35.204 [Test worker][AbstractMessageConverterMethodProcessor.java] [AbstractMessageConverterMethodProcessor.writeWithMessageConverters] - Using 'application/json', given [*/*] and supported [application/json, application/*+json, application/json, application/*+json, application/cbor]
[DEBUG ,] 2022-06-23 09:51:35.204 [Test worker][AbstractMessageConverterMethodProcessor.java] [AbstractMessageConverterMethodProcessor.writeWithMessageConverters] - Nothing to write: null body
[DEBUG ,] 2022-06-23 09:51:35.207 [Test worker][FrameworkServlet.java] [FrameworkServlet.logResult] - Completed 200 OK
MockHttpServletRequest:
HTTP Method = GET
Request URI = /recruit/report
Parameters = {repotyType=[0], reasonType=[1], attachFile=[123], deadlineDateTime=[2022-01-01T12:01:01]}
Headers = []
Body = null
Session Attrs = {}
이번에 GET 방식으로 사용하고 Setter 없이 @ModelAttribute 사용하는방법에 대해서 코드와 테스트 까지 진행해봤는데
여러분도 저처럼 필요하시다면 참고하시기 바랍니다.
다들 고생하세요 :)
'Backend 개발자 > StackOverflow' 카테고리의 다른 글
Rest API 호출시 보일러 플레이트 코드를 리펙토링 하는 방법, 같은 모양의 양을 줄여보시오!! (0) | 2022.08.09 |
---|---|
MSA 아키텍처 프로젝트 빅뱅 방식으로 God Object 처리시 발생하는 Tell Dont ask (0) | 2022.08.08 |
Rest Api 개발할때 @Setter 안쓰고 조회기능 만들기 Get Post 언제 상황에 맞게 써야하나요? (0) | 2022.05.26 |
JAVA Apache Log4j CVE-2021-44228 보안 공격대상 원격코드 실행 취약점 CVSS 10.0 문제점 파악 및 조치 방법 20 (0) | 2021.12.13 |
브라우저 와 서버 사이를 이어주는 쿠키(Cookie) 세션(Session) 토큰(Token) JWT 간단한 정리 (0) | 2021.11.02 |