[Spring] Lombok 주요 어노테이션 정리 및 주의사항

눈치없어·2025년 1월 8일

Lombok

여러 프로젝트를 진행하면서 사용한 롬복 라이브러리.

DTO나 Entity 코드를 작성할때 당연시하게
@Getter @Setter @Builder @NoArgsConstructor @AllArgsConstructor
위 다섯개를 사용해왔다. (Service, Controller에는 @RequiredArgsConstructor...)
하지만 여러 어노테이션이 정확히 어떤 기능 제공하고, 어떤 부작용이 있는지 깊이 이해하지 못하고 사용을 했던 감이 있다.
그래서 Lombok의 주요 어노테이션 기능과 주의사항을 정리한다.


@Getter, @Setter

  • Getter: 특정 필드의 값을 읽는 메서드를 자동 생성
  • Setter: 특정 필드의 값을 설정하는 메서드를 자동 생성
  • 객체의 안정성을 위해 어노테이션을 특정 필드 위에 할당한다면 해당 필드에만 적용이 가능
@Getter
public class MemberDto {
	private String age;
	@Setter
	private String name;
}
  • getter 메서드의 기본 작동 방식 중 메서드 이름은 get<필드명> 인데, boolean 타입은 is<필드명> 형식이다.
  • 필요한 경우메만 setter를 선택적으로 사용하고, getter와 setter를 남용하지 않는 것이 권장된다고 한다.
    - setter 사용 지양: 객체의 캡슐화 원칙이 깨질 수 있다. 불변객체를 설계하거나 데이터 무결성이 중요한 경우는 setter 사용을 최소화하는 것이 좋다.

@ToString

  • 객체의 상태를 표현하기 위해 toString() 메서드를 자동 생성
  • 디버깅이나 로깅 시 객체의 상태를 출력하는 데 유용하다.
  • toString을 적용하고 싶지 않다면
    - 제외할 필드 위에 @ToString.Exclude 추가 하거나,
    - 클래스 레벨에서 @ToString(exclude = "필드명") 옵션 사용
@ToString(exclude = "age")
public class MemberDto {
	private String age;      // 제외됨
	private String name;
	@ToString.Exclude
	private String address;  // 제외됨

//	public MemberDto() {} // 자동 생성
}

- 순환 참조 문제

  • 양방향 연관관계를 가진 엔티티에서 toString() 호출 시 무한 순환 참조(StackOverflowError)가 발생할 수 있다.
  • 해결 방법:
    @ToString.Exclude를 사용하여 순환 참조를 방지
    필요 시 @ToString 대신 수동으로 toString() 메서드를 작성

    공부를 하면서 사용한적이 있었지만, 진행했던 프로젝트에서는 사용한적이 없었다. -> 디버깅이나 로깅 시 객체 상태를 출력하는데 신경을 더 쓰지 못해서 그런것 같다.


@NoArgsConstructor

  • 파라미터가 없는 기본 생성자를 자동으로 생성
  • 객체 생성 시 초기값 설정이 필요 없을때 사용
@NoArgsConstructor
public class MemberDto {
	private String age;
	private String name;

//	public MemberDto() {} // 자동 생성
}

@AllArgsConstructor

  • 클래스의 모든 필드를 파라미터로 받는 생성자를 자동으로 생성
@AllArgsConstructor
public class MemberDto {
	private String age;
	private String name;

//	자동 생성
/*	public MemberDto(String age, String name) {
		this.age = age;
		this.name = name;
	}
*/

@RequiredArgsConstructor

  • final 키워드가 붙은 필드와 @NonNull 어노테이션이 붙은 필드를 포함하는 생성자를 자동으로 생성
  • 주로 의존성 주입을 위해 사용
    - Controller가 Spring Container에 Bean이 등록될 때 Service를 주입 시켜준다.
@RestController  
@RequiredArgsConstructor  
@RequestMapping("/api/study-posts")  
public class StudyPostController {  
  
	private final StudyPostService studyPostService;

//	사용 시 생성
/*	public StudyPostController(StudyPostService studyPostService) {
		this.studyPostService = studyPostService
	}
*/	

final 필드만 포함되므로 필드 값이 변경되지 않음


@EqualsAndHashCode

  • 클래스에 대한 equals(), hashCode() 메서드를 자동으로 생성
  • 두 객체가 동일한 값이나 상태를 가지고 있는지 비교할 때 사용
@Getter
@Setter
@ToString
@RequiredArgsConstructor
@EqualsAndHashCode(of = {"title"}) // title 필드만 비교
public class StudyPostDto {

	@NonNull
	private String title;
	private String description;
	private String subject;
	private String difficulty;
}
  • 적용 예시
@Slf4j
public class Test() {
	StudyPost studyPost1 = new StudyPost("java");
	StudyPost studyPost2 = new StudyPost("java");
	StudyPost studyPost3 = new StudyPost("springboot");

	log.debug(studyPost1.equals(studyPost2)); // true
	log.debug(studyPost1.equals(studyPost3)); // false
}

@Builder

  • 빌더 패턴을 자동으로 구현하여 객체를 생성할 때 필드 값을 체이닝 방식으로 설정할 수 있도록 함
public class Test() {
	StudyPost studyPost = StudyPost.builder()
							.title("docker study")
							.difficulty("high")
							.build();
}
  • 특정 필드만 설정하거나 순서를 무시하고 필드를 설정할 수 있음
  • @AllArgsConstructor와 차이
    - @AllArgsConstructor는 모든 필드를 포함하는 생성자를 생성하므로 객체 생성 시 모든 필드 값을 제공해야 한다.
    - @Builder는 특정 필드만 설정하거나, 선택적으로 값을 초기화할 수 있다.
// @AllArgsConstructor 사용
StudyPost studyPost1 = new StudyPost("Docker Study", "Advanced course", "High");

// @Builder 사용
StudyPost studyPost2 = StudyPost.builder()
						.title("Docker Study")
						.difficulty("High")
						.build();

- @Builder 사용 시 매개변수 최소화!

  • 클래스 상단에 @Builder를 사용하면 모든 필드에 대한 매개변수를 허용하게 되고 이 경우 불필요한 데이터 입력을 초래함
  • 불필요한 필드 초기화
    - 데이터베이스의 자동 생성 전략을 사용하는 id 같은 필드는 객체 생성 시 초기화할 필요가 없음
    - createdAt, UpdatedAt 같은 필드도 일반적으로 각각 어노테이션에서 자동 관리되므로, 객체 생성 시 수동으로 값을 전달할 필요가 없음
public class StudyPost {
    private Long id; // 자동 생성
    private String title;
    private String description;
    private String difficulty;
    private LocalDateTime createdAt; // @CreatedDate 어노테이션 관리
    private LocalDateTime updatedAt; // @LastModifiedDate 어노테이션 관리

    @Builder
    public StudyPost(String title, String description) {
        this.title = title;
        this.description = description;
    }
}

위와 같이 필요한 필드만 초기화 하도록 생성자를 지정한 후, 해당 생성자에 @Builder를 적용하는 것이 바람직하다.


@Data

  • @Getter @Setter @ToString @RequiredArgsConstructor @EqualsAndHashCode 어노테이션의 집합체
  • 해당 기능은 누가봐도 좋게 보인다고 생각한다. 다만 강력한 기능을 제공함에 따라, 그만큼의 부작용도 많다.
    (실무에서는 사용을 지양하는 경우가 많다고 한다.)

- ToString 으로 인한 순환 참조 문제

  • @Data에 포함된 @ToString 은 객체의 모든 필드를 출력하는 toString() 메서드를 생성한다.
  • 양방향 연관관계를 가진 엔티티에서 toString() 호출 시 무한 순환 참조가 발생할 수 있다.
@Data
public class User {
    private Team team;
}

@Data
public class Team {
    private User user;
}

// toString() 호출 시 User → Team → User → ... 무한 순환 발생
  • 해결 방법으로는 위 @ToString 설명과 같이 @ToString.Exclude를 사용해 특정 필드를 toString()에서 제외하거나, @Data 대신 필요한 어노테이션만 선택적으로 사용

- Setter 사용으로 인한 객체의 안정성 문제

  • @Data에 포함된 `@Setter는 객체의 필드를 어디서든지 변경할 수 있다.
  • 불변 객체를 설계하거나 상태 변경을 명확히 관리하려면 Setter를 최소화해야 한다.

intellij를 사용하는 경우 Lombok 플러그인 설치 후
Settings > Build, Execution, Deployment > Compiler > Annotation Processors로 이동
Enable annotation processing 옵션을 체크해야 사용 가능하다.

profile
dock 사이즈 다르잖아

0개의 댓글