본문 바로가기
Programming/Database & Query

[JPA] 자바 ORM 표준 JPA 프로그래밍 : 연관관계 매핑 기초

by 읽고 쓰는 개발자 2022. 1. 31.

목표

  • 객체와 테이블 연관관계의 차이를 이해
  • 객체의 참조와 테이블의 외래 키를 매핑
  • 용어 이해
    • 방향(Direction) : 단방향, 양방향
    • 다중성(Multiplicity) : 다대일(N:1), 일대다(1:N), 일대일(1:1), 다대다(N:M) 이해
    • 연관관계의 주인(Owner) : 객체 양방향 연관관계는 관리 주인이 필요

객체지향 설계의 목표는 자율적인 객체들의 협력 공동체를 만드는 것이다. - 조영호 (객체지향의 사실과 오해)


객체를 테이블에 맞추어 데이터 중심으로 모델링하면, 협력 관계를 만들 수 없다.

  • 테이블은 외래 키로 조인을 사용해서 연관된 테이블을 찾는다.
  • 객체는 참조를 사용해서 연관된 객체를 찾는다.
  • 테이블과 객체 사이에는 이런 큰 간격이 있다.

회원 저장시: 팀 저장 → 저장된 팀 ID 조회 → 회원 객체에 팀 ID 세팅 → 회원 저장

팀 조회시: 회원 조회 → 조회된 회원 객체의 팀 ID(식별자)로 다시 팀 조회

객체 지향적인 방법이 아님!

단방향 연관관계

객체 지향 모델링 (객체 연관관계를 사용)

  • (App) Member 객체가 Team 객체 참조하는 구조로 설계
// 객체의 참조와 테이블의 외래 키를 매핑
@Entity
public class Member {
    @Id
    private Long id;

    @Column(name = "name")
    private String username;

    private Integer age;  

    @ManyToOne  //단방향 연관관계 
    @JoinColumn(name = "TEAM_ID")
    private Team team;
}
// 연관관계 저장 
//          팀 저장 
            Team team = new Team();
            team.setName("TeamA");
            
//            회원 저장
            Member member = new Member();
            member.setUsername("member1");
            member.setTeam(team);   // 단방향 연관관계 설정, 참조 저장
            
            em.persist(member);

// 참조로 연관관계 조회 - 객체 그래프 탐색
//            조회 
            Member findMember = em.find(Member.class, member.getId());
            
//            참조를 사용해서 연관관계 조회 
            Team findTeam = findMember.getTeam();

// 연관관계 수정 
//            새로운 팀B
            Team teamB = new Team();
            team.setName("teamB");
            em.persist(teamB);
            
//            회원1에 새로운 팀B 설정 
            member.setTeam(teamB);

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

양방향 매핑 : 양쪽에서 모두 참조를 통해 접근할 수 있으면 양방향 연관관계

  • 테이블의 연관관계 : FK만 있으면 양방향 접근이 가능!
  • 객체의 연관관계 : 참조 객체를 양쪽에 다 넣어줘야만 양방향 접근이 가능
  • → 결국 단방향 연관관계를 두 번 맺는 것!

// 양방향 매핑 - Member 엔티티는 단방향과 동일
@Entity
public class Member {
    @Id
    private Long id;

    @Column(name = "name")
    private String username;

    private Integer age;  

    @ManyToOne 
    @JoinColumn(name = "TEAM_ID")
    private Team team;
}
// 양방향 매핑 - Team 엔티티는 컬렉션 추가
@Entity
public class Team {
    @Id
    private Long id;

    @Column(name = "name")
    private String name;

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

연관관계 주인과 mappedBy

  • mappedBy = JPA의 가장 고난이도 개념
  • mappedBy는 처음부터 이해하기 어려움
  • 객체와 테이블간의 연관관계 맺는 차이를 이해해야 함!

객체의 양방향 관계

  • 객체의 양방향 관계는 양방향 관계가 아니라 서로 다른 단방향 관계 2개이다.
  • 객체를 양방향으로 참조하려면 단방향 연관관계를 2개 만들어야 한다.
  • A → B (a.getB())
  • B → A (b.getA())

테이블의 양방향 관계

  • 테이블은 외래 키 하나로 두 테이블의 연관관계를 관리
  • MEMBER.TEAM_ID 외래 키 하나로 양방향 연관관계 가짐 (양쪽 조인 가능)
  • JOIN으로 양방향 관계 맺기만 하면 됨

연관관계의 주인(Owner)

  • 두 Entity의 key 중 하나를 외래키로 관리해야 함
  • 양방향 매핑 규칙
    • 객체의 두 관계 중 하나르 연관관계의 주인으로 지정
    • 연관관계의 주인만이 외래키를 관리 (등록, 수정)
    • 주인이 아닌 쪽은 읽기만 가능!!
    • 주인은 mappedBy 속성 사용 하지 않음
    • 주인이 아니면 mappedBy 속성으로 주인 지정
  • 누구를 주인으로?
    • 외래키가 있는 곳을 주인으로 정해라 (진짜 매핑) → 가이드
    • 예제에서는 Member.team이 연관관계의 주인
    • DB의 N(다)쪽이 연관관계의 주인이 되는 것
    • 연관관계 주인은 비즈니스 관련 개념이 아님. 단순 N쪽이 연관관계의 주인이라고 세팅하면 됨.

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

→ 연관관계의 주인에 값을 입력하지 않는 실수!!

→ 순수한 객체 관계를 고려하면 항상 양쪽 다 값을 입력해야 한다.

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

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

//역방향(주인이 아닌 방향)만 연관관계 설정 
team.getMembers().add(member); // mappedBy라 읽기전용! 가짜 관계이기 때문에 저장 안됨!!

em.persist(member);

//결과
// Team_ID = null

member.setTeam(team);  // 주인 엔티티에 넣어줘야 저장이 됨 !!
em.persiste(member);

//결과
// Team_ID = 1

양방향 연관관계 주의

  • 순수 객체 상태를 고려해서 항상 양쪽에 값을 설정하자
    • flush, clear 하지 않은 상태 → 1차캐시에서 값 가져올 때 (DB 쿼리 날리지 않을 때) DB와 다른 값이 나올 수 있음
    • TestCode 작성할 때도 값 일치하지 않을 가능성 있음
  • 연관관계 편의 메소드를 생성하자
    • 예) Member에 changeTeam() (or setTeam()) 메소드에 양쪽 값 세팅 로직 넣어주기
  • 양방향 매핑시에 무한 루프를 조심하자
    • 예) toString(), lombok, JSON 생성 라이브러리
    • Controller에서 Entity를 바로 반환을 하면 Entity가 JSON형태로 생성되어 return되기 때문에 무한루프 생성될 확률 높음 ( + Entity 필드 변경되는 순간 API 스펙이 바로 변경되어 문제가 됨)
    • → Entity는 DTO로 변환하여 반환하라!
    • lombok 자동 toString() 기능은 쓰지 말고, Controller에서는 절대 Entity를 반환하지 말아라.

양방향 매핑 정리

  • 단방향 매핑만으로도 이미 연관관계 매핑은 완료 ( 처음 설계시 단방향만! 그것만으로 관계 설계는 완료된 것. 반대방향에서 조회 기능이 필요할 때 양방향으로 바꾸기!)
  • 양방향 매핑은 반대 방향으로 조회(객체 그래프 탐색) 기능이 추가된 것 뿐
  • JPQL에서 역방향으로 탐색할 일이 많음
  • 단방향 매핑을 잘 하고 양방향은 필요할 때 추가해도 됨(테이블에 영향을 주지 않음)

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

  • 비즈니스 로직을 기준으로 연관관계의 주인을 선택하면 안됨
  • 연관관계의 주인은 외래키의 위치를 기준으로 정해야 함 

 

 

출처 : 자바 ORM 표준 JPA 프로그래밍-기본편 (김영한), 자바 ORM 표준 JPA 프로그래밍(김영한 저)