[JPA] 연관관계 매핑

19·2022년 9월 22일
0

JPA

목록 보기
8/18
post-custom-banner

연관관계 매핑

테이블에 맞춰서 외래키를 가져와서 조회하는 방식이 아니라, 다이렉트로 객체를 가져올 수 있는 연관관계를 맺어서 객체지향스럽게 설계하는 방법

// 주문한 멤버 찾기, 객체지향스럽지 않은 방식
Order order = em.find(Order.class, 1L);
Long memberId = order.getMemberId();
Member member = em.find(Member.class, memberId);

// 식별자를 가져와서 객체를 가져오는 것이 아니라, 바로 객체를 가져올 수 있어야한다.
Order order = em.find(Order.class, 1L);
Member member = order.getMember();

객체와 테이블 연관관계의 차이를 이해해야 하고, 이해를 바탕으로 객체의 '참조'와 테이블의 '외래키'를 매핑하는 방법을 알아야 한다.

용어 이해

  • 방향 (Direction)
    • 단방향, 양방향
  • 다중성 (Multiplicity)
    • 다대일(N:1), 일대다(1:N), 일대일(1:1), 다대다(N:M)
  • 연관관계의 주인 (Owner)
    • 객체 양방향 연관관계는 주인이 필요

연관관계 필요 이유?

연관관계가 없는 경우)

  • 객체를 테이블에 맞추어 모델링 한 예시이다.

Meber와 Team은 N:1 관계

@Entity
@Getter @Setter
public class Member {

    ...
    // 외래키
   @Column(name = "TEAM_ID")
   public Long teamId;
    ...
}

객체 참조가 아닌, '외래키'를 그대로 사용한다.


// 저장
Team team = new Team();
team.setName("Arsenal");
em.persist(team);

Member member = new Member();
member.setUsername("사카");
member.setTeamId(team.getId()); // 외래키를 직접 다룬다, 객체지향적 x
em.persist(member);

// 조회
Member findMember = em.find(Member.class, member.getId());
// 객체를 바로 꺼낼 수 있는 것이 아니라, 외래키를 사용해서 꺼내야함
Long findTeamId = findMember.getTeamId();
Team findTeam = em.find(Team.class, findTeamId);

tx.commit();

Member에 Team을 넣으려면 외래키를 직접 다뤄야 한다.
이는 객체지향적이 아닌 테이블 중심이다.
(테이블은 외래 키로 조인을 사용해 연관된 테이블을 찾고, 객체는 참조를 사용해 연관된 객체를 찾는다)
테이블 중심으로 모델링하면 협력 관계를 만들 수 없다.


단방향 연관관계

연관관계 사용한 경우)

  • 단방향 연관관계 사용
  • Member에 외래키가 아닌 객체 참조가 넣어져 있다.

@Entity
@Getter @Setter
public class Member {

    ...
    // 외래키
//    @Column(name = "TEAM_ID")
//    public Long teamId;

//     ORM 매핑
    @ManyToOne // 연관관계 설정
    @JoinColumn(name = "TEAM_ID")   // 외래키와 매핑
    private Team team;
    ...
}

'외래키'를 그대로 사용하지 않고 '객체 참조'를 사용한다.
연관관계 매핑을 통해 객체 참조와 외래키를 매핑한다.


  • 객체의 참조와 테이블의 외래키를 매핑시켜서 연관관계를 매핑한다

// 저장
Team team = new Team();
team.setName("Arsenal");
em.persist(team);

Member member = new Member();
member.setUsername("사카");
member.setTeam(team);   // JPA가 알아서 PK값을 FK로 등록해준다. 단방향 연관관계 설정
em.persist(member);

// 조회
Member findMember = em.find(Member.class, member.getId());

// 객체를 바로 꺼낼 수 있다.
Team findTeam = findMember.getTeam();
System.out.println("findTeam.getName() = " + findTeam.getName());
tx.commit();

외래키를 직접 다루지 않고, 객체 참조로 값을 넣다 뺐다할 수 있다.


양방향 연관관계

양방향 연관관계)

  • 서로 참조할 수 있다.
  • 테이블은 외래키를 통해 연관관계가 맺어지면 양방향으로 참조 가능
  • 객체에서 양방향 관계는 서로를 참조하는 단방향 관계 2개인 것
    • (Member -> Team, Team -> Member)
    • 객체에서 테이블처럼 양방향으로 참조하려면 단방향 연관관계 2개를 만들어야 한다.

Member)

@Entity
@Getter @Setter
public class Member {

//     ORM 매핑
    @ManyToOne // 연관관계 설정
    @JoinColumn(name = "TEAM_ID")   // 외래키와 매핑
    private Team team;
    ...
}
  • 단방향 매핑과 동일하게 매핑해주었다.

Team)

@Entity
@Getter @Setter
public class Team {

    @OneToMany(mappedBy = "team") // 무엇과 매핑되어 있는지 (Member의 team 필드와 매핑)
    private List<Member> members = new ArrayList<>();
    ...
}
  • 양방향으로 매핑하기 위해 연관관계 설정 후, mappedBy옵션을 달아주었다.

양방향으로 매핑하면 서로 참조하기 때문에, 반대 방향으로 탐색이 가능하다.


mappedBy? (연관관계 주인)

  • 테이블은 외래키를 통해 연관관계를 맺고 양방향 참조가 가능하다
  • 객체는 양방향 참조를 위해서는 서로를 참조하는 단방향 연관관계 2개를 만들어야 한다.
  • 위 그림은 그래서 단방향 연관관계 2개를 만들어 양방향 매핑을 한 그림이다.

테이블은 외래키 하나로 두 테이블의 연관관계를 관리한다.
그럼 객체는 어떻게 해야할까?

객체는 서로 참조하는 2개의 단방향 연관관계로 인해 생긴 2개의 필드 중, 어느 값을 바꿔야 테이블의 외래키에 반영이 되는지를 고려해야한다.
(테이블의 외래키를 관리할 곳을 지정해야 한다.)

그래서 객체에서 2개의 단방향 연관관계중에서 하나를 연관관계의 주인으로 지정해야 한다. 주인은 외래 키를 관리(@JoinColumn)하고, 주인이 아닌쪽은 읽기(mappedby)만 가능하다.

주인은 테이블에서 '외래키가 있는 곳'을 주인으로 정한다.
Member와 Team은 다대일 관계로, Member에 외래키가 담겨있기 때문에, Member 객체의 Team객체가 연관관계의 주인이 되는 것이다.


양방향 연관관계 주의

순수 객체 상태를 고려해서 양쪽에 값을 세팅해주어야 한다.
연관관계 편의 메소드를 통해 양쪽에 값을 세팅해주자
양방향 매핑시 무한 루프를 조심하자
ex) JSON 생성 라이브러리, toString(), lombok등

연관관계 편의 메소드)

@Entity
@Getter @Setter
public class Member {
    ...

    // 연관관계 편의 메소드, 양방향 관계일 때 사용
    public void changeTeam(Team team) {
        // 양쪽에 값을 세팅하기 위함
        // 하나의 값을 변경할 때, 나머지 하나의 값도 같이 바꿔줌
        this.team = team;
        team.getMembers().add(this);
    }
}
  • 양방향 관계일 때, 양쪽에 값을 편하게 설정하기 위해 사용한다.
  • 양쪽에 메소드를 만드는 것이 아니라, 한 쪽을 선택해서 한 쪽에만 만들고 사용한다.

정리

처음 설계 시, 단방향 매핑으로 설계를 끝내야 한다
처음부터 양방향 매핑을 할 필요는 없다.
양방향 매핑을 하게 되면 신경쓸 게 많아진다. (연관관계 편의 메소드 생성, 연관관계 주인 설정등)

객체와 테이블 매핑은 단방향 매핑으로 끝난 것이며, 양방향 매핑은 반대 방향으로 조회하는 기능이 추가된 것 뿐이다.

단방향 매핑을 잘 해놓고 양방향 매핑은 필요할 때 추가하면 된다.
(JPQL로 역방향으로 탐색할 일이 많다.)

연관관계의 주인은 외래 키의 위치를 기준으로 정해야 한다.
(비즈니스 로직을 기준으로 연관관계의 주인을 선택하면 안된다.)


연관관계 매핑 시 고려사항 3가지

  • 다중성
    • JPA 어노테이션들은 DB와 매핑하기 위해 존재하며, 데이터베이스 관점에서의 다중성을 기준으로 한다.
    • 다대일 @ManyToOne
    • 일대다 @OneToMany
    • 일대일 @OneToOne
    • 다대다 @ManyToMany
      • 다대다는 실무에서 안쓴다
  • 단방향, 양방향
  • 연관관계 주인

다대일 [N:1]

'다'쪽이 연관관계 주인인 경우


다대일 단방향

  • 가장 많이 사용하는 케이스
  • 외래키가 있는 곳에 연관된 객체 참조를 넣고 연관관계 매핑하는 방식 (EX. team)
  • '다'쪽에 외래키가 있어야 한다.

다대일 양방향

  • 반대방향을 탐색할 수 있도록 객체 참조가 추가된 형태
  • 단순 조회만 가능하고 테이블의 영향을 미치지 않는다.
  • 외래키가 있는 쪽이 연관관계 주인
  • 양쪽을 서로 참조하며 개발할 때 사용

일대다 [1:N]

'1'쪽이 연관관계 주인인 경우


일대다 단방향

  • 권장하지 않는 방식, 실무에서도 거의 안쓴다
  • 객체와 테이블의 차이때문에 반대편 테이블의 외래키를 관리하는 특이한 구조
    • 엔티티가 관리하는 외래키가 다른 테이블에 있다
    • 연관관계 관리를 위해 추가로 Update 쿼리가 추가로 날라간다
  • 다대일 양방향 매핑 사용을 권장

일대다 양방향

  • 공식 스펙이 아님
  • 다대일 양방향 매핑 사용을 권장

일대일 [1:1]

1:1이기 때문에 외래키를 어디에 넣을지 선택할 수 있다
주 테이블에 넣던지, 대상 테이블에 넣던지


일대일 단방향

주 테이블 외래키 단방향)

  • 다대일 단방향 매핑과 유사

대상 테이블 외래키 단방향)

  • JPA 지원 X

일대일 양방향

주 테이블 외래키 양방향)

  • 다대일 양방향 매핑처럼 외래키가 있는 곳이 연관관계 주인
  • 반대편은 mappedBy 적용한다

대상 테이블 외래키 양방향)

  • 주 테이블 외래키 양방향과 매핑 방법 같음

다대다 [N:M]

관계형 데이터베이스는 정규화된 테이블 2개로 다대다 관계를 표현할 수 없다.
연결 테이블을 추가해서, 다대일 관계로 풀어내야 한다.

  • 실무에서 사용할 수 없다.
    • 쿼리가 이상하게 나간다

-> 연결 테이블용 엔티티를 추가한다.


  • 다대다 관계인 @ManyToMany -> @OneToMany, @ManyToOne으로 풀어낸다



참고

자바 ORM 표준 JPA 프로그래밍 - 기본편 - 김영한

profile
하나씩 차근차근
post-custom-banner

0개의 댓글