본문 바로가기
Development/Spring Boot

Map에 mybatis의 map-underscore-to-camel-case가 안먹히는 이유를 알아보자

by Nahwasa 2024. 2. 21.

 

  관련된 질문을 받아 이유를 찾아보기 위해 mybatis 소스를 까보게 되었다. 찾아본김에 혹시 궁금할 사람도 있을 것 같아 어느 부분에 의해 Map을 사용 시 map-underscore-to-camel-case 옵션이 안먹히는지 공유하려고 한다.

 

map-underscore-to-camel-case 사용 이유

  테이블의 칼럼명이 phone_number 이런식으로 언더바가 들어간 형태로 되어 있는 경우가 있다. 이걸 받기 위해 dto의 변수도 'String phone_number;' 처럼 언더바가 들어간 형태로 가자니 자바의 기본적인 카멜 케이스 형태랑 안맞아서 멋없다.

setter도 'setPHONE_NUMBER' 이런식으로 들어가야하니 더더욱 멋없다. 그래서 String phoneNumber; / setPhoneNumber와 같이 사용할 수 있도록 해주는게 mybatis의 map-underscore-to-camel-case 옵션이다.

 

  application.properties 또는 application.yml에 다음처럼 적용하거나,

 

  따로 mybatis-config.xml 같은걸 만들어서 이하와 같이 옵션을 넣을 수 있다.

<setting name="mapUnderscoreToCamelCase" value="true"/>

 

 

근데 Map 에선 안먹힌다.

  물론 다들 dto로 하고싶긴 하겠지만, 경우에 따라 그냥 resultType="Map" 처럼 받아야 할 수도 있다. 이 경우에도 phoneNumber처럼 카멜 케이스로 받고 싶지만, key값이 그냥 "phone_number" 처럼 들어온다.

 

 

mybatis 코드의 어느 부분때문에 Map에선 안될까?

  mybatis에서 단순 select 쿼리를 디버깅 걸어 따라가보며 확인해보았다. 스프링부트 3.2.2에서 mybatis-spring-boot-starter 3.0.3 버전으로 수행했다.

 

  우선 설정한 옵션은 mybatis-spring-boot-autoconfigure에서 AutoConfiguration.imports에 등록된 MybatisAutoConfiguration에서 등록되게 된다.

 

 

  map-underscore-to-camel-case 옵션은 MybatisProperties.CoreConfiguration에 들어간다.

 

 

  그럼 이제 간단한 select문 같은거 돌려서 디버깅 걸면서 따라가볼 차례이다. 참고로 해당 부분이 결정되는 부분까지 따라가보면 스택 depth가 상당히 깊다. 그래서 다 작성하진 않았고, 중간중간 작성했다.

 

  우선 MapperProxy<T>.invoke() 로 시작된다. 그리고 쭉쭉 들어가다보면 execute로 쿼리마다 분기쳐서 실행되는 부분이 있다. (MapperMethod.execute())

 

 

  더 들어가보면 BaseExecutor.query() 에서 queryFromDatabase가 불린다.

 

 

  더 들어가다보면 MetaObject 생성자 부분이 있고, 여기서 MapWrapper, BeanWrapper에 따라 우리가 찾는 동작이 갈리게 된다.

 

  저 applyAutomaticMappings가 수행될 때 metaObject가 함께 들어가고, 

 

 

  여기서 findProperty 함수를 부르게 되는데 이 때 처음에 설명한 configuration.isMapUnderscoreToCamelCase 설정이 인자로 들어간다.

 

 

  그리고 metaObject에 따라 호출되는 findProperty 함수가 달라지게 된다.

 

Map으로 리턴시에는 MapWrapper의 findProperty가 불리는데 다음과 같다. 즉, useCamelCaseMapping 받은건 무시하고 그냥 바로 name을 리턴해준다. 따라서 저 옵션을 암만 줘도 key값이 그대로 "phone_number"일 수 밖에 없다.

 

 

반면에 dto로 리턴시에는 BeanWrapper의 findProperty가 불리게 되어 언더바를 없앤다.

 

 

최종적으로 언더바가 제거된 property를 가지고 세터를 찾아 값이 박히게 된다.

 

 

  결론적으로 Map에서는 카멜케이스로 변경되지 않은채로 칼럼명 그대로 들어가게 되고, dto는 카멜케이스가 변경된 setter 함수를 찾아 값이 들어가게 되는 것이다.

댓글