양방향보다 단방향이 좋다?

PPakSSam·2022년 1월 6일
0

스터디원 중 한명인 핑구님의 블로그를 보고 정리한 글임을 밝힌다.

참고한 레퍼런스는 JPA의 사실과 오해이다.


JPA강의를 듣고 정리한 글들 중 많은 글들이 양방향 연관관계보다는 단방향 연관관계
권장하고 있다. 이는 반은 맞고 반은 틀린말인데 예시를 들어 설명하고자 한다.

Case1 일대다[1:N] 관계에서의 단방향

[Member]

@Entity
public class Member {
	
    @Id
    @Column(name = "member_id")
    private Long memberId;
    
    private String name;
    
    @Column(name = "create_dt")
    private LocalDateTime createDate;
    
    @OneToMany(cascade = CascadeType.ALL)
    @JoinColumn(name = "member_id")
    private List<MemberDtail> details;
}

[MemberDetail]

@Entity
public class MemberDetail {
	
    @EmbeddedId
    private Pk pk;
    
    private String description;
    
    @Embeddable
    public static class Pk implements Serializable {
    	
        @Column(name = "member_id")
        private Long memberId;
        
        private String type;
    }
}

[MemberService]

@Transactional
public void createMemberWithDetails() {
	
    Member member = new Member("member1", LocalDateTime.now());
    Member savedMember = memberRepository.save(member);
    
    MemberDetail memberDtail1 = new MemberDetail();
    memberDtail1.setPk(new MemberDetail.Pk(savedMember.getMemberId(), "type1"));
    memberDetail1.setDescription("member1-type1");
    
    MemberDetail memberDtail2 = new MemberDetail();
    memberDtail1.setPk(new MemberDetail.Pk(savedMember.getMemberId(), "type2"));
    memberDetail1.setDescription("member1-type2");
    
    member.getDetails().add(memberDetail1);
    member.getDetails().add(memberDetail2);
}

[실행시 나가는 쿼리]

insert into members values ('2019-11-27T15:20:00.123', 'member1', 1)
insert into member_details values ('member1-type1', 1, 'type1')
insert into member_details values ('member1-type2', 1, 'type2')
update member_details set member_id=1 where member_id=1 and type='type1'
update member_details set member_id=1 where member_id=1 and type='type2'
  • 일대다의 단방향의 경우 엔티티의 반대테이블에서 외래키를 관리한다.
  • 따라서 INSERT쿼리 외에 UPDATE쿼리까지 나가게 된다.
  • 우리가 기대한 것은 INSERT쿼리 3개였으나 총 5개의 쿼리가 나가게 된다.
  • 성능상 좋지 않다 -> 이때는 다대일의 양방향 연관관계로 만들어주면 해결!

Case2 다대일[N:1] 관계에서의 양방향

[Member]

@Entity
public class Member {
	
    @Id
    @Column(name = "member_id")
    private Long memberId;
    
    private String name;
    
    @Column(name = "create_dt")
    private LocalDateTime createDate;
    
    @OneToMany(cascade = CascadeType.ALL, mappedBy = "member")
    private List<MemberDtail> details;
}

[MemberDetail]

@Entity
public class MemberDetail {
	
    @EmbeddedId
    private Pk pk;
    
    private String description;
    
    @ManyToOne
    @MapsId("memberId")
    @JoinColumn(name = "member_id")
    private Member member;
    
    @Embeddable
    public static class Pk implements Serializable {
    	
        @Column(name = "member_id")
        private Long memberId;
        
        private String type;
    }
}

[MemberService]

@Transactional
public void createMemberWithDetails() {
	
    Member member = new Member("member1", LocalDateTime.now());
    Member savedMember = memberRepository.save(member);
    
    MemberDetail memberDtail1 = new MemberDetail();
    memberDtail1.setPk(new MemberDetail.Pk(savedMember.getMemberId(), "type1"));
    memberDetail1.setDescription("member1-type1");
    
    MemberDetail memberDtail2 = new MemberDetail();
    memberDtail1.setPk(new MemberDetail.Pk(savedMember.getMemberId(), "type2"));
    memberDetail1.setDescription("member1-type2");
    
    member.getDetails().add(memberDetail1);
    member.getDetails().add(memberDetail2);
}

[실행시 나가는 쿼리]

insert into members values ('2019-11-27T15:20:00.123', 'member1', 1)
insert into member_details values ('member1-type1', 1, 'type1')
insert into member_details values ('member1-type2', 1, 'type2')

일단 내가 맨위의 포스트의 유튜브 영상에서는 Case2를 일대다의 양방향 연관관계라고 설명한다. 그러나 나는 다대일의 양방향 연관관계라고 생각한다.
이유는 다음과 같다.

  • 김영한님의 강의에 따르면 일대다의 양방향 연관관계는 둘 다 @JoinColumn이 있었다.
  • @ManyToOne 쪽의 @JoinColumn에 insertable=false, updatable=false를 해줘야한다.
    -> 왜냐하면 일대다(1:N) 중 일(1)이 연관관계의 주인이기 때문이다.
  • 그러나 여기는 @OneToMany에 mappedBy가 있다. 즉 일(1)이 연관관계의 주인이 아니다!
  • 그러므로 다(N)가 연관관계의 주인인것은 명확하다.

영한님 강의에서도 일대다 단방향 매핑보다는 다대일 양방향 매핑을 사용하자!!라고 나와있다.
내 관점에서는 Case1에서 Case2로 바꾼 것을 일대다 단방향 매핑에서 다대일 양방향 매핑으로 바꾼 것이므로 적절한 변환이라고 생각한다.

  • 다대일 양방향 매핑이므로 외래키가 있는 다(N)에서 외래키를 관리한다.
    -> 따라서 UPDATE쿼리가 나가지 않는 것이다.
  • 복합키의 경우 여기서는 @EmbeddedId를 사용하였는데 이 경우 연관관계 매핑을 위해 @MapsId를 같이 사용하여야 한다.

참고

여기서 의문점이 하나 들 수 있다.
Memberdetails는 연관관계의 주인이 아닌데 어째서 member.getDetails().add(memberDetail1);를 했는데 INSERT쿼리가 나가는거지??

그것은 바로

@OneToMany(cascade = CascadeType.ALL, mappedBy = "member")
private List<MemberDtail> details;

에서 cascade = CascadeType.ALL부분 때문이다.

  • casecade속성은 연관관계를 매핑하는 것과 아무런 관련이 없다.
  • 엔티티를 영속화할 때 연관된 엔티티도 함께 영속화하려는 편리함을 제공할 뿐이다.
profile
성장에 대한 경험을 공유하고픈 자발적 경험주의자

0개의 댓글