티스토리 뷰

@Data 애너테이션

  • 포함하는 애너테이션 : @Getter, @Setter, @RequireArgsConstructor, @ToString, @EqualsAndHashCode
/**
 * @see Getter
 * @see Setter
 * @see RequiredArgsConstructor
 * @see ToString
 * @see EqualsAndHashCode
*/
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.SOURCE)
public @interface Data {
    String staticConstructor() default "";
}

 

@Data 애너테이션이 포함하는 애너테이션들에 대해서 알아보자.

 

@Setter는 수정하면 안 되는 값을 수정하게 만든다.

Entity에 @Setter를 사용하게 되면 수정자 메서드를 자동으로 생성한다. 모든 필드가 수정자를 갖기 때문에 비즈니스 로직에서 수정해서는 안 될 데이터마저 실수로 건드릴 수 있다. 따라서 @Setter를 지양하여 불필요하게 수정할 수 있는 가능성을 아예 배제해야 한다.

 

@ToString은 양방향 연관관계에서 순환 참조를 발생시킨다.

양방향 연관관계에서 @ToString를 사용하면 순환 참조 문제가 발생할 수 있다. Entity에 @ToString을 사용하게 되면 toString() 메서드를 자동으로 생성한다. 예를 들어, MemberPost1:N 관계로 설정하고 각 엔티티에 @ToString을 설정한다.

@Entity
@ToString
public class Member {

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

    @OneToMany(mappedBy = "member")
    List<Post> posts = new ArrayList<>();

}
@Entity
@NoArgsConstructor
@ToString
public class Post {

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

    @ManyToOne
    Member member;

    public Post(Member member) {
        this.member = member;
    }
}
@Service
public class PostService {

    private PostRepository postRepository;

    @Transactional(readOnly = true)
    public void print() {
        Post post = postRepository.findById(1L)
            .orElseThrow(() -> new IllegalArgumentException("Post not found"));
        System.out.println(post);
    }
}

 

데이터베이스에서 조회한 Post 객체를 콘솔에 출력할 때 StackOverflow 예외가 발생한다. 이 예외는 메서드 호출 시 생성되는 스택 프레임이 너무 많아져 스택의 깊이가 한계에 도달했을 때 발생한다.

  1. Post 객체는 Member 객체를 참조하고 있으며, @ToString 어노테이션에 의해 Post 객체가 출력될 때 해당 Member 객체도 함께 출력된다.
  2. Member 객체는 List<Post> 형태로 여러 Post 객체를 참조하는데, 이 리스트 안에는 1번에서의 Post 객체가 포함되어 있다. 따라서 @ToString에 의해 이 리스트를 출력할 때 다시 해당 Post 객체를 출력하게 된다.
  3. 이 과정이 반복되면서, 다시 Post 객체가 출력된다.

이와 같은 순환 참조로 인해 스택 깊이가 계속해서 증가하고, 결국 StackOverflow 예외가 발생하게 된다. 이는 PostMember 간의 순환 참조로 인해 발생하는 문제이다.

 

따라서 Entity에는 @ToString을 지양하여 양방향 연관관계에서 순환 참조 문제가 발생하는 것을 방지해야 한다. 반대로 DTO에는 @ToString을 사용해도 크게 문제 될 것이 없다고 생각하는데, 그 이유는 DTO에는 보통 Entity를 포함하는 것이 아닌 API 스펙에 맞는 데이터를 반환하기 때문이다.

 

@AllArgsConstructor는 치명적인 버그로 이어질 수 있다.

@AllArgsConstructor는 클래스에 존재하는 모든 필드를 포함하는 생성자를 자동으로 생성한다.

@AllArgsConstructor
public class MemberResponse {

    private String name;
    private String email;
    
}

 

MemberResponse는 생성자를 통해 (name, email) 순서로 받고 있으나, 비즈니스 로직에서 (email, name) 순서로 삽입해도 오류가 발생하지 않는다. name과 email이 동일한 타입이기 때문에 컴파일 에러가 발생하지 않기 때문이다.

 

그렇다면 @RequiredArgsConstructor도 지양해야 할까?

사실, @RequiredArgsConstructor@AllArgsConstructor와 마찬가지로 특정 상황에서 비슷한 문제를 일으킬 수 있다. 그럼에도 불구하고 많은 개발자들이 @RequiredArgsConstructor를 사용하는 모습을 흔히 볼 수 있다.

 

@RequiredArgsConstructor는 final로 선언된 필드들을 대상으로 생성자를 자동으로 만든다. 즉, 개발자가 @RequiredArgsConstructor를 사용한다는 것은 해당 필드들이 final로 선언되어 있음을 인지하고 사용하는 것이다.

 

스프링 프레임워크에서는 의존성 주입 시 생성자를 통해 필요한 의존성을 전달받을 수 있는데, 이때 의존성이 필요한 객체들을 final 필드로 선언해두면 스프링이 해당 필드에 자동으로 빈을 주입한다.

 

따라서, @RequiredArgsConstructor를 사용하는 것은 final로 선언된 필드에 의존성을 주입받기 위한 편리한 방법으로, 개발자가 필드의 final 선언을 인지한 상태에서 의도적으로 사용하는 것이다.

 

 


참고

https://www.inflearn.com/community/questions/1179578/%ED%98%B9%EC%8B%9C-allargsconstructor-%EB%A5%BC-%EC%A7%80%EC%96%91%ED%95%98%EC%8B%9C%EB%8A%94-%EC%9D%B4%EC%9C%A0%EA%B0%80-%EB%B9%8C%EB%8D%94-%ED%8C%A8%ED%84%B4%EC%9D%84-%EC%82%AC%EC%9A%A9%ED%95%98%EA%B8%B0-%EC%9C%84%ED%95%A8%EC%9D%B8%EA%B0%80%EC%9A%94?srsltid=AfmBOopS20HqnrsFtkdVCuDVsVTXa0ZTcgrdBZ3q4DQicbQENxmo5X-8

 

Total
Today
Yesterday
최근에 올라온 글
«   2024/11   »
1 2
3 4 5 6 7 8 9
10 11 12 13 14 15 16
17 18 19 20 21 22 23
24 25 26 27 28 29 30