LombokGuide
1. Lombok이란? 🧩
Lombok은 Java의 반복적인 코드(보일러플레이트 코드)를 줄여주는 라이브러리입니다. 어노테이션을 기반으로 코드 생성을 자동화하여 개발자가 비즈니스 로직에 더 집중할 수 있게 도와줍니다.
1.1 주요 특징 ✨
코드 간소화: getter, setter, constructor 등을 자동 생성
가독성 향상: 핵심 비즈니스 로직을 더 명확하게 볼 수 있음
오류 감소: 수동으로 작성하는 반복적인 코드의 실수 방지
유지보수 용이성: 클래스의 필드 추가/변경 시 자동 반영
1.2 작동 원리 🔄
Lombok은 Java 컴파일 과정에 개입하는 어노테이션 프로세서로 동작합니다:
소스 코드에 Lombok 어노테이션 작성
컴파일 시점에 어노테이션 프로세서가 추가 코드 생성
컴파일된 클래스 파일에는 자동 생성된 메소드가 포함됨
graph LR
A[Java 소스코드 + Lombok 어노테이션] --> B[Lombok 어노테이션 프로세서]
B --> C[컴파일러가 생성된 코드 처리]
C --> D[완성된 .class 파일]
2. Lombok 설정하기 🛠️
2.1 Maven 프로젝트
<!-- pom.xml -->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.30</version>
<scope>provided</scope>
</dependency>
2.2 Gradle 프로젝트
// build.gradle
dependencies {
compileOnly 'org.projectlombok:lombok:1.18.30'
annotationProcessor 'org.projectlombok:lombok:1.18.30'
// 테스트에서도 사용하려면
testCompileOnly 'org.projectlombok:lombok:1.18.30'
testAnnotationProcessor 'org.projectlombok:lombok:1.18.30'
}
2.3 IDE 설정 🔧
IntelliJ IDEA
Lombok 플러그인 설치 (최신 버전은 기본 내장)
Settings > Build, Execution, Deployment > Compiler > Annotation Processors > Enable annotation processing 체크
Eclipse
lombok.jar 다운로드 후 실행하여 Eclipse에 설치
eclipse.ini 파일에
-javaagent:/path/to/lombok.jar
추가
3. 필수 Lombok 어노테이션 📝
3.1 필드 레벨 어노테이션
@Getter
/ @Setter
@Getter
/ @Setter
getter와 setter 메소드를 자동 생성합니다.
@Getter @Setter
private String name;
// 생성되는 코드
public String getName() { return this.name; }
public void setName(String name) { this.name = name; }
@NonNull
@NonNull
Null 체크를 자동으로 추가합니다.
public void setName(@NonNull String name) {
// 생성되는 코드
if (name == null) throw new NullPointerException("name is marked @NonNull but is null");
this.name = name;
}
3.2 클래스 레벨 어노테이션
@ToString
@ToString
toString()
메소드를 자동 생성합니다.
@ToString
public class User {
private String name;
private int age;
// 생성되는 코드
@Override
public String toString() {
return "User(name=" + this.name + ", age=" + this.age + ")";
}
}
@EqualsAndHashCode
@EqualsAndHashCode
equals()
와 hashCode()
메소드를 자동 생성합니다.
@EqualsAndHashCode
public class User {
private String name;
private int age;
// name과 age를 기반으로 equals와 hashCode 메소드 생성
}
@NoArgsConstructor
/ @AllArgsConstructor
/ @RequiredArgsConstructor
@NoArgsConstructor
/ @AllArgsConstructor
/ @RequiredArgsConstructor
다양한 생성자를 자동 생성합니다.
@NoArgsConstructor // 파라미터 없는 생성자
@AllArgsConstructor // 모든 필드를 파라미터로 받는 생성자
public class User {
private String name;
private int age;
}
@Data
@Data
가장 많이 사용되는 종합 어노테이션으로, 아래 어노테이션들을 모두 포함합니다:
@Getter
@Setter
@ToString
@EqualsAndHashCode
@RequiredArgsConstructor
@Data
public class User {
private String name;
private int age;
// 위의 모든 메소드가 자동 생성됨
}
4. 고급 Lombok 기능 🚀
4.1 빌더 패턴 (@Builder
)
@Builder
)객체 생성을 위한 빌더 패턴을 자동 구현합니다.
@Builder
@Getter
public class User {
private String name;
private int age;
private String email;
}
// 사용 예시
User user = User.builder()
.name("홍길동")
.age(30)
.email("hong@example.com")
.build();
4.2 불변 객체 만들기
@Value // @Data의 불변(immutable) 버전
public class ImmutableUser {
String name;
int age;
// 모든 필드가 private final로 선언되고
// getter만 생성됨 (setter 없음)
}
4.3 로깅 어노테이션
@Slf4j // SLF4J Logger 생성
public class UserService {
public void processUser() {
// 자동 생성된 log 변수 사용
log.info("Processing user data");
log.debug("User details: {}", userDetails);
}
}
다른 로깅 프레임워크도 지원합니다:
@Log
(java.util.logging)@Log4j
(Log4j 1.x)@Log4j2
(Log4j 2.x)@CommonsLog
(Apache Commons Logging)
4.4 예외 처리 간소화 (@SneakyThrows
)
@SneakyThrows
)checked exception을 unchecked exception처럼 처리할 수 있게 해줍니다.
@SneakyThrows
public String readFirstLine(String path) {
// IOException이 선언되지 않아도 됨
return Files.readAllLines(Paths.get(path)).get(0);
}
5. Spring과 Lombok 함께 사용하기 💼
5.1 엔티티 클래스
@Entity
@Data
@NoArgsConstructor
@AllArgsConstructor
public class Product {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String name;
private BigDecimal price;
@ManyToOne
@ToString.Exclude // 순환 참조 방지
private Category category;
}
5.2 DTO 클래스
@Data
@Builder
public class ProductDTO {
private Long id;
private String name;
private BigDecimal price;
private Long categoryId;
}
5.3 Repository 및 Service 클래스
@Repository
@RequiredArgsConstructor // final 필드에 대한 생성자 자동 생성
public class ProductRepositoryImpl implements ProductRepository {
private final EntityManager entityManager;
@Override
public Product findById(Long id) {
return entityManager.find(Product.class, id);
}
}
@Service
@Slf4j
@RequiredArgsConstructor
public class ProductService {
private final ProductRepository productRepository;
public Product getProduct(Long id) {
log.info("Fetching product with id: {}", id);
return productRepository.findById(id);
}
}
6. Lombok 주의사항 및 모범 사례 ⚠️
6.1 주의사항
순환 참조 조심하기
@ToString
,@EqualsAndHashCode
에서 양방향 관계 엔티티는@ToString.Exclude
,@EqualsAndHashCode.Exclude
로 제외 필요
JPA와 함께 사용 시 주의점
@EqualsAndHashCode
에서 ID 필드만 사용하도록 설정 권장:@EqualsAndHashCode(of = "id")
프록시 객체 관련 이슈:
callSuper = false
설정
@Data
사용 주의모든 필드에 getter/setter가 열리므로 도메인 객체에는 적절하지 않을 수 있음
DTO나 값 객체에 적합
@Builder
와 생성자@NoArgsConstructor
와 함께 사용 시 별도의@AllArgsConstructor
필요
6.2 모범 사례
도메인 엔티티 설계
@Entity
@Getter // Setter 없이 Getter만 사용
@NoArgsConstructor(access = AccessLevel.PROTECTED) // JPA 요구사항
@ToString(exclude = "orders") // 양방향 관계 필드 제외
@EqualsAndHashCode(of = "id") // ID 필드만 사용
public class Customer {
@Id @GeneratedValue
private Long id;
private String name;
@Builder // 생성 시에만 값 설정 가능하도록
public Customer(String name) {
this.name = name;
}
@OneToMany(mappedBy = "customer")
private List<Order> orders = new ArrayList<>();
}
DTO 클래스 설계
@Data // DTO는 단순 데이터 전송 목적이므로 @Data 적합
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class CustomerDTO {
private Long id;
private String name;
private List<OrderSummaryDTO> orders;
}
서비스 계층 설계
@Service
@RequiredArgsConstructor // 의존성 주입을 위한 생성자 자동 생성
@Slf4j // 로깅
@Transactional // Spring 트랜잭션과 함께 사용
public class CustomerService {
private final CustomerRepository customerRepository;
public Customer findById(Long id) {
log.debug("Finding customer with id: {}", id);
return customerRepository.findById(id)
.orElseThrow(() -> new EntityNotFoundException("Customer not found"));
}
}
7. Lombok 디버깅 및 문제 해결 🔍
7.1 디버깅 팁
생성된 코드 확인
IntelliJ IDEA: Structure 창(Alt+7)에서 생성된 메소드 확인
또는 디컴파일된 클래스 확인
lombok.config 파일로 설정 관리
프로젝트 루트에 생성하여 전역 설정 지정
# lombok.config
lombok.addLombokGeneratedAnnotation = true
lombok.anyConstructor.addConstructorProperties = true
lombok.nonNull.exceptionType = IllegalArgumentException
7.2 흔한 문제와 해결책
IDE에서 Lombok 메소드를 인식하지 못함
원인: 어노테이션 프로세싱이 활성화되지 않음
해결: IDE 설정에서 어노테이션 프로세싱 활성화
빌드는 성공하지만 런타임 오류 발생
원인: Lombok 의존성 설정 문제
해결:
provided
또는compileOnly
범위 확인
NPE 발생
원인:
@NonNull
어노테이션이 없는 필드해결: 필요한 필드에
@NonNull
추가
8. 실전 예제: Spring Boot 애플리케이션 💻
8.1 완전한 Spring Boot 엔티티 예제
@Entity
@Table(name = "products")
@Getter
@NoArgsConstructor(access = AccessLevel.PROTECTED)
@ToString(exclude = {"category", "orderItems"})
@EqualsAndHashCode(of = "id")
public class Product {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@Column(nullable = false)
@NonNull
private String name;
private String description;
@Column(nullable = false)
@NonNull
private BigDecimal price;
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "category_id")
private Category category;
@OneToMany(mappedBy = "product", cascade = CascadeType.ALL, orphanRemoval = true)
private List<OrderItem> orderItems = new ArrayList<>();
@Builder
public Product(@NonNull String name, String description, @NonNull BigDecimal price, Category category) {
this.name = name;
this.description = description;
this.price = price;
this.category = category;
}
// 비즈니스 메소드
public void updatePrice(BigDecimal newPrice) {
this.price = newPrice;
}
public void assignCategory(Category category) {
this.category = category;
}
}
8.2 Controller와 Service 예제
@RestController
@RequestMapping("/api/products")
@RequiredArgsConstructor
@Slf4j
public class ProductController {
private final ProductService productService;
@GetMapping("/{id}")
public ResponseEntity<ProductDTO> getProduct(@PathVariable Long id) {
log.info("Request to get product with id: {}", id);
return ResponseEntity.ok(productService.getProductById(id));
}
@PostMapping
public ResponseEntity<ProductDTO> createProduct(@RequestBody @Valid ProductDTO productDTO) {
log.info("Request to create product: {}", productDTO);
ProductDTO created = productService.createProduct(productDTO);
return ResponseEntity
.created(URI.create("/api/products/" + created.getId()))
.body(created);
}
}
@Service
@Transactional
@RequiredArgsConstructor
@Slf4j
public class ProductService {
private final ProductRepository productRepository;
private final CategoryRepository categoryRepository;
public ProductDTO getProductById(Long id) {
log.debug("Getting product with id: {}", id);
return productRepository.findById(id)
.map(this::convertToDTO)
.orElseThrow(() -> new EntityNotFoundException("Product not found with id: " + id));
}
public ProductDTO createProduct(ProductDTO productDTO) {
log.debug("Creating new product: {}", productDTO);
Category category = categoryRepository.findById(productDTO.getCategoryId())
.orElseThrow(() -> new EntityNotFoundException("Category not found"));
Product product = Product.builder()
.name(productDTO.getName())
.description(productDTO.getDescription())
.price(productDTO.getPrice())
.category(category)
.build();
Product saved = productRepository.save(product);
log.info("Created product with id: {}", saved.getId());
return convertToDTO(saved);
}
private ProductDTO convertToDTO(Product product) {
return ProductDTO.builder()
.id(product.getId())
.name(product.getName())
.description(product.getDescription())
.price(product.getPrice())
.categoryId(product.getCategory() != null ? product.getCategory().getId() : null)
.build();
}
}
9. 결론 및 추가 자료 📚
9.1 Lombok 사용의 장단점
장점
코드 간결성과 가독성 향상
반복적인 코드 작성 시간 절약
실수 가능성 감소
IDE 지원으로 생산성 향상
단점
마법 같은(magic) 코드 생성으로 디버깅 어려움
IDE나 빌드 도구 설정 필요
때로는 지나친 추상화로 코드 이해 어려움
일부 고급 기능은 예상치 못한 동작 발생 가능
9.2 추가 학습 자료
9.3 결론
Lombok은 Java 개발자의 생산성을 크게 향상시키는 강력한 도구입니다. Spring 프로젝트에서 특히 유용하며, 반복적인 코드를 줄이고 비즈니스 로직에 집중할 수 있게 해줍니다. 어노테이션 기반의 간결한 문법으로 클래스를 더 깔끔하게 유지할 수 있습니다.
다만, 팀 프로젝트에서는 Lombok 사용에 대한 가이드라인을 수립하고 일관된 스타일을 유지하는 것이 중요합니다. 적절히 사용하면 코드의 품질과 개발 속도 모두를 향상시킬 수 있습니다.
Last updated