새로운 블로그로 이전하였습니다!

웹에서 보낸 Request Parameter를 Controller 에서 처리해야 하는데

같은 로직에 다양한 VO / DTO 를 넣어줘야 할 경우가 있다.

VO/DTO 마다 Controller를 새로 구현하여 데이터를 수신받는 것은 중복된 코드가 많아지고 유지보수도 귀찮아진다.

필드상에 VO/DTO를 선언하지 않고 하나의 Controller 에서 데이터를 수신할 수 있는 방법 2가지와

수신받은 데이터를 VO / DTO에 매핑시킬 수 있게 ObjectMapper 라이브러리를 사용한 방식에 대해 작성하였다.

매핑시킬 VO

@Data
public class Person {
    private int age;
    private String name;
    private String birth;
}

서버로 데이터 전송

모든 Parameter를 HashMap 으로 받기

JavaScript

<body>
	<button onclick="submitHandler()">SUBMIT</button>
</body>

const submitHandler = () => {
      let formData = new FormData();
      formData.append("dtoName", "Person"); // DTO 클래스 명
      formData.append("age", "26"); // DTO 멤버변수
      formData.append("name", "kdh");
      formData.append("birth", "971001");
      // formData 값 확인
      console.log(...formData);

      fetch('http://localhost:9003/input', {
          method: 'POST',
          cache: 'no-cache',
          body: formData // body 부분에 폼데이터 변수를 할당
      })
          .then((response) => response.json())
          .then((data) => {
             if(data.result === 1) {
                console.log(data);
             }
             else {
                 console.log("요청 실패");
             }
         });
}

dto 의 클래스명과 각 멤버변수의 값을 formData에 넣고 넘겨준다.

Controller

@RestController
@RequiredArgsConstructor // lombok
public class Controller {

    public final DtoMappingService dtoMappingService;

    @PostMapping("/input")
    @ResponseBody
    public ResponseEntity<?> dtoMapping( @RequestParam HashMap<String, Object> item ) {
        Map<String, Object> map = new LinkedHashMap<String, Object>();
    
        dtoMappingService.dtoMapping( item );
        
        if( )
            map.put("result", 1);
        else
            map.put("result", 0);
        return new ResponseEntity<Map<String, Object>>(map, HttpStatus.OK);
}

@RequestParam 어노테이션 안에 아무런 명칭을 지정하지 않고 HashMap을 사용하면

모든 데이터가 key:value 형태로 들어가게 된다.

Service

@Service
public class DtoMappingService{
    public void dtoMapping(HashMap<String, Object> item) {

        List<RuleItemDto> resultList = new ArrayList<>();
        ObjectMapper mapper = new ObjectMapper();
        String dtoName = item.getKey("dtoName");
        item.remove("dtoName"); // Object Mapping 시 없는 멤버변수라서 지워야 함

        try
        {
            Object item = mapper.convertValue(item, Class.forName("패키지 경로." + dtoName ).newInstance().getClass());
        catch ( ClassNotFoundException cnfe ) {
            System.out.println( cnfe );
        }
}

Dto 내 Map에 매핑

javascript

<body>
	<button onclick="submitHandler()">SUBMIT</button>
</body>

const submitHandler = () => {
      let formData = new FormData();
      formData.append("dtoName", "Person"); // DTO 클래스 명
      formData.append("item[age]", "26"); // DTO 멤버변수
      formData.append("item[name]", "kdh");
      formData.append("item[birth]", "971001");
      // formData 값 확인
      console.log(...formData);

      fetch('http://localhost:9003/input', {
          method: 'POST',
          cache: 'no-cache',
          body: formData // body 부분에 폼데이터 변수를 할당
      })
          .then((response) => response.json())
          .then((data) => {
             if(data.result === 1) {
                console.log(data);
             }
             else {
                 console.log("요청 실패");
             }
         });
}

formData.append 함수의 name 부분을 "map변수명[key]" 로 입력 시 Map 의 key 로 들어가게 된다.

Dto

@Data // lombok
public class InputDto{
    private String dtoName;
    private HashMap<String, Object> item;
}

Controller

@RestController
@RequiredArgsConstructor // lombok
public class Controller {

    public final DtoMappingService dtoMappingService;

    @PostMapping("/input")
    public ResponseEntity<?> dtoMapping( InputDto dto ) {
        Map<String, Object> map = new LinkedHashMap<String, Object>();
    
        dtoMappingService.dtoMapping( dto );

        if( 로직 성공 시 )
            map.put("result", 1);
        else
            map.put("result", 0);
        return new ResponseEntity<Map<String, Object>>(map, HttpStatus.OK);
}

Controller 의 매개변수 부분에 Dto를 넣어주면 formData 의 name 에 알아서 맞춰서 들어가진다.

Service

@Service
public class DtoMappingService{
    public void dtoMapping( InputDto item) {

        List<RuleItemDto> resultList = new ArrayList<>();
        ObjectMapper mapper = new ObjectMapper();

        try
        {
            Object item = mapper.convertValue(dto.getItem(), Class.forName("패키지 경로." + dto.getDtoName() ).newInstance().getClass());
        catch ( ClassNotFoundException cnfe ) {
            System.out.println( cnfe );
        }
}

1)은 모든 Parameter를 HashMap으로 받아서 dtoName을 제거해줘야 했지만

원하는 값만 HashMap으로 받았기 때문에 1번 처럼 dtoName을 제거하는 과정이 필요가 없어진다.

 

Dto 내 List에 매핑

formData를 받을 DTO를 생성한 후 formData에서 DTO의 멤버변수에 맞게 삽입해준다.

위 방법들의 단점과는 다르게 원하는 값만 List안에 넣을 수 있고, json으로 파싱하지 않아도 된다.

javascript

const submitButton = document.getElementById("submitButton");

function submit () {
      let formData = new FormData();
      formData.append("dtoName", "Person"); // DTO 클래스 명
      itemList.forEach((item, index) => {
      	formData.append("itemLiet[" + i + "].key", item);
        formData.append("itemLiet[" + i + "].value", document.getElementById(item).value);
      });
      // formData 값 확인
      console.log(...formData);

      fetch('http://localhost:9003/input', {
          method: 'POST',
          cache: 'no-cache',
          body: formData // body 부분에 폼데이터 변수를 할당
      })
          .then((response) => response.json())
          .then((data) => {
             if(data.result === 1) {
                console.log(data);
             }
             else {
                 console.log("요청 실패");
             }
         });
}
submitButton.addEventListener("click", submit);

age, name, birth를 갖는 itemList와 그걸 id로 갖는 HTML Input Element가 있다고 치자..

DTO

@Data // lombok
public class ItemDto{
    private String key;
    private Object value;
}

JSON 배열을 받을 수 있는 Dto를 생성해준다.

( VO에 매핑시킬 목적이 아니라면 꼭 key value 형식일 필요는 없다. )

@Data // lombok
public class InputDto {
    private String dtoName;
    private List<ItemDto> itemList;
}

Controller

@RestController
@RequiredArgsConstructor // lombok
public class Controller {

    public final DtoMappingService dtoMappingService;

    @PostMapping("/input")
    @ResponseBody
    public ResponseEntity<?> dtoMapping( InputDto dto ) {
        Map<String, Object> map = new LinkedHashMap<String, Object>();
    
        dtoMappingService.dtoMapping( dto );

        if( 로직 성공 시 )
            map.put("result", 1);
        else
            map.put("result", 0);
        return new ResponseEntity<Map<String, Object>>(map, HttpStatus.OK);
}

Service

( VO 매핑이 굳이 필요 없다면 그냥 List 가져가서 사용해도 된다. )

@Service
public class DtoMappingService{
    public void dtoMapping( InputDto dto ) {

        List<RuleItemDto> resultList = new ArrayList<>();
        ObjectMapper mapper = new ObjectMapper();
        Map<String ,Object> map = new HashMap<>();

        for (ItemDto itemDto : dto.getItemList())
            map.put(itemDto.getKey(), itemDto.getValue());

        try
        {
            Object item = mapper.convertValue(dto.getItem(), Class.forName("패키지 경로." + dto.getDtoName() ).newInstance().getClass());
        catch ( ClassNotFoundException cnfe ) {
            System.out.println( cnfe );
        }
}

 

json 으로 파싱하여 전송

1) 과의 차이점은 모든 데이터가 HashMap으로 구분없이 들어가지만

json으로 전송 시 json의 Key 값을 이용해 DTO에 매핑시킬 수 있다.

대신 단점으로는 json 으로 파싱하는 과정이 추가된다.

JavaScript

const submitButton = document.getElementById("submitButton");

function submit () {
      
      let jsonData = {
         "dtoName": "Person",
         "item": {
            "age":"26"
            "name":"kdh"
            "birth":"971001"
         };

      fetch('http://localhost:9003/input', {
          method: 'POST',
          cache: 'no-cache',
          headers: {
                "Content-Type" : "application/json" // 기본 타입이 x-www.form-urlencoded 라서 바꿔줘야 함
          },
          body: JSON.stringfiy( jsonData ) // json 형식 데이터를 문자열로 변환
      })
          .then((response) => response.json())
          .then((data) => {
             if(data.result === 1) {
                console.log(data);
             }
             else {
                 console.log("요청 실패");
             }
         });
}
submitButton.addEventListener("click", submit);

Dto

@Data // lombok
public class InputDto{
    private String dtoName;
    private Object item; // json 배열. HashMap도 가능
}

변수명은 json의 key 와 일치하게 작성해준다.

HashMap 으로 하면 json 배열의 key/value 가 HashMap의 key/value로 삽입된다.

Controller

@RestController
@RequiredArgsConstructor // lombok
public class Controller {

    public final DtoMappingService dtoMappingService;

    @PostMapping("/input")
    public ResponseEntity<?> dtoMapping( @RequestBody InputDto dto ) {
        Map<String, Object> map = new LinkedHashMap<String, Object>();
    
        dtoMappingService.dtoMapping( dto );

        if( 로직 성공 시 )
            map.put("result", 1);
        else
            map.put("result", 0);
        return new ResponseEntity<Map<String, Object>>(map, HttpStatus.OK);
}

Service

@Service
public class DtoMappingService{
    public void dtoMapping( InputDto item) {

        List<RuleItemDto> resultList = new ArrayList<>();
        ObjectMapper mapper = new ObjectMapper();

        try
        {
            Object item = mapper.convertValue(dto.getItem(), Class.forName("패키지 경로." + dto.getDtoName() ).newInstance().getClass());
        catch ( ClassNotFoundException cnfe ) {
            System.out.println( cnfe );
        }
}

 

매핑 된 값 추출

이 후 공통 로직을 구현할 때는 Field 클래스를 사용하여 값을 추출해내면 됩니다.

특정 멤버변수

Field field = ReflectionUtils.findField(item.getClass(), 멤버변수명);
Objects.requireNonNull(field).setAccessible(true); // private 접근 허용

String key = field.getName(); // 변수명
Object value = field.get(item).toString(); // 값

 

모든 멤버변수

for (Field field : item.getClass().getDeclaredFields()){
    field.setAccessible(true);
    field.getName(); // 멤버변수명 return
    field.get(item).toString(); // 멤버변수 값 return
}
복사했습니다!