해당 포스팅은 인프런에서 제공하는 김영한 님의 '자바 ORM 표준 JPA 프로그래밍 - 기본편'을 수강한 후 정리한 글입니다. 유료 강의를 정리한 내용이기에 제공되는 예제나 몇몇 내용들은 제외하였고, 정리한 내용을 바탕으로 글 작성자인 저의 언어로 다시 작성한 글이기에 서술이 부족하거나 잘못된 내용이 있을 수 있습니다. 그렇기에 해당 글은 개념에 대한 참고 정도만 해주시고, 강의를 통해 학습하시기를 추천합니다.
데이터베이스에서 테이블간의 연관관계를 설정하는 방법은 크게 조인 컬럼 즉 외래 키를 사용하는 방법과 조인 테이블을 사용하는 방법이 존재한다. 보통 조인 컬럼을 사용해서 연관관계를 맺게 되지만, 이 경우 조인 컬럼에 null
을 허용하는 선택적 비식별 관계일 때 모든 데이터를 조회하기 위해서는 외부 조인을 사용해야 한다는 단점이 존재한다.
이렇듯 관계를 가끔 맺게 되는 테이블들의 관계는 조인 테이블을 사용해서 풀어낼 수 있다.
두 테이블의 연관관계를 외래 키를 통해 해당 테이블들이 관리하는 것이 아닌 별도의 테이블을 사용해서 관리하는 방법을 말한다.
위의 관계도를 통해 알 수 있듯 조인 테이블 방식은 연관관계를 관리하는 조인 테이블(EmployeeProject)을 추가하여 두 테이블의 기본 키를 외래 키로 관리한다. 따라서 관계를 맺는 두 테이블은 관계를 맺기 위한 외래 키 컬럼이 존재하지 않으며 필요시에만 조인 테이블에 값을 추가하면 된다.
그러나 테이블을 추가해야 하기 때문에 관리해야 하는 테이블이 늘어나고, 두 테이블을 조인하기 위해서는 조인 테이블 까지 같이 조인해야 한다는 단점도 존재한다. 그렇기 때문에 기본은 조인 컬럼을 사용하고 필요 시에만 조인 테이블을 사용하는 것이 좋다.
조인 테이블을 통해 일대일 관계를 만들기 위해서는 각각의 외래 키 컬럼에 유니크 제약조건을 걸어야 한다.
// 부모
@Entity
public class Parent {
@Id @GeneratedValue
@Column(name = "PARENT_ID")
private Long id;
private String name;
@OneToOne
@JoinTable(name = "PARENT_CHILD",
joinColumns = @JoinColumn(name = "PARENT_ID),
inverseJoinColumns = @JoinColumn(name = "CHILD_ID"))
private Child child;
...
}
// 자식
@Entity
public class Child {
@Id @GeneratedValue
@Column(name = "CHILD_ID")
private Long id;
private String name;
...
}
부모 엔티티인 Parent
를 보면 @JoinColoumn
이 아닌 @JoinTable
을 사용한 것을 볼 수 있다. @JoinTable
의 속성은 다음과 같다.
name
: 매핑할 조인 테이블의 이름joinColumns
: 현재 엔티티를 참조하는 외래 키inverseJoinColumns
: 반대방향 엔티티를 참조하는 외래 키일대다 관계를 조인 테이블로 매핑하기 위해서는 조인 테이블의 컬럼 중 다 쪽에 유니크 제약조건을 걸어야 한다.
// 부모
@Entity
public class Parent {
@Id @GeneratedValue
@Column(name = "PARENT_ID")
private Long id;
private String name;
@OneToMany
@JoinTable(name = "PARENT_CHILD",
joinColumns = @JoinColumn(name = "PARENT_ID),
inverseJoinColumns = @JoinColumn(name = "CHILD_ID"))
private List<Child> childs = new ArrayList<>();
...
}
// 자식
@Entity
public class Child {
@Id @GeneratedValue
@Column(name = "CHILD_ID")
private Long id;
private String name;
...
}
다대일은 일대다에서 방향만 반대으로 매핑해주면 된다. 이를 양방향 관계로 매핑할 경우 아래와 같다.
// 부모
@Entity
public class Parent {
@Id @GeneratedValue
@Column(name = "PARENT_ID")
private Long id;
private String name;
@OneToMany(mappedBy = "parent")
@JoinTable(name = "PARENT_CHILD",
joinColumns = @JoinColumn(name = "PARENT_ID),
inverseJoinColumns = @JoinColumn(name = "CHILD_ID"))
private List<Child> childs = new ArrayList<>();
...
}
// 자식
@Entity
public class Child {
@Id @GeneratedValue
@Column(name = "CHILD_ID")
private Long id;
private String name;
@ManyToOne(optional = false)
@JoinTable(name = "PARENT_CHILD",
joinColumns = @JoinColumn(name = "CHILD_ID),
inverseJoinColumns = @JoinColumn(name = "PARENT_ID"))
private Parent parent;
...
}
다대다 관계를 만들기 위해서는 조인 테이블의 두 컬럼을 합쳐서 하나의 복합 유니크 제약조건으로 만들어야 한다.
// 부모
@Entity
public class Parent {
@Id @GeneratedValue
@Column(name = "PARENT_ID")
private Long id;
private String name;
@ManyToMany
@JoinTable(name = "PARENT_CHILD",
joinColumns = @JoinColumn(name = "PARENT_ID),
inverseJoinColumns = @JoinColumn(name = "CHILD_ID"))
private List<Child> childs = new ArrayList<>();
...
}
// 자식
@Entity
public class Child {
@Id @GeneratedValue
@Column(name = "CHILD_ID")
private Long id;
private String name;
...
}
조인 테이블에 컬럼을 추가하게 된다면 @JoinTable
전략을 사용할 수 없다. 대신 새로운 엔티티를 만들어서 조인 테이블과 매핑해야 한다.
@SecondaryTable
을 사용해 한 엔티티에 여러 테이블을 매핑할 수 있다.
@Entity
@Table(name = "FIRST_TABLE")
@SecondaryTable(name = "SECOND_TALBE",
pkJoinColumns = @PrimaryKeyJoinColumn(name = "SECOND_TABLE_ID"))
public class Table {
@Id @GeneratedValue
@Column(name = "FIRST_TABLE_ID)
private Long id;
private String firstContent;
@Column(table = "SECOND_TABLE")
private String secondContent;
...
}
@SecondaryTable
의 속성은 다음과 같다.
name
: 매핑할 다른 테이블의 이름pkJoinColumns
: 매핑할 다른 테이블의 기본 키 컬럼 속성 @SecondaryTables
를 사용하여 더 많은 테이블을 매핑할 수도 있다. 그러나 조회시 항상 두 테이블을 같이 조회하게 되므로 최적화가 어렵다. 따라서 각각의 엔티티를 만들어서 매핑하는 것이 권장된다.