TokyoAJ

도쿄아재

SPRINGBOOT 2025.04.14

MyBatis와 Lombok 함께 사용할 때 주의할 점

Lombok은 코드 작성을 획기적으로 줄여주는 인기 라이브러리이고,

MyBatis는 SQL을 자유롭게 컨트롤할 수 있는 강력한 Mapper 프레임워크입니다.

두 기술은 많이 함께 쓰이지만,

실제로는 Lombok의 기능과 MyBatis의 내부 작동 방식이 충돌하거나 예상치 못한 버그를 만드는 경우가 있습니다.

이 글에서는 MyBatis + Lombok 조합 시 자주 발생하는 문제와 해결 방법을 실제 예제와 함께 정리해봅니다.


먼저, Lombok이 주는 이점

@Getter
@Setter
@NoArgsConstructor
@AllArgsConstructor
@Builder
public class User {
private Long id;
private String username;
private String email;
}
  1. 코드를 최소화하고
  2. 생성자, Builder, Getter/Setter 등을 자동 생성
  3. 특히 DTO나 Entity에서 매우 유용

그런데 이게 MyBatis와 함께 쓸 때, 문제가 생길 수 있습니다.


1. 기본 생성자(@NoArgsConstructor)가 없으면 오류 발생

문제

MyBatis는 기본 생성자를 이용해 객체를 생성하고,

각 필드를 setter를 통해 주입합니다.

하지만 @AllArgsConstructor만 붙이고 @NoArgsConstructor를 안 붙이면 다음과 같은 에러가 발생할 수 있습니다:

org.apache.ibatis.executor.ExecutorException:
Cannot create a new instance of class com.example.dto.User.
Constructor is missing


해결 방법

@Getter
@Setter
@NoArgsConstructor // 반드시 필요!
@AllArgsConstructor
@Builder
public class User {
...
}


2. @Builder 사용 시 주의

@Builder는 생성자 기반으로 필드를 초기화합니다.

그런데 MyBatis는 setter 방식으로 필드를 주입하기 때문에

Builder만 존재하고 기본 생성자+setter가 없으면 정상 작동하지 않습니다.

잘못된 예

@Getter
@Builder
public class User {
private String username;
private String email;
}

MyBatis는 이 DTO를 사용할 수 없습니다 (setter 없음).


해결 방법

@Getter
@Setter
@NoArgsConstructor
@AllArgsConstructor
@Builder
public class User {
private String username;
private String email;
}

@Builder를 쓰되, @Setter, @NoArgsConstructor는 반드시 같이 써야 함


3. equals/hashCode 문제

MyBatis의 캐시나 컬렉션 처리에서는 equals()/hashCode()가 정확히 동작해야 합니다.

하지만 Lombok의 기본 @Data는 모든 필드로 equals/hashCode를 자동 생성하기 때문에

id만 같아도 equals가 false가 나오는 경우가 발생할 수 있습니다.

해결 방법

  1. @EqualsAndHashCode(of = "id")로 명시적으로 설정
  2. 또는 @Data 대신 @Getter, @Setter를 개별 설정
@Getter
@Setter
@NoArgsConstructor
@AllArgsConstructor
@EqualsAndHashCode(of = "id")
public class User {
private Long id;
private String username;
}


4. 생성자 기반 매핑이 필요한 경우

기본적으로 MyBatis는 setter 기반 매핑을 합니다.

하지만 다음과 같은 상황에서는 생성자 매핑을 고려할 수 있습니다:

  1. Immutable DTO 사용
  2. final 필드
  3. Record 클래스 (Java 16+)

예시: 생성자 매핑 사용

@ConstructorArgs({
@Arg(column = "id", javaType = Long.class),
@Arg(column = "username", javaType = String.class)
})
@Select("SELECT id, username FROM user WHERE id = #{id}")
User selectById(Long id);

일반적인 경우에는 setter + 기본 생성자 방식이 더 안전합니다.


실무 적용 팁 정리

상황주의 사항 / 권장 설정
일반 DTO 매핑@NoArgsConstructor + @Setter 필수
@Builder 사용 시반드시 @Setter 함께
equals/hashCode@EqualsAndHashCode(of = "id") 로 제어
Entity 클래스@Getter, @Setter, @NoArgsConstructor, @AllArgsConstructor 조합 추천
Record 클래스 사용생성자 매핑(@ConstructorArgs) 필요
Mapper의 resultType 사용 시DTO에 alias 필드명 정확히 매핑 필요


마무리

MyBatis와 Lombok은 함께 쓸 수 있는 강력한 조합이지만,

Lombok이 생성해주는 코드의 구조를 MyBatis가 그대로 인식하는 건 아닙니다.

따라서 MyBatis가 어떻게 객체를 주입하는지를 이해하고

그에 맞춰 Lombok 어노테이션을 적절히 조합해야 안정적인 코드가 완성됩니다.

댓글