@ManyToMany에 대한 정리

들어가며

f-lab 멘토링을 진행하며 SNS 서비스를 만들어보는 프로젝트를 진행하고 있다. 엔티티를 설계하는 과정에서 다대다 관계의 테이블이 필요한 상황이었다. 예를 들어 회원이 좋아요를 한 글을 관리하고 싶었는데 한명의 회원은 다수의 글에 대해 좋아요를 할 수 있고 반대로 하나의 글은 여러 회원들에게 좋아요를 받을 수 있는 상황이므로 회원과 글 사이에는 다대다 관계가 성립하는 것이다. 

 

프로젝트는 JPA를 사용하고 있으므로 @ManyToMany를 사용해 다대다 관계의 엔티티로 설계를 하였다. 그런데 Pull Request에 대한 리뷰를 멘토님이 해주시며 @ManyToMany 사용을 지양해야 한다고 말씀해주셨다. 첨부해주신 글에는 @ManyToMany를 사용했을 때 JPA에서 연결 테이블을 만들어 주기는 하지만, 매핑에 필요한 컬럼만 생성해주므로 사용해서는 안된다는 내용이 있었다. 뭔가 해당 근거만으로는 잘 납득이 안 되어서 조금 더 찾아보고 싶은 마음에 김영한님의 JPA 강좌 중 다대다 관계에 대한 강의를 보았다. 시작하자마자 하시는 말씀 "실무에서는 사용해서 안된다고 본다." 도대체 왜 @ManyToMany를 사용해선 안된다는 것일지 강의 내용을 정리해보고자 한다.

다대다 관계

다대다 관계는 관계형 DB에서 2개의 테이블 만으로는 표현할 수 없다. 이유는 위에 설명했듯이 예를 들어 좋아요를 누르는 글마다 컬럼을 하나씩 만들어 표현하지 않는 이상 하나의 회원이 좋아요를 누른 글을 관리할 방법이 없기 때문이다. 따라서 관계형 DB에서는 일대다, 다대일 관계로 구성되는 연결테이블을 만든다.

 

그런데 JAVA에서는 연결테이블이 없어도 다대다 관계의 표현이 가능하다. 나아가 객체지향적 방식으로는 표현이 문제없이 가능하다. 회원 클래스 안에 List 컬렉션으로 글을 가지고 있으면 된다. 반대로 글 클래스에서도 List로 회원을 가지고 있으면 된다. 이렇게 객체지향과 관계형 DB의 불일치를 해결하기 위해 JPA에서는 @ManyToMany를 제공한다.

 

사용을 지양하지만 간단히 사용법을 보자면 아래와 같은 식으로 사용할 수 있다.

@Entity
@Table(name = "USER")
public class User {

	@Id @GeneratedValue
	private Long id;
    
    private String username;
    
    @ManyToMany
    @JoinTable(name="LIKE_POST")
    private List<Post> posts = new ArrayList<>();
}

@Entity
@Table(name = "POST")
public class Post {

	@Id @GeneratedValue
	private Long id;
    
    private String content;
    
    @ManyToMany(mappedBy= "posts")
    private List<User> users = new ArrayList<>();
}

 

이렇게 하면 ddl-auto 값이 true일 때 자동적으로 User의 PK와 Post의 PK를 외래키로 갖는 연결테이블이 생성된다.

왜 쓰면 안되는가

김영한 님 강연에서 말씀하신 것도 결론적으로는 실무에서 연결테이블에 컬럼을 추가하는 일이 많다는 것이다. 예를 들어 회원과 상품 간의 다대다 관계가 있을 때 우리는 이를 결국 주문이라는 연결 테이블로 뽑아낼 수 있고 주문에는 주문 수량, 주문 일시 등 많은 정보들이 들어간다. 그런데 @ManyToMany 를 썼을 때는 딱 연결에 필요한 컬럼만으로 테이블이 구성되고 무엇보다 엔티티가 아닌 DB에 테이블로만 존재하기 때문에 비즈니스 로직에서 추가적인 정보에 대해 처리할 수 없게 된다.

@Entity
@Table(name = "USER")
public class User {

	@Id @GeneratedValue
	private Long id;
    
    private String username;
    
    @ManyToMany
    @JoinTable(name="LIKE_POST")
    private List<Post> posts = new ArrayList<>(); // LIKE_POST에 좋아요 일시를 정보를 넣고 싶을 때 엔티티 상으로 넣을 수가 없다.
}

 

그러면 @ManyToMany를 쓰지 않고 처리하려면 어떻게 해야할까? 관계형 DB에서 다대다 관계를 일대다, 다대일 관계로 풀어내듯 @OneToMany, @ManyToOne으로 풀어내고 연결테이블을 엔티티로 승격시키면 된다.

 

@Entity
@Table(name = "USER")
public class User {

	@Id @GeneratedValue
	private Long id;
    
    private String username;
    
    @OneToMany(mappedBy = "post")
    private List<Post> posts = new ArrayList<>();
}

@Entity
@Table(name ="LIKE_POST")
public class LikePost{

	@Id
    @GeneratedValue
	private Long id;
    
    @ManyToOne
    @JoinColumn(name = "POST_NO")
    private Post post;

    @ManyToOne
    @JoinColumn(name = "USER_NO")
    private User user;
    
   
    private LocalDateTime likeDateTime; //좋아요 일시를 나타낼 수 있게 됐다.

}

@Entity
@Table(name = "POST")
public class Post {

	@Id @GeneratedValue
	private Long id;
    
    private String content;
    
    @OneToMany(mappedBy="user")
    private List<User> users = new ArrayList<>();
}

마치며

실무에서 JPA를 사용 중인데도 막상 프로젝트를 진행하니 ERD에 따라 별 고민 없이 엔티티 설계를 했다. JPA를 잘 사용하기 위한 공부도 꾸준히 해나가야할 것 같다...