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;
}
- 코드를 최소화하고
- 생성자, Builder, Getter/Setter 등을 자동 생성
- 특히 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가 나오는 경우가 발생할 수 있습니다.
해결 방법
- @EqualsAndHashCode(of = "id")로 명시적으로 설정
- 또는 @Data 대신 @Getter, @Setter를 개별 설정
@Getter
@Setter
@NoArgsConstructor
@AllArgsConstructor
@EqualsAndHashCode(of = "id")
public class User {
private Long id;
private String username;
}
4. 생성자 기반 매핑이 필요한 경우
기본적으로 MyBatis는 setter 기반 매핑을 합니다.
하지만 다음과 같은 상황에서는 생성자 매핑을 고려할 수 있습니다:
- Immutable DTO 사용
- final 필드
- 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 어노테이션을 적절히 조합해야 안정적인 코드가 완성됩니다.
댓글