객체 관계 매핑 ORM 에서 가장 어려운 부분이 바로 객체 연관 관계와 테이블 연관관계를 매핑하는 일이다.

  • 방향(Direction):

    • [ 단방향, 양방향 ]이 있다.
    • 한 쪽만 참조하는 것을 단방향 관계라 하고, 양쪽 모두 서로 참조하는 것을 양방향 관계라 한다.
    • 방향은 객체관계에만 존재하고 테이블 관계는 항상 양방향이다.
  • 다중성(Multiplicity):

    • [ 다대일(N:1), 일대다(1:N), 일대일(1:1), 다대다(N:M) ] 다중성이있다.

    • 예를 들어 회원과 팀이 관계가 있을 때 여러 회원은 한 팀에 속하므로 회원과 팀은 다대일 관계다.

    • 반대로 한 팀에 여러 회원이 소속될 수 있으므로 팀과 회원은 일대다 관계다.

  • 연관관계의 주인(Owner): 객체를 양방향 연관관계로 만들면 연관관계의 주인을 정해야 한다.

5.1 단방향 연관관계

  • 객체 연관관계와 테이블 연관관계의 가장 큰 차이 참조를 통한 연관관계는 언제나 단방향이다.

  • 객체간에 연관관계를 양방향으로 만들고 싶으면 반대쪽에도 필드를 추가해서 참조를 보관해야 한다.

    • 결국 연관관계를 하나 더 만들어야 한다.
  • 이렇게 양쪽에서 서로 참조하는 것을 양방향 연관관계라 한다.

    • 하지만 정확히 이야기하면 이것은 양방향 관계가 아니라 서로 다른 단방향 관계 2개다.
  • 반면에 테이블은 외래 키 하나로 양방향으로 조인할 수 있다.

  • 객체 연관관계 vs 테이블 연관관계 정리

    • 객체는 참조(주소) 로 연관관계를 맺는다.
      • 객체 참조
      • 단방향
      • 객체를 양방향으로 참조하려면 단방향 연관관계를 2개 만들어야 한다.
    • 테이블은 외래 키로 연관관계를 맺는다.
      • 조인(join)
      • 양방향
  • 객체는 참조를 사용해서 연관관계를 탐색할 수 있는데 이것을 객체 그래프 탐색이라 한다.

SCR-20240830-lngo

  • Member.teamMEMBER.TEAM_ID를 매핑하는 것이 연관관계 매핑이다.

  • @ManyToOne: 이름 그대로 다대일(N:1) 관계라는 매핑 정보다.

    • 회원과 팀은 다대일 관계다.
    • 연관관계를 매핑할 때 이렇게 다중성을 나타내는 어노테이션을필수로 사용해야 한다.
  • @JoinColumn(name="TEAM_ID"): 조인 컬럼은 외래 키를 매핑할 때 사용한다.

    • name 속성에는 매핑할 외래 키 이름을 지정한다. 회원과 팀 테이블은 TEAMLID
    • 외래 키로 연관관계를 맺으므로 이 값을 지정하면 된다. 이 어노테이션은 생략할 수 있다.

SCR-20240830-locv

SCR-20240830-loim

5.2 연관관계 사용

JPA에서 엔티티를 저장할 때 연관된 모든 엔티티는 영속 상태여야 한다.

5.2.1 저장

SCR-20240830-lphs

SCR-20240830-lpju

5.2.2 조회

[ 연관관계가 있는 엔티티를 조회 하는 방법 ]

  • 객체 그래프 탐색(객체 연관관계를 사용한 조회)

  • 객체지향 쿼리 사용; JPQL

    • JPQL도 조인을 지원한다.

    • 조인해서 검색조건을 사용하면 된다.

    • :teamName 과 같이 : 로 시작하는 것은 파라미터를 바인딩받는 문법이다.

      SCR-20240830-lpzx

5.2.3 수정

SCR-20240830-lqwk

엔티티의 값만 변경해두면 트랙잭션을 커밋할 때 플러시가 일어나면서 변경 감지 기능이 작동하고, 변경사항을 DB에 자동 반영한다.

5.2.4 연관관계 제거

SCR-20240830-lrfv

5.2.5 연관된 엔티티 삭제

SCR-20240830-lrpd

연관된 엔티티를 삭제하려면 기존에 있던 연관관계를 먼저 제거하고 삭제해야 한다.

그렇지 않으면 외래 키 제약조건으로 인해, 데이터베이스에서 오류가 발생한다.

5.3 양방향 연관관계

  • 팀에서도 회원으로 접근할 수 있도록 하자

SCR-20240830-lrxb

  • 회원 -> 팀 (Member.team)
    • 다대일, N:1
  • 팀 -> 회원 (Team.members)
    • 일대다, 1:N
    • 여러 건과 연관계를 맺을 수 있으므로, 컬렉션 사용

SCR-20240830-lsbz

  • 테이블은 외래 키 하나로 양방향 조회가 가능
    • A JOIN B, B JOIN A 둘 다 가능

SCR-20240830-lsly

SCR-20240830-lsrx

5.4 연관관계의 주인

  • 엄밀히 이야기하면 객체에는 양방향 연관관계라는 것이 없다.

    • 서로 다른 단방향 연관관계 2개를 애플리케이션 로직으로 잘 묶어서 양방향인 것처럼 보이게 할 뿐이다.
  • 반면에 데이터베이스 테이블은 앞서 설명했듯이 외래 키 하나로 양쪽이 서로 조인할 수 있다.

    • 따라서 테이블은 외래 키 하나만으로 양방향 연관관계를 맺는다.
  • 엔티티를 양방향 연관관계로 설정하면 객체의 참조는 둘인데 외래 키는 하나다. 따라서 둘 사이에 차이가 발생한다.

  • 그렇다면 둘 중 어떤 관계를 사용해서 외래 키를 관리해야 할까?

    • JPA에서는 두 객체 연관관계 중 하나를 정해서 테이블의 외래키를 관리해야 하는데, 이를 연관관계의 주인(Owner) 이라 한다.

5.4.1 양방향 매핑의 규칙: 연관관계의 주인

  • 양방향 연관관계 매핑 시 지켜야 할 규칙이 있는데 두 연관관계 중 하나를 연관관계의 주인으로 정해야 한다.
    • 연관관계의 주인만이 데이터베이스 연관관계와 매핑되고 외래 키를 관리 (등록, 수정, 삭제)
    • 반면에 주인이 아닌 쪽은 읽기만 할 수있다.
  • 어떤 연관관계를 주인으로 정할지는 mappedBy 속성을 사용하면 된다.
    • 주인은 mappedBy 속성을 사용하지 않는다.
    • 주인이 아니면 mappedBy 속성을 사용해서 속성의 값으로 연관관계의 주인을 지정해야 한다.

SCR-20240830-lwds

  • 연관관계의 주인을 정한다는 것은 사실 외래 키 관리자를 선택하는 것이다.
    • 회원 테이블에 있는 TEAM_ID 외래 키를 관리할 관리자를 선택해야 한다.
    • 회원 엔티티에 있는 Member.team을 주인으로 선택하면, 자기 테이블에 있는 외래 키를 관리하면 된다.
    • 하지만 팀 엔티티에 있는 Team.members를 주인으로 선택하면 물리적으로 전혀 다른 테이블의 외래 키를 관리해야 한다.

비즈니스 로직상 더 중요하다고 연관관계의 주인으로 선택하면 안 된다.

비즈니스 중요도를 배제하고 단순히 외래 키 관리자 정도의 의미만 부여해야 한다.

5.4.2 연관관계의 주인은 외래 키가 있는 곳

SCR-20240830-lwta

데이터베이스 테이블의 다대일, 일대다 관계에서는 항상 다 쪽이 외래 키를 가진다.

다 쪽인 @ManyToOne은 항상 연관관계의 주인이 되므로 mappedBy를 설정할 수 없다.

따라서 @ManyToOne에는 mappedBy 속성이 없다.

양방향 매핑 시에는 무한 루프에 빠지지 않게 조심해야 한다.

예를 들어 Member.toString()에서 getTeam()을 호출하고 Team.toString()에서 getMember()를 호출하면 무한 루프에 빠질수 있다.

이런 문제는 엔티티를 JSON으로 변환할 때 자주 발생, 그리고 Lombok이라는 라이브러리를 사용할 때도 자주 발생

JSON 라이브러리들은 보통 무한루프에 빠지지 않도록 하는 어노테이션이나 기능을 제공한다.

5.5 양방향 연관관계 저장

SCR-20240830-lxnm

  • 주인이 아닌 곳에 입력된 값은 외래 키에 영향을 주지 않는다.

SCR-20240830-lxnv

  • 양방향 연관관계는 연관관계의 주인이 외래 키를 관리한다.
    • 따라서 주인이 아닌 방향은 값을 설정하지 않아도 데이터베이스에 외래 키 값이 정상 입력된다.

5.6 양방향 연관관계의 주의점

양방향 연관관계를 설정하고 가장 흔히 하는 실수는 연관관계의 주인에는 값을 입력하지 않고, 주인이 아닌 곳에만 값을 입력하는 것이다.

5.6.1 순수한 객체까지 고려한 양방향 연관관계

  • 그렇다면 정말 연관관계의 주인에만 값을 저장하고 주인이 아닌 곳에는 값을 저장하지 않아도 될까?
    • 사실은 객체 관점에서 양쪽 방향에 모두 값을 입력해주는 것이 가장 안전하다.
    • 양쪽 방향 모두 값을 입력하지 않으면 JPA를 사용하지 않는 순수한 객체 상태에서 심각한 문제가 발생할 수 있다.

SCR-20240830-lytz

  • 객체까지 고려하면 이렇게 양쪽 다 관계를 맺어야 한다.
    • Member.team: 연관관계의 주인, 이 값으로 외래 키를 관리한다.
    • Team.members: 연관관계의 주인이 아니다. 따라서 저장 시에 사용되지 않는다.

5.6.2 연관관계 편의 메소드

  • 양방향 관계에서 두 코드(회원 -> 팀, 팀 -> 회원)는 하나인 것처럼 사용하는 것이 안전하다.
    • 양방향 관계를 설정하는 메소드를 연관관계 편의 메소드라 한다.

SCR-20240830-lziw

5.6.3 연관관계 편의 메소드 작성 시 주의사항

SCR-20240830-maeu

  • 연관관계를 변경할 때는 기존 팀이 있으면 기존 팀과 회원의 연관관계를 삭제하는 코드를 추가해야 한다.

SCR-20240830-lzrb

Reference