티스토리 뷰
@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() 메서드를 자동으로 생성한다. 예를 들어, Member와 Post를 1: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 예외가 발생한다. 이 예외는 메서드 호출 시 생성되는 스택 프레임이 너무 많아져 스택의 깊이가 한계에 도달했을 때 발생한다.
- Post 객체는 Member 객체를 참조하고 있으며, @ToString 어노테이션에 의해 Post 객체가 출력될 때 해당 Member 객체도 함께 출력된다.
- Member 객체는 List<Post> 형태로 여러 Post 객체를 참조하는데, 이 리스트 안에는 1번에서의 Post 객체가 포함되어 있다. 따라서 @ToString에 의해 이 리스트를 출력할 때 다시 해당 Post 객체를 출력하게 된다.
- 이 과정이 반복되면서, 다시 Post 객체가 출력된다.
이와 같은 순환 참조로 인해 스택 깊이가 계속해서 증가하고, 결국 StackOverflow 예외가 발생하게 된다. 이는 Post와 Member 간의 순환 참조로 인해 발생하는 문제이다.
따라서 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 선언을 인지한 상태에서 의도적으로 사용하는 것이다.
참고