Backend 개발자/StackOverflow

Springboot Rest API GetMapping 방식 @Setter 없이 @ParameterObject, @AllArgsConstructor 2가지로 @ModelAttribute 사용 하는 방법

by 앵과장 2022. 6. 23. 10:16
반응형

안녕하세요

앵과장입니다.

 

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 사용하는방법에 대해서 코드와 테스트 까지 진행해봤는데 

여러분도 저처럼 필요하시다면 참고하시기 바랍니다. 

 

다들 고생하세요 :)