[MyBatis] MyBatis와 @Builder 사용 시 resultMap 매핑 문제 해결하기
1. 문제 상황
MyBatis를 사용할 때 일반적으로 xml의 <resultMap> 을 통해 SQL 조회 결과를 DTO에 매핑한다.
기본적인 @Getter, @Setter 가 있는 DTO 에서는 resultMap 의 순서와 DTO 필드 순서가 맞지 않더라도 필드 이름을 기준으로 매핑되기 때문에 큰 문제가 발생하지 않는다.
하지만 @Builder 를 사용한 DTO 에서는 resultMap 의 필드 순서와 DTO 의 필드 순서가 맞지 않으면 매핑 오류가 발생할 수 있다.
2. 코드 예시
<select id="getDB" resultMap="BuilderMap">
SELECT *
FROM TABLE_A
</select>
<resultMap id="BuilderMap" type="BuilderDto">
<result property="age" column="AGE" />
<result property="name" column="NAME" />
</resultMap>
@Getter
@Builder
public class BuilderDto {
private String name;
private int age;
}
3. 원인
Java 에서는 일반적으로 모든 클래스가 디폴트로 기본 생성자 (빈 생성자)를 가지고 있지만, 롬복의 @Builder 를 사용하면 이 기본 생성자가 사라진다.
@Builder 는 모든 필드를 초기화할 수 있는 모든 인수 생성자를 생성하는데, 이로 인해 기본 생성자가 자동으로 생성되지 않게 된다.
MyBatis 는 결과 매핑 시 다음과 같은 방식을 사용한다
1. 빈 생성자를 호출해 객체를 생성한다
2. 필드에 SQL 결과를 바인딩하는 과정으로 값을 채워 넣는다.
기본 생성자가 없는 상태에서 MyBatis가 빈 인스턴스를 생성하려고 하면, MyBatis는 생성자 호출에 실패하게 되고, 그 결과 매핑 오류나 타입 불일치 오류가 발생하는 것이다.
4. 해결 방법
@NoArgsConstructor 추가: @Builder 와 함께 @NoArgsConstructor 를 추가하여 MyBatis가 기본 생성자를 사용해 객체를 생성할 수 있게 한다.
5. 기본 생성자가 없어도 resultMap 의 필드 순서와 DTO 의 필드 순서가 정확히 일치한다면 오류가 발생하지 않는다.
MyBatis가 @Builder 를 사용한 DTO 에서도 필드 순서와 이름이 정확히 맞을 경우 내부적으로 생성자나 매핑 로직에서 필드 순서로 값을 맞추어 바인딩 할 수 있기 때문이다. 하지만 @NoArgsConstructor 어노테이션을 추가해서 매핑 오류를 예방하는 것이 안전하다.