JPA에 대해 - JPA 연관관계 매핑

쿠우·2022년 12월 27일
0

연관관계 매핑 기초

  • 객체지향스럽게 만드는 방법의 기본이자 어려운 부분
  • 객체와 테이블의 연관관계의 차이를 이해해야한다.
  • 객체를 테이블에 맞춰 데이터 중심으로 모델링하면 , 협력관계를 만들 수 없다.

    왜냐하면, 테이블은 외래 키로 조인을 사용해서 연관된 테이블을 찾고 객체는 참조를 사용해서 연관된 객체를 찾는다.
    따라서, 테이블과 객체 사이에는 큰 간격이 존재.

객체 지향 모델링

  • 객체의 참조와 테이블의 외래 키를 매핑
// 기존 설명의 데이터 중심 방식
// @Column(name = "TEAM_ID")
// private Long teamId;

// 객체 지향 방식 
 @ManyToOne
 @JoinColumn(name = "TEAM_ID")
 private Team team;

양방향 연관관계와 연관관계의 주인

  • 테이블에 연관관계에서는 방향이 없다. 그냥 외래키와 조인만 있으면 된다.
    하지만, 객체가 중요하다 반대편에 어떤 것과 관계 매핑되어 있는지 알려줘야 한다.

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

가급적이면 단방향이 고려사항이 적어 더 좋다.

객체와 테이블이 관계를 맺는 차이점

  • 객체 연관관계 = 2개
    회원 -> 팀 연관관계 1개 (단방향)
    팀 -> 회원 연관관계 1개 (단방향)
  • 테이블 연관관계 = 1개
    회원<->팀 의 연관관계 1개 (양방향)

객체의 양방향 관계

  • 객체의 양방향 관계는 사실 양방향 관계가 아니라 서로 다른 단방향 관계 2개
    결국, 객체를 양방향으로 참조하려면 단방향 연관관계를 2개 만들어야 한다.

테이블의 양방향 연관관계

  • 테이블은 외래 키 하나로 두 테이블의 연관관계 관리 하고 조인하여 참조 한다.

여기서 오는 딜레마, 어떤 객체로 테이블의 외래키를 관리 해야하나?

  • Member와 Team 객체 둘다 연관관계 매핑을 해놓았기 때문에 둘 다 관리가 가능은 하지만
    둘 중 하나로만 외래키를 관리해야한다. 여기서 나오는 개념이 연관관계의 주인

연관관계의 주인(Owner)

  • 양방향 관계에서만 있는 개념이다.

양방향 매핑 규칙!!!

  • 객체의 두 관계중 하나를 연관관계의 주인으로 지정
  • 연관관계의 주인만이 외래 키를 관리(등록, 수정)
  • 주인이 아닌쪽은 읽기만 가능
  • 주인은 mappedBy 속성 사용X
  • 주인이 아니면 mappedBy 속성으로 주인 지정

어떤 것을 주인으로 하는 것이 좋을까?

  • 외래키가 있는 곳을 주인으로 정하면 된다.
  • Member.team가 연관관계 주인

양방향 매핑시 가장 많이 하는 실수

1.순수 객체 상태를 고려해서 항상 양쪽에 값을 설정하자

(양방향 매핑시 양쪽 다 값을 세팅해줘야한다.)

team.getMembers().add(member);

 //연관관계의 주인에 값 설정
 member.setTeam(team); //**

2.연관관계 편의 메소드를 생성하자

(위처럼 양쪽 값 세팅을 둘 다 하는게 깜빡 할 수 있기 때문에 메소드를 만들어 버린다.)
(반대로 Team쪽에도 연관관계 편의 메서드를 만들 수 있다. 정해서 한쪽에만 만드는 것이 좋다. )

class Member {
.
.
.
  public void changeTeam(Team team){
      this.team = team;
      team.getMembers().add(this);
  }
}

3.양방향 매핑시에 무한 루프를 조심하자

ex) toString(), lombok, JSON 생성 라이브러리 (서로를 매핑으로 참조하다보니까 왔다갔다 계속무한루프에 빠짐)

양방향 매핑에 대한 결론

  • 단방향 매핑만으로도 이미 연관관계 매핑은 완료(양방향은 고민거리가 추가된다.)
  • 양방향이 아니어도 다른방법으로 충분히 만들 수 있다. 실무에서는 JPQL때문에 양방향 조회가 필요할 때가 있어서 주로 용함.
  • 양방향 매핑은 반대 방향으로 조회(객체 그래프 탐색) 기능이 추가된 것 뿐
  • JPQL에서 역방향으로 탐색할 일이 많음, 단방향 매핑을 잘 하고 양방향은 필요할 때 추가해도 됨 (테이블에 영향을 주지 않음)

연관관계의 주인을 정하는 기준

  • 비즈니스 로직을 기준으로 연관관계 주인을 선택하지 않는다.
  • 외래키를 가지고 있는 기준으로 정해야한다.

다양한 연관관계 매핑

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

1. 다중성

  • 다대일: @ManyToOne
  • 일대다: @OneToMany
  • 일대일: @OneToOne
  • 다대다: @ManyToMany

2. 단방향, 양방향

  • 테이블: 외래키 하나로 양쪽 조인가능 , 방향이라는 개념 x
  • 객체 : 참조용 필드가 있는 쪽으로 참조 가능 , 한쪽만 참조는 단방향, 양쪽 참조시 양방향

3. 연관관계의 주인

  • 객체는 참조가 2군데 되고 테이블은 외래키가 하나로 연관관계를 맺는데 이 때 노선 정리를 해주는 것을 의미
  • 외래키가 있는 곳에 연관관계의 주인으로 설정
  • 주인의 반대편은 외래키에 영향을 주지 않고 단순 조회만 가능

다대일 [N:1]

단방향 일 때

  • 가장 많이 사용하는 연관관계
  • 외래키가 있는 곳에 참조를 걸고 연관관계 매핑을 하면 된다.

양방향 일 때

  • 외래 키가 있는 쪽이 연관관계의 주인
  • 양쪽을 서로 참조하도록 개발

일대다 [1:N]

단방향 일 때

  • 일대다 단방향은 일대다(1:N)에서 일(1)이 연관관계의 주인
  • 테이블 일대다 관계는 항상 다(N) 쪽에 외래 키가 있음
  • @JoinColumn을 꼭 사용해야 함. 그렇지 않으면 조인 테이블 방식을 사용헌디야(중간에 테이블을 하나 추가함)
  • 객체와 테이블의 차이 때문에 반대편 테이블의 외래 키를 관리하는 특이한 구조
  • 단점 : 엔티티가 관리하는 외래 키가 다른 테이블에 있고, 연관관계 관리를 위해 추가로 UPDATE SQL 실행
  • 따라서, 일대다 단방향 매핑보다는 다대일 양방향 매핑을 사용하자

양방향에 대해서

  • 이런 매핑은 공식적으로 존재X (가라임)
  • 깔끔하게 그냥 다대일 양방향을 사용하자

일대일 [1:1]

  • 일대일 관계는 그 반대도 일대일
  • 주 테이블이나, 대상 테이블 중에 둘 중 아무거나 외래 키 선택 가능
  • 외래 키에 데이터베이스 유니크(UNI) 제약조건 추가

주 테이블에 외래키를 매핑하는 방법, 단방향과 양방향 둘 다 다대일 관계와 유사하게 사용

  • 다대일 양방향 매핑 처럼 외래 키가 있는 곳이 연관관계의 주인, 반대편은 mappedBy 적용
  • 객체지향 개발자 선호
  • 장점 : 주 테이블만 조회해도 대상 테이블에 데이터가 있는지 확인 가능
  • 단점 : 값이 없으면 외래 키에 null 허용(DB개발자들이 싫어함)

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

  • 대상 테이블에 외래키 단방향은 JPA 지원X
  • 양방향만 지원한다.

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

  • 전통적인 데이터베이스 개발자 선호
  • 일대일 주 테이블에 외래 키 양방향과 매핑 방법은 같다. 그냥 주랑 대상만 바뀐 것.
  • 장점: 주 테이블과 대상 테이블을 일대일에서 일대다 관계로 변경할 때 테이블 구조 유지
  • 단점: 프록시 기능의 한계로 지연 로딩으로 설정해도 항상 즉시 로딩됨

정리 하자면, 주 테이블에 외래 키를 매핑하는 것이 객체지향적으로나 JPA 단방향 매핑이 가능하여 편리하다.
너무 여기에 매몰되지 말고 그냥 상황에 따라 자연스럽게 받아들이고 설계해도 된다.

다대다 [N:M] 사용하지 않는다.

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

다대다 관계 일 때?

  • 정규화 하듯이 풀어준다.
  • 연결 테이블용 엔티티 추가(연결 테이블을 엔티티로 승격)
  • @ManyToMany -> @OneToMany, @ManyToOne

주요 연관관계 매핑 어노테이션

@JoinColumn (외래 키를 매핑할 때 사용) 속성

  • name : 매핑할 외래키 이름 (기본값 = 필드명 + _ + 참조하는 테이블의 기본 키 컬럼명)
  • referencedColumnName : 외래 키가 참조하는 대상 테이블의 컬럼명
    (기본값 = 참조하는 테이블 기본키 컬럼명)
    (개발하다보면 외래키의 참조하는 대상의 컬럼명이 달라질 수 있다 이 때 사용)
  • foreignKey(DDL) : 외래 키 제약조건을 직접 지정(테이블을 생성할 때만 이 속성 사용)
  • 나머지 @Column의 속성과 같다.

@ManyToOne의 주요 속성

  • optional : false로 설정하면 연관된 엔티티가 항상 있어야 한다. (기본값 = TRUE)
  • fetch : 글로벌 페치 전략을 설정한다. (기본값 = FetchType.EAGER )
  • cascade : 영속성 전이 기능을 사용한다.
  • targetEntity : 연관된 엔티티의 타입 정보를 설정한다. 이 기능은 거의 사용하지 않는다. 컬렉션을 사용해도 제네릭으로 타입 정보를 알 수 있다.

@OneToMany의 주요속성

  • mappedBy : 연관관계의 주인 필드를 선택한다.
  • fetch : 글로벌 페치 전략을 설정한다. (기본값 = FetchType.LAZY)
  • cascade : 영속성 전이 기능을 사용한다.
  • targetEntity : 연관된 엔티티의 타입 정보를 설정한다. 이 기능은 거의 사용하지 않는다. 컬렉션을 사용해도 제네릭으로 타입 정보를 알 수 있다.
profile
일단 흐자

0개의 댓글