ManyToMany를 사용하지 않는다면?
인터넷에서 글을 찾거나 강의를 듣다 보면, 직접적인 ManyToMany 사용을 지양하라고 많이 말합니다. 그러면 어떻게 사용해야 할까요? JPA에서는 다양한 연관관계가 존재하며, 상황에 따라 알맞게 연관관계를 맺어주게 됩니다. 사용자간의 팔로우를 예를 들어보겠습니다. 한 사람은 여러 팔로우를 신청할 수 있고 여러 사람도 한사람에게 팔로우를 신청할 수 있어 이러한 경우들을 다대다 관계로 맺을 수 있습니다. 이번 글에서는 이런 관계들을 어떻게 풀어야 적합한지 알아보도록 하겠습니다.
ManyToMany의 기본 구조
다대다 관계는 어노테이션을 사용한다면, 간단하게 클래스의 별도 설정 없이 테이블 명, FK설정 등을 간편하게 설정할 수 있습니다.아래 코드는 사용자간의 팔로우에 대한 다대다를 어노테이션을 사용해서 나타낸 코드입니다.
//Member.class
@ManyToMany
@JoinTable(
name = "member_follow",
joinColumns = @JoinColumn(name = "follower_id"),
inverseJoinColumns = @JoinColumn(name = "followed_id")
)
private List<Member> followed = new ArrayList<>();
@ManyToMany(mappedBy = "followed")
private List<Member> followers = new ArrayList<>();
ManyToMany의 한계
실무에서 ManyToMany를 사용하는 것은 한계가 있습니다. 연관관계를 그대로 사용하게 되면 테이블도 자동으로 생성되고 한 엔티티 내에서 처리 할 수 있어 도메인 모델이 단순한 것처럼 보이게 됩니다. 하지만 대부분 실무에서는 두 엔티티 사이의 관계가 단순히 연결만 하고 끝나지 않고, 부가적인 의미를 나타내며 도메인에 대해 변화가 일어날 수 있어 한계에 부딪히게 됩니다.
ManyToMany를 직접 구현하지 않고 사용하는 법
ManyToMany를 직접 구현하지 않고 ManyToOne으로 관계를 맺어 다대다 관계로 표현할 수 있습니다. 팔로우를 예로 들면 팔로우를 한 사람의 관계와 팔로우를 당한 사람이 관계로 나뉘어 설정한다면 다대다를 표현할 수 있게 됩니다. 추가로 비즈니스 로직에 추가사항이 생긴다면 컬럼이나 특정한 값들을 추가하여 테이블을 구성할 수 있어 이러한 방법을 주로 사용합니다.
@Entity
@Getter
@Setter
@NoArgsConstructor(access = AccessLevel.PROTECTED)
public class Follow {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
@Column
private Long id;
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "follower_id", referencedColumnName = "id")
private Member follower;
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "follow_id", referencedColumnName = "id")
private Member follow;
public static Follow of(Member member_follower, Member member_follow) {
Follow follow = new Follow();
follow.setFollower(member_follower);
follow.setFollow(member_follow);
return follow;
}
}
ManyToMany를 복합키로 풀어내기
ManyToMany를 인덱스를 가진 테이블로 생성하고 싶다면, 위와같은 방법을 사용하면 되지만 특정 인덱스 없이 다대다 관계에 있는 테이블의 인덱스만을 가지고 표현할 수 있는 방법도 있습니다. 바로 복합키로 구성하여 다대다 관계를 표현하는 방법입니다.보통 실무의 프로젝트에서는 복합키를 기존에 사용하고 있던 프로젝트에서는 이러한 방법으로 다대다 관계를 구성할 수도 있습니다.
//Follow.class
@Entity
@Getter
@Setter
@NoArgsConstructor(access = AccessLevel.PROTECTED)
public class Follow {
@EmbeddedId
private FollowId id;
@ManyToOne(fetch = FetchType.LAZY)
@MapsId("followerId")
@JoinColumn(name = "followerId", referencedColumnName = "id")
private Member follower;
@ManyToOne(fetch = FetchType.LAZY)
@MapsId("followId")
@JoinColumn(name = "follow_id", referencedColumnName = "id")
private Member follow;
public static Follow of(Member member_follower, Member member_follow) {
Follow follow = new Follow();
follow.setId(new FollowId(member_follower.getId(), member_follow.getId()));
follow.setFollower(member_follower);
follow.setFollow(member_follow);
return follow;
}
}
//FollowId.class
@Getter
@RequiredArgsConstructor
@Embeddable
@EqualsAndHashCode
public class FollowId implements Serializable {
@Column
private Long followerId;
@Column
private Long followId;
public FollowId(Long followerId, Long followId) {
this.followId = followId;
this.followerId = followerId;
}
}
이렇게 ManyToMany를 직접적으로 사용하지 않고 구현하는 방법에 대해 알아봤습니다. 실무에서 테이블 사이 간의 관계에서 직접적으로 ManyToMany를 사용해서 테이블을 구성한다면 유지보수하기 어렵고, 예측하지 못한 에러가 발생할 수 있어 사용은 지양해야겠습니다. 읽어주셔서 감사합니다.
'Spring' 카테고리의 다른 글
[JPA] JPA Pagination 처리 방법 (Pageable, Page, Slice) (0) | 2024.02.28 |
---|---|
[JPA] Batch Insert로 대량의 데이터 처리하기 (0) | 2024.02.27 |
[JPA] JPQL개념과 사용법 패턴 정리 (0) | 2024.02.22 |
[JPA] QueryDSL의 페이징 카테시안 곱 문제 해결 방법 (1:N Pagination) (0) | 2024.02.16 |
[JPA] QueryDSL에서 JPA N+1 문제 해결하기 (0) | 2024.02.13 |