ex)
@Entity
@Table(name = "member")
@Data
public class Member {
....
@OneToMany
@JoinColumn(name = "coupon_id")
private List<Coupon> coupons = new ArrayList<>();
}
@Entity
@Table(name = "coupon")
@Data
public class Coupon {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private long id;
@ManyToOne
private Member member;
public Coupon(Member member) {
this.member = member;
}
}
위 코드처럼 Member 객체와 Coupon 객체가 양방향 연관관계일 경우 ToString을 호출하면 무한 순환 참조가 발생합니다. JPA를 사용하다 보면 객체를 Json으로 직렬화 하는 가정에서 발생하는 문제와 동일한 이유입니다.
쉬운 해결방법으로는
@ToString(exclude = "coupons")
public class Member {...}
해당 어노테이션을 이용해서 ToString 항목에서 제외시키는 것입니다.
접근권한 public인 경우 예시
@Entity
@Table(name = "product")
@Getter
@NoArgsConstructor(access = AccessLevel.PUBLIC) // 테스트를 위해 임시로 Public, 의도한 코드는 PROTECTED
public class Product {
@Id
private String id;
private String name;
@Builder
public Product(String name) {
this.id = UUID.randomUUID().toString();
this.name = name;
}
}
Test
@Test
public void test(){
Product product = new Product();
assertThat(product.getId(), is(notNullValue()));
}
Id는 항상 null이 아니어야 하지만 public 생성자를 통해 객체를 생성하면 Id 값은 null이 되어 오류가 발생합니다. 이처럼 기본 생성자를 아무 이유 없이 열어두는 것은 객체 생성 시 안전성을 떨어뜨리게 됩니다.
@Builder
public class Member {...}
클래스 위에 @Builder를 사용 시 @AllArgsNConstructor 어노테이션을 붙인 효과를 발생시켜 모든 멤버 필드에 대해서 매개변수를 받는 기본 생성자를 만듭니다.
하지만 다음과 같은 문제가 있습니다.
Member의 Id 생성전략은 DB의 auto_increment를 의존하고 있다고 가정하면 Id를 넘겨받지 않아야 합니다.
또한 createAt, updateAt 같은 경우는 @CreationTimestamp, @UpdateTimestamp 각각의 어노테이션이 해당 일을 담당하고 있습니다. 이처럼 객체 생성시 받지 않아야 할 데이터들이 클래스 상단 @Builder를 사용하게 되면 불필요한 값을 생성할 수 있게 됩니다.
public class Member{
@Builder
public Membeer(String email, String name){
this.email = email;
this.name = name;
}
}
이렇게 받으면 생성자를 필요조건에 따라 지정하고 그 위에 @Builder를 붙이는게 바람직하다고 생각합니다.