091

[Java] Lombok 본문

Programming Language/Java

[Java] Lombok

공구일 2026. 5. 5. 20:19
728x90

0. 3 Layer Architecture

 

- @Component 계열의 Bean인 @RestController(@Controller), @Service, @Repository는 이 3계층 아키텍처의 흐름에서 Presentation Layer, Service Layer, Data Access Layer에 해당합니다. 

-> DTO(Data Transfer Object)는 데이터를 전달하기 위해 사용하는 객체입니다. Entity는 ORM 구조에서 DB의 테이블과 매핑되어 실제로 데이터를 관리하는 객체입니다. 이렇게 데이터를 다루는 객체를 굳이 두개로 쪼갠 이유는 이전에 JDBC를 사용하던 시절에는 쿼리를 날리고 결과를 받아서 데이터를 담을 수 있는 객체에 일일이 넣어줬지만, Entity와 DTO가 지금처럼 명확하게는 구분되지 않았습니다. JPA의 등장으로, Entity라는 DB 테이블과 매핑된 아주 예민한 객체가 만들어졌고 영속성이라는 특징 때문에 데이터를 이동하는 부분에서까지 이 Entity를 사용하면, 성능문제(N+1), 보안 문제, 사이드 이펙트가 너무 커졌습니다. 

 

- 이렇게 분리된 구조를 사용하다보면 수많은 Entity, Dto 클래스마다 비슷한 코드(Getter, Setter, 생성자, toString, equals, hashCode 등)를 작성해줘야하는 번거로움이 발생했고 이를 해결하기 위해 롬복이라는 API가 등장하게 됩니다.

 

1. Lombok

 

- 롬복(Lombok)이란 반복되는 보일러플레이트(Boilerplate) 코드를 자동으로 생성해주는 라이브러리입니다. 

*보일러플레이트 코드는 로직과 직접적인 관련은 없지만 반복적으로 작성해야하는 정형화된 코드를 의미합니다. 위에서 말했던 Getter/Setter 등이 대표적입니다.

class User {
    private String name;
    private int age;

    public User() {}

    public User(String name, int age) {
        this.name = name;
        this.age = age;
    }

    public String getName() { return name; }
    public int getAge() { return age; }

    public void setName(String name) { this.name = name; }
    public void setAge(int age) { this.age = age; }

    @Override
    public String toString() {
        return "User{name='" + name + "'}";
    }
}

->롬복을 사용하지 않는 경우에는 아래의 코드처럼 직접 생성자, Getter, Setter을 선언해줘야합니다. 코드가 길어지고 핵심적인 로직이 잘 보이지 않아 유지보수에 어려움을 겪습니다. 하지만, 아래처럼 롬복을 활용해준다면 훨씬 깔끔하며 유지보수에도 편합니다.

@Getter
@Setter
@NoArgsConstructor
@AllArgsConstructor
@ToString
class User {
    private String name;
    private int age;
}

-> 코드가 간결해지고 유지보수가 편리해집니다. 

 

- 롬복에서 주로 사용하는 어노테이션

종류 어노테이션 설명
기본 @Getter, @Setter 데이터 읽기,쓰기 메서드 자동 생성
생성자 @RequiredArgsConstructor,
@NoArgsConstructor, @AllArgsConstructor
final 필드 전용 생성자,
파라미터가 없는 생성자, 모든 필드를 파라미터로 가지는 생성자
출력/비교 @ToString, @EqualsAndHasCode 객체 문자열 출력 및 객체 간 비교 로직
종합 @Data(DTO 전용), @Value(불변) 편리한 묶음 어노테이션
기타 @Builder, @Slf4j 빌더 패턴 구현 및 로그 객체 생성

1️⃣ @Getter, @Setter: 자바의 캡슐화를 지키기 위해서 getFieldName(), setFieldName() 메서드를 생성합니다. 클래스 위에 붙이면 모든 필드에 적용되고, 특정 필드 위에만 붙일 수도 있습니다.

 

2️⃣ @RequiredArgsConstructor, @NoArgsConstructor, @AllArgsConstructor: 생성자를 만드는 3가지 어노테이션으로, 각각 설명에 나온 특징을 가지는 생성자를 자동으로 만들어줍니다. 특히 @RequiredArgsConstructor은 final이나 @NonNull이 붙은 필수 필드만 파라미터로 받는 생성자를 만듭니다. 이게 Spring의 큰 특징인 DI와 관련이 있습니다.

 

3️⃣ @ToString, @EqualsAndHasCode: @ToString은 객체를 문자열로 예쁘게 보여주는 toString()을 생성합니다. @EqualsAndHashCode는 모든 필드 값이 같은 지를 비교하여(of 속성으로 특정 필드만 비교하도록 제한할 수도 있습니다.) 객체의 값 기준 비교를 가능하게 해주는 어노테이션으로, Set 객체처럼 중복되지 않아야되는 상황에서 해당 어노테이션을 작성해둔 클래스에서는 중복제거를 가능하게끔 합니다. 즉, 원래는 같은 객체인가에 대한 비교였다면 해당 어노테이션을 작성하여 같은 데이터인지를 비교합니다.

 

4️⃣ @Data, @Value: @Data는 @Getter,@Setter, @ToString, @EqualsAndHashCode, @RequeiredConstructor이 포함되어 있는 어노테이션입니다. 주로 DTO에서만 사용하며, Entity에서는 사용을 금지합니다. @Value는 객체를 수정할 수 없게 만들기 위해 모든 필드를 기본적으로 private final로 만들며, @Setter를 생성하지 않고 @ToString, @EqualsAndHashCode, @AllArgsConstructor가 포함됩니다.

 

5️⃣ @Builder, @Slf4j: @Builder는 복잡한 객체를 생성할 때 가독성을 높여주는 빌더 패턴을 자동으로 구현하는 어노테이션으로, 파라미터의 순서를 헷갈릴 필요없이 필요한 데이터만 골라 넣을 수 있어 매우 편리합니다. @Slf4j는 로그를 남길 수 있는 log 객체를 클래스 안에 자동으로 생성하기 때문에 선언없이 사용이 가능합니다.

 

Q. @Data를 Entity에서는 사용하면 안되는 이유가 뭔가요?
A. @Data는 개발자를 편하게 해주는 묶음 어노테이션 같지만, JPA라는 특수 환경에서는 애플리케이션을 멈추게 할 수 있습니다. 내부에 가지고 있는 @ToString, @EqualsAndHashCode, @Setter가 그 문제의 주범입닌다.

-> @ToString은 양방향 참조관계일 때, 무한대로 출력을 시도하다 결국 서버가 다운(StackOverFlowError)가 발생합니다. 예를 들어보자면 Member가 Order를 가지고, Order가 Member를 참조하는 양방향 관계일 때 member.toString() 호출 시 orders 출력 시도, order.toString() 호출이 되면서 또다시 member 출력 시도를 하게됩니다. 양방향 관계에서는 한쪽의 @ToString을 제외해줘야합니다. exclude 속성을 활용하면 이 문제를 해결할 수 있습니다.

-> @EqualsAndHashCode는 JPA 엔티티의 식별자인 ID는 DB에 저장되기 전까지는 null 상태인데, @Data가 만들어낸 equals()는 모든 필드를 비교하고 저장되지 않은 두 객체를 비교했을 때는 두 객체가 모두 null이기 때문에 논리적으로 다른 데이터임에도 true라고 판단해버리는 오류가 발생합니다.

-> @Setter는 어디서나 객체의 상태 변경을 할 수 있다는 점이 문제입니다. 객체의 상태 변경은 서비스 계층에서 비지니스 메서드를 통해 일어나야 하므로, 아무곳에서나 상태를 바꿀 수 있게 만들면 안됩니다.

728x90