[JPA] 다양한 연관관계 매핑 (1)

30661 단어 TILJPAJPA

제가 프로젝트 3개쯤 날려먹었기🙂⛏ 때문에🤤🔧 (jpashop, jpashop(실전편), ex1-hello-jp)
새로 프로젝트를😉🪓 생성해 실습하도록 하겠습니다.


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

  • 다중성
  • 단방향, 양방향
  • 연관관계의 주인

📌 다중성

  • 다대일: @ManyToOne → 제일 많이 사용
  • 일대다: @OneToMany
  • 일대일: @OneToOne
  • 다대다: @ManyToMany → 사실은 실무에서 쓰면 안됨

JPA가 이 네가지 애노테이션을 제공. (DB와 매핑하기 위해 존재)
대칭성이 있기 때문에 헷갈릴 때는 반대로도 생각해보자.

📌 단방향, 양방향

테이블
  • 외래 키 하나로 양쪽을 조인 가능
  • 사실 방향이란 개념은 없음
객체
  • 참조용 필드가 있는 쪽으로만 참조 가능
  • 한쪽만 참조 → 단방향
  • 양쪽이 서로 참조 → 양방향(이라곤 하지만 사실 단방향이 2개)

📌 연관관계의 주인

  • 테이블은 외래 키 하나로 두 테이블이 연관관계를 맺음
  • 객체 양방향 관계는 참조가 두 군데
  • 객체 양방향 관계는 참조가 두 군데라 외래 키를 관리할 곳을 지정해줘야 함
  • 연관관계의 주인: 외래 키를 관리하는 참조
  • 주인의 반대편: 외래키에 영향을 주지 않으며 단순 조회만 가능

[1] 다대일[N:1]

프로젝트 생성..

JPA 시작하기 포스트를 보면서 ex1-hello-jpa 생성하기..

다대일 단방향

  • 가장 많이 사용
  • 다대일의 반대는 일대다

다대일 양방향

(다: 연관관계의 주인)

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

[2] 일대다 [1:N]

✔️ 일대다 단방향

Member.java

package hellojpa;

import javax.persistence.*;

@Entity // 스프링이 뜰 때 "애는 jpa를 사용하는 애구나" 알 수 있음
public class Member {

    @Id
    @GeneratedValue
    @Column(name = "MEMBER_ID")
    private Long id;

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

    public Long getId() {
        return id;
    }

    public void setId(Long id) {
        this.id = id;
    }

    public String getUsername() {
        return username;
    }

    public void setUsername(String username) {
        this.username = username;
    }
}

Team.java

package hellojpa;

import javax.persistence.*;
import java.util.ArrayList;
import java.util.List;

@Entity
public class Team {

    @Id @GeneratedValue
    @Column(name = "TEAM_ID")
    private Long id;
    private String name;

    @OneToMany
    @JoinColumn(name = "TEAM_ID")   // 일대다 단방향은 @JoinColumn이 필수
    private List<Member> members = new ArrayList<>();

    public Long getId() {
        return id;
    }

    public void setId(Long id) {
        this.id = id;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public List<Member> getMembers() {
        return members;
    }

    public void setMembers(List<Member> members) {
        this.members = members;
    }
}

JpaMain.java

package hellojpa;

import javax.persistence.*;
import java.util.List;

public class JpaMain {

    public static void main(String[] args) {
        EntityManagerFactory emf = Persistence.createEntityManagerFactory("hello");

        EntityManager em = emf.createEntityManager();

        EntityTransaction tx = em.getTransaction();
        tx.begin();

        try {
            Member member = new Member();
            member.setUsername("member1");

            em.persist(member);

            Team team = new Team();
            team.setName("teamA");
            team.getMembers().add(member);

            em.persist(team);

            tx.commit();
        } catch (Exception e) {
            tx.rollback();
        } finally {
            em.close();
        }

        emf.close();
    }
}
  • 일대다 단방향은 1:N에서 1이 연관관계의 주인
  • 테이블 1:N 관계는 항상 N쪽에 외래 키가 있음
  • 객체와 테이블 차이 때문에 반대편 테이블의 외래 키를 관리하는 특이한 구조
  • @JoinColumn을 꼭 사용하기
    이걸 넣어주지 않으면 @JoinTable이 default여서 중간에 테이블이 하나 생겨버린다. 좋은 점도 있겠지만 아무래도 테이블 하나가 더 생겨 운영하기 쉽지 않다.

일대다 단방향 매핑 단점

  • 엔티티가 관리하는 외래 키가 다른 테이블에 있다.
  • 연관관계 관리를 위해 추가로 UPDATE SQL 실행

즉, 일대다 단방향 매핑보단 다대일 양방향 매핑을 사용하도록.

✔️ 일대다 양방향

Member.java

package hellojpa;

import javax.persistence.*;

@Entity // 스프링이 뜰 때 "애는 jpa를 사용하는 애구나" 알 수 있음
public class Member {

    @Id
    @GeneratedValue
    @Column(name = "MEMBER_ID")
    private Long id;

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

    @ManyToOne
    @JoinColumn(name = "TEAM_ID", insertable = false, updatable = false)
    // 이렇게 false로 넣어주면 충돌이 발생하지 않고 읽기 전용이 됨
    private Team team;

    public Long getId() {
        return id;
    }

    public void setId(Long id) {
        this.id = id;
    }

    public String getUsername() {
        return username;
    }

    public void setUsername(String username) {
        this.username = username;
    }
}
  • 일대다 양방향 매핑이 공식적으로 존재하는 것은 아니다.
  • @JoinColumn(name = "TEAM_ID", insertable = false, updatable = false) 이런식으로 읽기 전용 필드를 사용해 양방향처럼 사용할 수 있다.

그냥 다대일 양방향을 사용하도록 하자^^.


[3] 일대일 [1:1]

주 테이블에 외래키 일대일 단방향

결국 다대일(@ManyToOne) 단방향 매핑과 유사

주 테이블에 외래키 일대일 양방향

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

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

단방향 관계는 JPA를 지원하지 않으며 양방향 관계는 지원함

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

사실 일대일 주 테이블에 외래 키 양방향과 매핑 방법은 같음

Locker.java

package hellojpa;

import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.Id;
import javax.persistence.OneToOne;

@Entity
public class Locker {

    @Id @GeneratedValue
    private Long id;

    private String name;

    @OneToOne(mappedBy = "locker")
    private Member member;
}

Member.java

package hellojpa;

import javax.persistence.*;

@Entity // 스프링이 뜰 때 "애는 jpa를 사용하는 애구나" 알 수 있음
public class Member {

    @Id
    @GeneratedValue
    @Column(name = "MEMBER_ID")
    private Long id;

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

    @ManyToOne
    @JoinColumn(name = "TEAM_ID", insertable = false, updatable = false)
    // 이렇게 false로 넣어주면 충돌이 발생하지 않고 읽기 전용이 됨
    private Team team;

    @OneToOne
    @JoinColumn(name = "LOCKER_ID")
    private Locker locker;

    public Long getId() {
        return id;
    }

    public void setId(Long id) {
        this.id = id;
    }

    public String getUsername() {
        return username;
    }

    public void setUsername(String username) {
        this.username = username;
    }
}

  • 1:1 반대도 1:1
  • 주 테이블이나 대상 테이블 중 외래 키 선택 가능 (주 테이블에 외래 키를 넣거나, 대상 테이블에 외래 키를 넣거나)
  • 외래 키에 데이터베이스 유니크(UNI) 제약조건 추가

즉,

  • 주 테이블에 외래 키
    - 주 객체가 대상 객체의 참조를 가지는 것처럼 주 테이블에 외래 키를 두고 대상 테이블을 찾음
    - 객체지향 개발자 선호
    - JPA 매핑 편리
    - 장점: 주 테이블만 조회해도 대상 테이블에 데이터가 있는지 확인 가능
    - 단점: 값이 없으면 외래 키에 Null 허용

  • 대상 테이블에 외래 키
    - 대상 테이블에 외래 키가 존재
    - 전통적인 데이터베이스 개발자 선호
    - 장점: 주. 테이블과 대상 테이블을 일대일에서 일대다 관계로 변경할 때 테이블 구조 유지
    - 단점: 프록시 기능의 한계로 지연 로딩으로 설정해도 항상 즉시 로딩


좋은 웹페이지 즐겨찾기