java and sping

[Spring] JPA의 연관관계(단방향/양방향, 연관 관계의 주인)

코딩하는 공부방 2023. 4. 27. 02:06

JPA에서 가장 중요한 것

JPA에서 가장 중요한 것을 뽑자면,
"객체"데이터베이스 테이블이 어떻게 매핑되는지를 이해하는 것"
이라고 생각합니다.

왜냐하면 JPA의 목적"객체 지향 프로그래밍과 데이터베이스 사이의 패러다임 불일치를 해결"이라는 것과 가장 직접적으로 연관되어 있기 때문입니다.

 

연관관계 매핑

객체의 참조테이블의 외래 키 매핑

필요한 이유

  • 객체를 테이블에 맞추어 데이터 중심으로 모델링하면 협력 관계를 만들 수 없다.
  • 테이블은 외래 키로 조인을 사용해서 연관된 테이블을 찾는다.
  • 객체는 참조를 사용해서 연관된 객체를 찾는다.
  • 테이블과 객체 사이에는 이런 큰 간격이 있다.

3가지를 "방향" , "다중성", "연관관계 주인"고려해야 합니다. 

단방향 연관관계


연관관계는 연관관계인데 단방향은 무엇을 말하는 것일까요?

회원 엔티티

@Entity
@Data
@Table(name = "member")
@NoArgsConstructor
public class Member {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    @Column(name = "member_id")
    private Long id;

    private String name;

    @ManyToOne
    @JoinColumn(name="team_id")
    private Team team;

    @Builder
    public Member(Long id,String name) {
        this.name = name;
        this.id = id;
    }
}

팀 엔티티

@Entity
@Data
public class Team {

    @Id
    @GeneratedValue
    @Column(name = "team_id")
    private Long id;

    private String name;

}

DB 테이블 관점

데이터베이스 테이블을 보시면 Member 테이블에 외래 키를 가지고 있고, Team 테이블을 조회할 수 있고, 반대로 Team 테이블에서도 teamId를 가지고 Member 테이블을 조회할 수 있습니다. 즉, DB 테이블은 서로가 서로를 조회할 수 있는 양방향 관계라고 할 수 있습니다.

객체 관점에서 생각해 보겠습니다.

객체 관점에서 생각해 보면 DB 테이블처럼 무조건 양방향일 필요는 없습니다. Member 클래스와 Team 클래스에 필드로 서로에 대한 참조를 넣어주면 양방향이 되는 것이고, 한쪽에만 넣어주면 단방향이 되는 것입니다.

MemberTeamN:1 관계Member는 N(여러 개)에 속합니다. N -> 1로 가는 방향이기 때문에 @ManyToOne이 됩니다.

다음으로 조인할 칼럼명(외래 키명)을 명시해 줍니다.(N:1 관계에서 외래 키는 N 쪽에 둡니다.)

양방향 연관관계


이제 Team에서도 Member를 조회하고 싶다고 가정을 합니다. 우선, Member 클래스는 위와 같고, Team 클래스는 다음과 같이 @OneToMany 애너테이션을 추가합니다.

Team 엔티티

@Entity
@Data
public class Team {

    @Id
    @GeneratedValue
    @Column(name = "team_id")
    private Long id;

    private String name;

    @OneToMany(mappedBy = "team")
    private List<Member> memberList = new ArrayList<>();

}

@OneToManyTeamMember1:N 관계라서 그런 줄 알았는데  mappedBy 속성은 무엇을 의미할까요?

이것은 TeamMember 테이블을 연결시키는 외래 키 칼럼과 매핑된 Member 객체의 필드명을 가리킵니다.

연관 관계의 주인은 연관관계를 갖는 두 객체 사이에서 조회, 저장, 수정, 삭제를 할 수 있지만 연관 관계의 주인이 아니면 조회만 가능합니다. 연관 관계의 주인이 아닌 객체에서  mappedBy  속성을 사용해서 주인을 저장해줘야 합니다.

외래 키가 있는 곳을 연관 관계의 주인으로 정하면 됩니다.

 

무조건 양방향 관계를 하면 되지 않나요?

객체 입자에서 양방향 매핑을 했을 때 오히려 복잡해질 수 있습니다.
예를 들어 비즈니스 애플리케이션에서 사용자(Member) 엔티티는 굉장히 많은 엔티티와 연관 관계를 갖습니다.

이런 경우에 모든 엔티티를 양방향 관계로 설정하게 되면 사용자(Member) 엔티티는 엄청나게 많은 테이블과 연관 관계를 맺게 되고 Member 클래스를 보면 엄청나게 복잡해집니다.

그리고 다른 엔티티들도 불필요한 연관관계 매핑으로 인해 복잡성이 증가할 수 있습니다.
그래서 구분하기 좋은 기준은 기본적으로 단방향으로 매핑을 시켜주고 나중에 객체 탐색이 꼭 필요하다고 느낄 때 추가하는 것으로 잡으면 됩니다.

양방향 매핑 시 가장 많은 실수


//TEST 코드
    @Test
    void 양방향(){
        Team team = new Team();
        team.setName("한팀");
        teamRepository.save(team);
        Member member = Member.builder()
                .name("member1")
                .build();
		team.addMember(member)
        

        Member findMember = memberRepository.save(member);

        assertThat(findMember.getTeam().getName()).isEqualTo("한팀");

    }
    
 //Team
     public void addMember(Member member){
        memberList.add(member);
    }

이렇게 테스트를 하면 결과로 테스트 결과가 'NullPointException' 에러가 뜹니다.

분명 addMember에서 memberList 값에다가 member 객체를 넣어주지 않았는가?

필자도 여기서 상당히 애를 먹었고, 머리가 상당히 아팠습니다...
여기서 백기선님 유튜브하고 영한님 인프런 강의를 참조해서 이해를 했습니다

 

 

 

 

 

 

※결론 ※

               양방향 관계에서는 순수 객체 상태를 고려해서 항상 양쪽으로 값을 설정합시다.
    public void addMember(Member member){
        memberList.add(member);
        member.setTeam(this);
    }

setTeam()으로 member 객체에 team 을 넣어줍니다. 

여기서 또 고민이 될 것이다. Member 객체에 메서드를 추가해서 넣어주어야 할지 Team 객체에 메서드를 추가해서 넣어주어야 할지 여기 고민은 영한님 강의의 질문에서 해답을 찾을 수 있습니다.

 

연관관계 편의 메서드에 관련 문의 - 인프런 | 질문 & 답변

연관관계 편의 메서드에 이점을 느끼고 있습니다만...이를 이해하는데 있어서 상당히 어려움을 느끼고 있습니다.상품 주문 시1. 주문한 member 를 조회2. 주문한 item을 조회3. 주문한 member address를

www.inflearn.com