Lombok은 Java 클래스에서 자주 사용하는 코드 (getter/setter, toString, equals, hashCode, 생성자 등)를 어노테이션 한 줄로 자동 생성해주는 라이브러리입니다.
필드의 getter/setter 자동 생성
@Getter
@Setter
public class User {
private String name;
private int age;
}
toString() 메서드 자동 생성
@ToString
public class User {
private String name;
private int age;
}
생성자 자동 생성
@NoArgsConstructor // 기본 생성자
@AllArgsConstructor // 모든 필드 생성자
@RequiredArgsConstructor // final 또는 @NonNull 필드만 포함
public class User {
private String name;
private int age;
}
가장 자주 쓰임! 다음 모두 포함:
@Getter, @Setter@ToString@EqualsAndHashCode@RequiredArgsConstructor@Data
public class User {
private String name;
private int age;
}
빌더 패턴 구현 (특히 DTO나 Entity 생성 시 유용)
@Builder
public class User {
private String name;
private int age;
}
사용 예:
User user = User.builder()
.name("홍길동")
.age(30)
.build();
로그 객체 log 자동 생성
@Slf4j는 다음 코드를 자동으로 생성합니다:
private static final org.slf4j.Logger log = LoggerFactory.getLogger(현재클래스.class);
즉, 로그를 사용할 수 있도록 log.info(), log.debug() 등등을 바로 쓸 수 있게 됩니다.
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 로그입니다.
@Slf4j
@Service
public class PaymentService {
public void processPayment(String userId) {
try {
// 결제 로직
log.info("결제 시작 - 사용자 ID: {}", userId);
// 결제 처리...
} catch (Exception e) {
log.error("결제 실패: {}", e.getMessage(), e);
}
}
}
logging:
level:
root: info
com.example.demo: debug
| 메서드 | 설명 |
|---|---|
log.debug() | 디버깅용 상세 정보 로그 |
log.info() | 일반적인 흐름 정보 로그 |
log.warn() | 경고성 정보 로그 |
log.error() | 예외 및 치명적인 문제 로그 |
System.out.println() 대신 log 사용"message" + value 대신 placeholder 사용:log.info("userId: {}", userId);
@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;
}
}
| 항목 | 설명 |
|---|---|
@Builder + @NoArgsConstructor 같이 사용 시 | 생성자 충돌 주의 |
| Lombok이 자동 생성한 코드 | 실제로는 컴파일 시 생성되며 IDE에서 보이지 않음 |
| 유지보수 | 과도한 사용은 오히려 디버깅에 어려움이 될 수도 있음 |
전제: 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개가 충돌 가능.@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) { ... } ❌ 충돌
→ AllArgsConstructor와 Builder가 생성한 생성자 시그니처가 동일하므로 컴파일 오류 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;
}
}
@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;
}
}
| 어노테이션 | 설명 |
|---|---|
@Getter | Entity는 주로 조회 목적이므로 setter는 최소화하고 getter만 생성 |
@NoArgsConstructor(access = PROTECTED) | JPA는 기본 생성자가 필요하되 외부에서 직접 호출되는 걸 막기 위해 protected |
@Builder | 생성자 대신 빌더 패턴으로 객체 생성 (필수 값만 명확히 지정 가능) |
@ToString | 디버깅 편리, 단 @ManyToOne 관계는 순환참조에 주의 |
❌ @Setter | Entity에 setter 남발하면 객체 무결성 무너짐 – ❌ 비추천 |
❌ @Data | equals(), hashCode() 포함되어 예상치 못한 동작 가능성 – ❌ 비추천 |
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "team_id")
private Team team;
// 이런 건 ToString에 포함 ❌
@ToString(exclude = "team") 또는 @ToString 대신 로그를 따로 찍는 방법 권장예: Post가 User를 참조 (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;
}
}
User가 Post들을 가지고 있고, Post는 User를 참조
@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는 주인이 아님을 명시함