TIL_221014_자바 ORM 표준 JPA 프로그래밍_04 정리

창고·2022년 10월 14일
0

6. 연관관계 매핑 기초

(1) 목표

  • 객체와 테이블 연관관계의 차이를 이해
  • 객체의 참조와 테이블의 외래 키를 매핑
  • 용어 이해
    • 방향(Direction) : 단방향, 양방향
    • 다중성(Multiplicity) : 다대일, 일대다, 일대일, 다대다 이해
    • 연관관계의 주인(Owner) : 객체 양방향 연관관계는 관리 주인이 필요
  • 추천책 : 객체지향의 사실과 오해, 오브젝트 (조영호 저)
  • 객체를 테이블에 맞추어 데이터 중심으로 모델링 시 -> 협력 관계를 만들 수 없음
    • 테이블은 외래 키로 조인을 사용, 연관 테이블을 찾음
    • 객체는 참조를 사용, 연관 객체를 찾음
    • 즉 테이블과 객체 사이에는 이런 큰 간격이 있음

(2) 단방향 연관관계

  • 예제 시나리오
    • 회원과 팀이 있다
    • 회원은 하나의 팀에만 소속될 수 있다
    • 회원과 팀은 다대일 관계다
      -> 내 프로젝트의 그룹-에이전트, 에이전트-광고주, 광고주-캠페인, 캠페인-소재, 소재-실적
    • 연관관계가 없는 객체, 객체를 테이블에 맞추어 모델링

      -> ERD 수정해야겠다...
    • 객체 지향 모델링 (객체 연관관계 사용)
[Member]

//연관관계 없는 경우
//@Column(name = "TEAM_ID")
//private Long teamId;

//연관관계 설정
@ManyToOne
@JoinColumn(name = "TEAM_ID")
private Team team;

  • 연관관계 수정
Team teamB = new Team();
teamB.setName("TeamB");
em.persist(teamB);

// 회원에 새 팀 설정
member.setTeam(teamB);

(3) 양방향 연관관계와 연관관계의 주인

  • 꽤 어려운 내용 (JPA 계의 Pointer)
  • 연관관계의 주인 부분이 가장 어려움
  • 어려운 이유 : 테이블-객체 간의 차이(외래키 - 참조의 차이) 이해가 선행되어야 함
  • 테이블은 변화가 없는 이유 : Team_ID가 외래 키이므로 이를 조인하여 양방향 관계를 알 수 있음
  • 객체 쪽의 변화 : 기본적으로 한쪽으로만 참조가 되므로 팀 내에 멤버 리스트를 추가하였음
  • 양방향 매핑
[Team]

@OneToMany(mappedBy = "team")
List<Member> members = new ArrayList<Member>();
  • 팀 내 멤버 조회
List<Member> members = findMember.getTeam().getMembers();

for (Member m : members) {
	System.out.println("m = " + m.getUsername());
}
  • 연관관계의 주인과 mappedBy

    • mappedBy = JPA의 멘탈 붕괴 난이도
    • mappedBy는 처음에는 이해하기 어렵다
    • 객체와 테이블 간에 연관관계를 맺는 차이를 이해해야 한다.
  • 객체와 테이블이 관계를 맺는 차이

    • 객체 연관관계 = 2개
      • 회원 -> 팀 연관관계 1개 (단방향)
      • 팀 -> 회원 연관관계 1개 (단방향)
    • 테이블 연관관계 = 1개
      • 회원 <-> 팀의 연관관계 1개 (양방향, 사실 방향 개념이 없긴 함) FK 조인 하나로 끝
  • 객체의 양방향 관계

    • 객체의 양방향 관계는 사실 양방향 관계가 아닌, 서로 다른 단방향 관계 2개임
    • A -> B (a.getB())
    class A {
    	B b;
     }
    • B -> A (b.getA())
    class B {
    	A a;
    }
    
  • 테이블의 양방향 연관관계

    • 테이블은 외래 키 하나로 두 테이블의 연관관계를 관리
    • MEMBER.TEAM_ID 외래 키 하나로 양방향 연관관계 가짐 (양쪽 조인 가능)
    SELECT *
    FROM MEMBER M
    JOIN TEAM T ON M.TEAM_ID = T.TEAM_ID
    
    SELECT *
    FROM TEAM T
    JOIN MEMBER M ON T.TEAM_ID = M.TEAM_ID
  • 두 패러다임 차이에 따른 딜레마

  • 멤버의 팀을 옮기고 싶다

    • 객체 입장은 복잡하다
      • Member 객체에서 Team을 바꿔?
      • Team 객체에서 Members의 값을 바꿔?
    • DB 입장에서는 다 필요 없고 FK값만 바뀌면 됨
    • 따라서 연관관계의 주인을 정해서 한쪽의 값만 바꿔야 함
  • 연관관계의 주인(Owner)과 양방향 매핑 규칙

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

    • 외래 키가 있는 곳을 주인으로 정하라
    • 비즈니스적으로 중요하다는 의미는 아니고 DB 테이블 입장에서 '다' 쪽인 곳이 주인이 됨
  • 다행히 내 프로젝트는 양방향 끼리는 잘 매핑되어있음

  • 양방향 매핑 시 가장 많이 하는 실수 : 연관관계의 주인에 값을 입력하지 않음

Team team = new Team();
team.setName("TeamA");
em.persist(team);

Member member = new Member();
member.setName("member1");

// 역방향 (주인이 아닌 방향) 만 연관관계 설정
team.getMembers().add(member);

em.persist(member); // teamId null로 뜸
// 왜냐? Member가 연관관계의 주인인데 Member의 팀은 정해지지 않았음
// Team은 읽기 전용이므로 members에 member를 추가할 수 없음
  • 양방향 매핑 시 연관관계의 주인에 값을 반드시 입력해야 한다.
    (순수한 객체 관계를 고려하면 항상 양쪽 다 값을 입력해야 한다.)
Team team = new Team();
team.setName("TeamA");
em.persist(team);

Member member = new Member();
member.setName("member1");
member.setTeam(team); // 연관관계의 주인인 Member의 팀을 지정
team.getMembers().add(member); // 역방향 관계인 Team에도 member를 추가 (객체 관계 고려)

em.persist(member);
  • 양뱡향 연관관계 주의
    • 순수 객체 상태를 고려하여 항상 양쪽에 값을 설정해야 함
    • 연관관계 편의 메소드를 생성 (예시 : Member의 setTeam 메소드) -> 어느 한 쪽에만!
    public void setTeam(Team team) {
    	this.team = team;
      	team.getMembers().add(this); // 역방향 세팅까지 같이
    }  
    • 양방향 매핑 시 무한 루프 조심할 것 -> 예전에 실제로 걸렸음...
      • toString(), Lombok, JSON 생성 라이브러리 (엔티티를 직접 다루면서 JSON으로 변환할 때)
  • 양방향 매핑 정리
    • 단방향 매핑만으로도 이미 연관관계 매핑은 완료 (단방향으로 설계하는 것이 좋다)
    • 양방향 매핑은 반대 방향으로 조회(객체 그래프 탐색) 기능이 추가된 것 뿐!!
    • JPQL에서 역방향으로 탐색할 일이 많음
    • 단방향 매핑을 잘 하고 양방향은 필요할 때 추가해도 됨
      (테이블에 영향을 주지 않음)
    • 기본적으로 단방향으로 끝내버리고 ('다' 인 곳에 ManyToOne) 이후 양방향으로
    • 연관관계의 주인을 정하는 기준 : 비즈니스 로직을 기준으로 하지 말고 외래 키의 위치를 기준으로 정해야 함

(4) 실전 예제

  • 객체 변경 전 : 연관관계 매핑 숙지 전
  • 객체 변경 후 : 객체 참조를 사용하도록 변경
  • 강의에서 프로젝트 수정 사항 찾은 것
    • 연관관계를 잘 끊어내는게 좋음
    • 상위 객체는 비즈니스 관점에서 봤을 때 중요한 건이 아닌 이상 굳이 하위 객체 리스트를 가질 필요가 없음 (=단방향으로 처리해도 충분함)
    • 예를 들어, 에이전트 - 광고주 관계면 이미 광고주가 에이전트 FK를 가지고 있으므로 어느 에이전트의 광고주 리스트를 뽑자! 하면 에이전트 FK로 조회를 하면 끝나는 것이지 굳이 에이전트로 가서 광고주 리스트를 찾을 필요가 없다...
    • 다만 실전에서는 JPQL로 역방향 조회를 하는 편도 많으니 양방향 관계를 쓸 수 밖에 없긴 함... 어렵다 ㅋㅋㅋㅋㅋㅋ; 최대한 단방향으로 해보고 안되면 양방향 해보자
    • 일단 지금 프로젝트에서는 고민을 해보자...
profile
공부했던 내용들을 모아둔 창고입니다.

0개의 댓글