Skip to the content.

자바(Java) 진영에서 보일러플레이트(Boilerplate) 코드를 획기적으로 줄여주는 라이브러리를 꼽으라면 단연 롬복(Lombok)일 것입니다. 개발자가 게터(Getter), 세터(Setter), 생성자(Constructor) 등을 직접 타이핑하지 않아도 어노테이션 하나로 컴파일러가 알아서 코드를 완성해 줍니다. 이번 포스팅에서는 롬복의 핵심 동작 메커니즘과 편리한 어노테이션인 @Data를 실무에서 무분별하게 사용할 때 마주칠 수 있는 심각한 위험성에 대해 다뤄보겠습니다.


1. 롬복(Lombok)의 동작 원리

롬복은 일반적인 자바 라이브러리처럼 런타임에 리플렉션(Reflection)을 기반으로 동작하는 것이 아닙니다. 롬복은 컴파일 시점에 코드를 수정하는 독특한 메커니즘을 사용합니다.

[자바 컴파일러(javac) 과정]
자바 소스 파일(.java)
    ↓
구문 분석 (AST - 추상 구문 트리 생성)
    ↓
어노테이션 프로세서 실행 (★ Lombok이 개입하여 AST 구조를 강제로 조작/수정)
    ↓
바이트코드 생성 (.class)

자바 컴파일러가 소스 코드를 읽어 구문 분석(Parsing)을 마치면 메모리에 AST(Abstract Syntax Tree, 추상 구문 트리)라는 구조화된 트리 데이터가 생성됩니다. 이때 롬복 어노테이션 프로세서가 개입하여 이 AST를 물리적으로 개조합니다. 예컨대 @Getter가 붙어 있다면 AST 내부에 public Object getXxx() 메소드 노드를 강제로 주입하는 식입니다. 컴파일러는 조작된 AST를 기반으로 최종 바이트코드(.class 파일)를 작성하기 때문에 컴파일 후 역컴파일해보면 실제 Getter/Setter 메소드가 구현되어 있는 것을 볼 수 있습니다.


2. 편리함 속에 숨겨진 양날의 검, @Data

롬복에서 제공하는 @Data는 개발할 때 매우 흔하게 사용됩니다. 다음과 같은 어노테이션들을 통째로 묶어둔 강력한 종합 패키지이기 때문입니다.

이렇게 편리한 @Data는 자칫 잘못하면 소프트웨어 구조와 시스템의 안정성을 심각하게 무너뜨리는 요인이 될 수 있습니다.

위험 요인 1: 무분별한 Setter로 인한 객체 무결성(캡슐화) 훼손

@Data를 붙이면 클래스 내 모든 필드에 @Setter가 자동 생성됩니다. 외부에서 언제든지 객체의 핵심 데이터를 변경할 수 있게 열어두면 객체의 비즈니스 일관성과 상태를 제어하기가 매우 힘들어집니다.

위험 요인 2: JPA Entity 사용 시 toString() 순환 참조 에러

JPA 엔티티(Entity) 간에 양방향 연관관계(예: Member ↔ Team)가 맺어져 있는 상태에서 두 엔티티 모두에 @Data를 붙이면, 각 엔티티의 toString()이 서로를 끊임없이 호출하는 무한 루프(StackOverflowError)가 발생합니다.

위험 요인 3: equals()와 hashCode() 자동 생성의 문제

@EqualsAndHashCode는 객체의 모든 필드를 비교하여 객체의 동등성을 연산합니다. 하지만 JPA 환경에서 지연 로딩(Lazy Loading) 대상 필드를 조회하지 않은 상태에서 비교 연산을 수행하면 예상치 못한 프록시 초기화 에러가 터지거나, 대리 키(ID) 값만 비교해도 충분할 대상을 모든 필드 기준으로 비교해 성능 및 동작의 오류를 초래할 수 있습니다.


3. 안전한 롬복 활용 가이드 & Java Record와의 비교

실무에서 안전하게 롬복을 사용하기 위해서는 가급적 클래스 수준의 공용 종합 어노테이션인 @Data를 지양하고 다음과 같이 목적별 어노테이션을 쪼개어 사용하는 것이 안전합니다.

@Getter
@NoArgsConstructor(access = AccessLevel.PROTECTED)
@ToString(of = {"id", "name"}) // 필요한 필드만 한정
public class User {
    private Long id;
    private String name;
    
    // 비즈니스 메서드로 상태 변경을 명확하게 제어
    public void rename(String newName) {
        this.name = newName;
    }
}

또한 최신 자바 버전(Java 14+)부터 지원하는 record 키워드는 언어 스펙 자체에서 불변(Immutable) 데이터 객체를 정의하도록 고안되어, 롬복의 의존성을 줄이고 롬복 없이도 보다 안전하게 불변 DTO를 설계할 수 있는 좋은 대안이 됩니다.

롬복은 엄청난 개발 생산성을 가져다주지만, 동작 메커니즘과 그로 인한 파급 효과를 완벽하게 이해하고 통제할 때 비로소 진정한 가치를 발휘합니다.