Ajax 코드를 살펴봐도, Postman을 통해 보내는 데이터의 필드명을 확인해도 DTO의 필드명과 다른 게 없다. 그런데 데이터가 안 들어온다. 호출된 메서드 이름만 나올 뿐 파라미터는 나오지 않는 로그를 보며 난데없는 고민이 시작됐다.
실행 위치와 메서드 이름만 나오고 입력된 파라미터가 표시되지 않는다다행인 건 전날 Postman을 사용해 전송한 데이터를 로깅하면서, 필드 명에 대문자가 섞였던 게 모두 소문자로 변환되는 경우가 있었다는 걸 기억해낸 것이었다. 필드를 모두 소문자로 처리해서 API를 테스트해 보니 역시나 Postman은 정상적인 값을 리턴하기 시작했다. 전날엔 쳐야 할 코드가 제법 많아서 어물쩍 넘어갔는데, 다시 생각해 보니 이건 생각보다 큰 문제였다. 데이터를 의도대로 전송할 수 없다면 API 문서도 작성할 수 없을 터였다. 요행으로 문제를 해결했으니 이젠 원인을 찾아야 했다.
스프링은 HttpMessageBody를 HttpMessageConverter를 사용해서 읽는다. JSON을 읽는 건Jackson2HttpMessageConverter가 한다. Jackson은 Java Beans 규약을 따르는데, JavaBeans가 필드명을 처리하는 방식은 아래와 같다.
- 필드명의 첫 두글자가 대문자라면 그대로 반환한다.
- 그 외의 경우 첫 글자만 소문자로 변환하여 반환한다.
여기서 내가 작성한 DTO클래스의 일부를 가져와보면
@Getter
@Setter
@ToString
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class TodoDTO {
private int tNo;
private String tTitle;
private String tText;
}
모든 필드가 Todo의 약자인 t를 첫 자로 한 카멜케이스로 작성되어 있다. 이것만 보면 문제가 없다. 첫 글자는 이미 소문자이고, 두 번째 글자만 대문자이기 때문에 Java Beans 규약을 따르는 Jackson은 위의 필드명을 그대로 받아줘야 할 것 같다.
여기서 Jackson이 Java Beans와 다른 규칙이 하나 있다. 이녀석은 첫 두글자가 대문자인 경우 두 글자 모두 소문자로 치환해서 반환하게 된다. 이 경우에도 소문자+대문자로 되어있는 필드명에는 문제가 없을 것 같지만, 별도의 설정이 없다면 이녀석은 필드명이 아니라 Getter메서드를 사용해서 데이터를 바인딩한다. Lombok으로 날로 먹은 나의 Getter메서드는 다음과 같다.
필드명 : tNo, tTitle, tText
Getter : getTNo(), getTTitle(), getTText()
Getter메서드의 앞 두글자가 모두 대문자였으니, 그 둘을 소문자로 바꾸고나면 모든 필드명이 소문자로 바뀌는 것 처럼 보였던 것이었다. 스프링이 나를 괴롭힌 게 아니었다. 첫번째 문제는 Jackson이니 Java Beans니 아무것도 모르고 @RequestBody를 남발한 내게 있는 것이고, 둘째는 앞뒤 생각 없이 변수 명을 거지깽깽이처럼 지어놓은 내게 있는 것이다.
데이터 송수신에서 일어나는 필드명 불일치를 해결하려면 변수명을 모조리 수정하거나, API문서를 작성할 때마다 필드명과 Getter메서드를 확인하면서 써야 하는 절망적인 상황일뻔... 했으나 다행히도 @JsonProperty라는 어노테이션을 사용하면 기존 필드명 그대로 사용하더라도 문제가 없다고 한다. 그나마 나은 상황이긴 하지만 머리가 나쁘면 몸이 고생이라고, 처음부터 이런 내용을 인지하고 변수 이름을 잘 지으면 될 일이다. 내일의 나는 좀 더 똑똑하길 바라면서, 오늘도 몸으로 때워 머리를 채운다.