Lombok

Jerry·2025년 8월 1일

Lombok

Lombok은 Java 클래스에서 자주 사용하는 코드 (getter/setter, toString, equals, hashCode, 생성자 등)를 어노테이션 한 줄로 자동 생성해주는 라이브러리입니다.

Why use Lombok?

  • Java의 verbose한 문법을 줄일 수 있음
  • 코드 간결화
  • 생산성 향상
  • 가독성 증가

Lombok annotation frequently used in Spring Boot

@Getter, @Setter

필드의 getter/setter 자동 생성

@Getter
@Setter
public class User {
    private String name;
    private int age;
}

@ToString

toString() 메서드 자동 생성

@ToString
public class User {
    private String name;
    private int age;
}

@NoArgsConstructor, @AllArgsConstructor, @RequiredArgsConstructor

생성자 자동 생성

@NoArgsConstructor // 기본 생성자
@AllArgsConstructor // 모든 필드 생성자
@RequiredArgsConstructor // final 또는 @NonNull 필드만 포함
public class User {
    private String name;
    private int age;
}

@Data

가장 자주 쓰임! 다음 모두 포함:

  • @Getter, @Setter
  • @ToString
  • @EqualsAndHashCode
  • @RequiredArgsConstructor
@Data
public class User {
    private String name;
    private int age;
}

@Builder

빌더 패턴 구현 (특히 DTO나 Entity 생성 시 유용)

@Builder
public class User {
    private String name;
    private int age;
}

사용 예:

User user = User.builder()
                .name("홍길동")
                .age(30)
                .build();

@Slf4j

로그 객체 log 자동 생성
@Slf4j는 다음 코드를 자동으로 생성합니다:

private static final org.slf4j.Logger log = LoggerFactory.getLogger(현재클래스.class);

즉, 로그를 사용할 수 있도록 log.info(), log.debug() 등등을 바로 쓸 수 있게 됩니다.

Practice example 1: Simple log print

import lombok.extern.slf4j.Slf4j;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;

@Slf4j
@RestController
public class HelloController {

    @GetMapping("/hello")
    public String hello() {
        log.info("INFO 로그입니다.");
        log.warn("WARN 로그입니다.");
        log.error("ERROR 로그입니다.");
        log.debug("DEBUG 로그입니다.");
        return "Hello, Lombok Logging!";
    }
}

실행 결과 (application.yml에서 로그 레벨에 따라 다름):

INFO  [main] HelloController - INFO 로그입니다.
WARN  [main] HelloController - WARN 로그입니다.
ERROR [main] HelloController - ERROR 로그입니다.

Practice example 2: Exception log

@Slf4j
@Service
public class PaymentService {
    public void processPayment(String userId) {
        try {
            // 결제 로직
            log.info("결제 시작 - 사용자 ID: {}", userId);
            // 결제 처리...
        } catch (Exception e) {
            log.error("결제 실패: {}", e.getMessage(), e);
        }
    }
}

Log level settings (application.yml)

logging:
  level:
    root: info
    com.example.demo: debug

Frequently used log methods

메서드설명
log.debug()디버깅용 상세 정보 로그
log.info()일반적인 흐름 정보 로그
log.warn()경고성 정보 로그
log.error()예외 및 치명적인 문제 로그

Tip

  • System.out.println() 대신 log 사용
  • 성능 최적화를 위해 문자열은 "message" + value 대신 placeholder 사용:
log.info("userId: {}", userId);

Spring Boot Practice Example (DTO + Builder + Log)

@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class UserDto {
    private String username;
    private String email;
}
@Slf4j
@RestController
@RequestMapping("/api/users")
public class UserController {

    @GetMapping
    public UserDto getUser() {
        UserDto user = UserDto.builder()
                .username("kim")
                .email("kim@example.com")
                .build();

        log.info("유저 정보: {}", user);
        return user;
    }
}

Tip & warning

항목설명
@Builder + @NoArgsConstructor 같이 사용 시생성자 충돌 주의
Lombok이 자동 생성한 코드실제로는 컴파일 시 생성되며 IDE에서 보이지 않음
유지보수과도한 사용은 오히려 디버깅에 어려움이 될 수도 있음

Resolving the constructor conflict

why @Builder conflicts with @NoArgsConstructor?

전제: JPA는 기본 생성자 (No-Args Constructor) 가 반드시 필요합니다.
JPA는 엔티티 객체를 리플렉션 기반으로 생성하기 때문에, 다음과 같은 생성자가 필요합니다:

protected User() { }

즉, @NoArgsConstructor(access = PROTECTED)는 JPA를 위한 필수 조건입니다.

문제 발생 조건

@Builder
@NoArgsConstructor
@AllArgsConstructor
@Entity
public class User {
    @Id
    @GeneratedValue
    private Long id;
    private String username;
}

위처럼 @Builder, @NoArgsConstructor, @AllArgsConstructor를 같이 쓸 경우, 다음 문제가 생깁니다:

  • @Builder는 내부적으로 @AllArgsConstructor 와 비슷한 생성자를 생성합니다.
  • @AllArgsConstructor 도 명시적으로 이미 만들어지기 때문에, 생성자 2개가 충돌 가능.
  • 컴파일러는 애매한 생성자 선택 또는 중복 생성자로 인해 충돌을 유발하거나,
  • JPA가 기본 생성자를 찾지 못해서 런타임 오류 발생

충돌 상세 예시

@Builder
@NoArgsConstructor
@AllArgsConstructor
@Entity
public class User {
    @Id
    private Long id;
    private String username;
}

위 코드는 다음과 같이 세 개의 생성자가 생깁니다:

// 1. 기본 생성자 (NoArgsConstructor)
public User() {}

// 2. 전체 필드 생성자 (AllArgsConstructor)
public User(Long id, String username) { ... }

// 3. 빌더용 생성자 (Lombok 내부적으로 생성)
public User(Long id, String username) { ... } ❌ 충돌

AllArgsConstructorBuilder가 생성한 생성자 시그니처가 동일하므로 컴파일 오류 or 런타임 불안정

해결 방법 (권장 패턴)

핵심: @Builder는 생성자에 직접 붙이기
@NoArgsConstructor는 access = PROTECTED로 설정

@Getter
@NoArgsConstructor(access = AccessLevel.PROTECTED)
@Entity
public class User {

    @Id
    @GeneratedValue
    private Long id;

    private String username;

    @Builder
    public User(String username) {
        this.username = username;
    }
}

실무에서 가장 좋은 Lombok 조합 (@Entity 기준)

@Getter
@NoArgsConstructor(access = AccessLevel.PROTECTED)
@ToString
@Entity
public class User {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    @Column(nullable = false)
    private String username;

    private String email;

    // 필수 값만 받는 생성자
    @Builder
    public User(String username, String email) {
        this.username = username;
        this.email = email;
    }

    // 비즈니스 로직 예시
    public void updateEmail(String newEmail) {
        this.email = newEmail;
    }
}

왜 이렇게 쓰는가?

어노테이션설명
@GetterEntity는 주로 조회 목적이므로 setter는 최소화하고 getter만 생성
@NoArgsConstructor(access = PROTECTED)JPA는 기본 생성자가 필요하되 외부에서 직접 호출되는 걸 막기 위해 protected
@Builder생성자 대신 빌더 패턴으로 객체 생성 (필수 값만 명확히 지정 가능)
@ToString디버깅 편리, 단 @ManyToOne 관계는 순환참조에 주의
@SetterEntity에 setter 남발하면 객체 무결성 무너짐 – ❌ 비추천
@Dataequals(), hashCode() 포함되어 예상치 못한 동작 가능성 – ❌ 비추천

연관관계 설정 시

@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "team_id")
private Team team;

// 이런 건 ToString에 포함 ❌
  • @ToString(exclude = "team") 또는 @ToString 대신 로그를 따로 찍는 방법 권장

Entity 간 관계 설정 (양방향, 단방향) + Lombok

단방향 관계

예: PostUser를 참조 (User는 Post를 모름)

@Getter
@NoArgsConstructor(access = AccessLevel.PROTECTED)
@Entity
public class Post {

    @Id @GeneratedValue
    private Long id;

    private String content;

    @ManyToOne(fetch = FetchType.LAZY)
    @JoinColumn(name = "user_id") // 외래키
    private User user;

    @Builder
    public Post(String content, User user) {
        this.content = content;
        this.user = user;
    }
}

양방향 관계

UserPost들을 가지고 있고, PostUser를 참조

@Getter
@NoArgsConstructor(access = AccessLevel.PROTECTED)
@Entity
public class User {

    @Id @GeneratedValue
    private Long id;

    private String name;

    @OneToMany(mappedBy = "user", cascade = CascadeType.ALL, orphanRemoval = true)
    private List<Post> posts = new ArrayList<>();

    @Builder
    public User(String name) {
        this.name = name;
    }

    // 양방향 편의 메서드
    public void addPost(Post post) {
        posts.add(post);
        post.setUser(this);
    }
}

@Getter
@NoArgsConstructor(access = AccessLevel.PROTECTED)
@Entity
public class Post {

    @Id @GeneratedValue
    private Long id;

    private String content;

    @ManyToOne(fetch = FetchType.LAZY)
    @JoinColumn(name = "user_id")
    private User user;

    @Builder
    public Post(String content) {
        this.content = content;
    }

    public void setUser(User user) {
        this.user = user;
    }
}
  • 연관관계의 주인은 @ManyToOne 쪽 (즉, Post.user)
  • mappedBy는 주인이 아님을 명시함
profile
Backend engineer

0개의 댓글