[JPA] 다양한 연관관계 매핑Back-End/JPA2024. 7. 24. 17:53@seungwook_TIL
Table of Contents
이 글은 인프런 김영한님의 Spring 강의를 바탕으로 개인적인 정리를 위해 작성한 글입니다.
연관관계 매핑시 고려사항 3가지
다중성
단방향, 양방향
연관관계의 주인
다중성
다대일: @ManyToOne
일대다: @OneToMany
일대일: @OneToOne
다대다: @ManyToMany
단방향, 양방향
테이블
객체
외래 키 하나로 양쪽 조인 가능
참조용 필드가 있는 쪽으로만 참조 가능
방향이라는 개념이 없다.
한쪽만 참조하면 단방향
양쪽이 서로 참조하면 양방향
연관 관계의 주인
테이블은 외래 키 하나로 두 테이블이 연관관계를 맺음
객체 양방향 관계는 A->B, B->A 처럼 참조가 두 방향
객체 양방향 관계는 참조가 두 방향이 있음. 둘중 테이블의 외래 키를 관리할 곳을 지정해야 함
연관관계의 주인: 외래 키를 관리하는 참조
주인의 반대편: 외래 키에 영향을 주지 않음, 단순 조회만 가능(Read Only)
다대일 [N : 1]
다대일은 가장 많이 사용하는 연관관계이다.
다대일의 반대는 일대다[1: N]이다.
단방향
@Entity
public class Member {
@Id
private Long id;
@Column(name = "USERNAME")
private String name; private int age;
@Column(name = "TEAM_ID")
private Long teamId;
@ManyToOne //Member 입장에서 Team과의 관계는 ManyToOne
@JoinColumn(name = "TEAM_ID")
private Team team;
//getter and setter...
}
이렇게 엔티티를 설정하면 단방향 매핑(Member -> Team)이 된다.
양방향
외래 키가 있는 쪽이 연관관계의 주인이 되도록 설정해야 한다.
양쪽을 서로 참조하도록 개발 한 것이다.
@Entity
public class Member {
@Id
private Long id;
@Column(name = "USERNAME")
private String name; private int age;
@Column(name = "TEAM_ID")
private Long teamId;
@ManyToOne //Member 입장에서 Team과의 관계는 ManyToOne
@JoinColumn(name = "TEAM_ID") //외래키 지정
private Team team;
//getter and setter...
}
@Entity
public class Team {
private String name;
@Id @GeneratedValue
private Long id;
@OneToMany(mappedBy = "team") //Team 입장에서는 Member와 OneToMany
List<Member> members = new ArrayList<Member>();
//getter and setter...
}
Member 테이블은 연관관계의 주인이므로 아무 속성 없이 @ManyToOne 애노테이션을 사용한다.
또한, 외래키를 통해 JPA가 JOIN을 하기 때문에 @JoinColumn를 통해 외래키를 지정해주어야 한다.
연관관계 주인이 아닌 Team 테이블은 MappedBy 속성을 사용한다.(@OneToMany(mappedBy = "team"))
일대다 [N : 1]
단방향
일대다 단방향은 일대다(1:N)에서 일(1)이 연관관계의 주인
테이블 일대다 관계는 항상 다(N) 쪽에 외래 키가 있음
객체와 테이블의 차이 때문에 반대편 테이블의 외래 키를 관리하는 특이한 구조
@JoinColumn을 꼭 사용해야 함. 그렇지 않으면 조인 테이블 방식을 사용함(중간에 테이블을 하나 추가함)
@Entity
public class Member {
@Id @GeneratedValue
@Column(name = "MEMBER_ID")
private Long ID;
@Column(name = "USERNAME")
private String username;
//getter and setter..
}
@Entity
public class Team {
@Id @GeneratedValue
@Column(name = "TEAM_ID")
private Long id;
private String name;
@OneToMany
@JoinColumn(name = "TEAM_ID")
private List<Member> members = new ArrayList<>();
//getter and setter...
}
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); //UPDATE 쿼리 생성
em.persist(team);
tx.commit();
} catch (Exception e) {
tx.rollback();
} finally {
em.close();
}
emf.close();
}
}
일대다 단방향 매핑의 단점
엔티티가 관리하는 외래 키가 다른 테이블에 있음
연관관계 관리를 위해 추가로 UPDATE SQL 실행
일대다 단방향 매핑보다는 다대일 양방향 매핑을 사용하는 것이 좋다.
양방향
이런 매핑은 공식적으로 존재하지 않는다.
@JoinColumn(insertable=false, updatable=false)
읽기 전용 필드를 사용해서 양방향 처럼 사용하는 방법
@Entity
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)
private Team team;
}
@Entity
public class Team {
@Id @GeneratedValue
@Column(name = "TEAM_ID")
private Long id;
private String name;
@OneToMany
@JoinColumn(name = "TEAM_ID")
private List<Member> members = new ArrayList<>();
}
양쪽 엔티티를 연관관계의 주인으로 설정하고 한쪽을 읽기 전용으로 설정하는 방식이다.
결론은 다대일 양방향을 사용해야한다.
일대일 [1 : 1]
일대일 관계는 그 반대도 일대일
주 테이블이나 대상 테이블 중에 외래 키 선택 가능
주 테이블에 외래 키
대상 테이블에 외래 키
외래 키에 데이터베이스 유니크(UNI) 제약조건 추가
단방향
다대일(@ManyToOne) 단방향 매핑과 유사
@Entity
public class Member {
@Id @GeneratedValue
@Column(name = "MEMBER_ID")
private Long ID;
@Column(name = "USERNAME")
private String username;
@OneToOne
@JoinColumn(name = "LOCKER_ID")
private Locker locker;
}
@Entity
public class Locker {
@Id @GeneratedValue
private Long id;
private String name;
}
연관관계의 주인이 아닌 테이블 즉, 대상 테이블에 외래키 단방향은 존재하지 않는다.
양방향
CASE 1
다대일 양방향 매핑 처럼 외래 키가 있는 곳이 연관관계의 주인
반대편은 mappedBy 적용
@Entity
public class Member {
@Id @GeneratedValue
@Column(name = "MEMBER_ID")
private Long ID;
@Column(name = "USERNAME")
private String username;
@OneToOne
@JoinColumn(name = "LOCKER_ID")
private Locker locker;
}
@Entity
public class Locker {
@Id @GeneratedValue
private Long id;
private String name;
@OneToOne(mappedBy = "locker")
private Member member;
}
주 객체가 대상 객체의 참조를 가지는 것 처럼
주 테이블에 외래 키를 두고 대상 테이블을 찾음
객체지향 개발자 선호
JPA 매핑 편리
장점: 주 테이블만 조회해도 대상 테이블에 데이터가 있는지 확인 가능
단점: 값이 없으면 외래 키에 null 허용
CASE 2
@Entity
public class Locker {
@Id @GeneratedValue
private Long id;
private String name;
@OneToOne(mappedBy = "locker")
private Member member;
}
@Entity
public class Locker {
@Id @GeneratedValue
private Long id;
private String name;
@OneToOne
@JoinColumn(name = "LOCKER_ID")
private Member member;
}
대상 테이블에 외래 키가 존재
전통적인 데이터베이스 개발자 선호
장점: 주 테이블과 대상 테이블을 일대일에서 일대다 관계로 변경할 때 테이블 구조 유지
단점: 프록시 기능의 한계로 지연 로딩으로 설정해도 항상 즉시 로딩됨
다대다 [N : M]
다대다 매핑은 사용해선 안된다.
따라서 중간 테이블을 두고 @OneToMany, @ManyToOne 를 활용해서 매핑해야 한다.