BOOK 3 - 자바 ORM 표준 JPA 프로그래밍(7)
고급 매핑
- 상속 관계 매핑
- 객체의 상속 관계를 데이터베이스에 어떻게 매핑하는지 다룬다.
@MappedSuperClass- 등록일, 수정일 같이 여러 entity에서 공통으로 사용하는 매핑 정보만 상속받고 싶을 때 사용한다.
- 복합 키와 식별 관계 매핑
- 데이터베이스의 식별자가 하나 이상일 때 매핑하는 방법을 다룬다.
- 데이터베이스 설계에서 이야기 하는 식별관계와 비식별 관계에 대해 다룬다.
- 조인 테이블
- 연관관계를 관리하는 연결 테이블을 두어 연관관계를 맺을 수 있다.
- 이 조인 테이블을 매핑하는 방법을 다룬다.
- 엔티티 하나에 여러 테이블 매핑하기
- 보통 엔티티 하나에 테이블 하나를 매핑하지만 엔티티 하나에 여러 테이블을 매핑하는 방법도 있다.
상속 관계 매핑
- 관계형 데이터베이스에는 상속이라는 개념이 없다.
- 대신
Super-Type Sub-Type Relationship이라는 모델링 기법이 객체의 상속 개념과 가장 유사하다.
- 대신
- ORM에서 이야기하는 상속 관계 매핑은 객체의 상속 구조와 데이터베이스의 슈퍼타입 서브타입 관계를 매핑하는 것이다.

- 슈퍼타입 서브타입 논리 모델을 실제 물리 모델인 테이블로 구현할 때는 다음과 같은 3가지 방법을 선택할 수 있다.
- 각각의 테이블로 변환 : 각각을 모두 테이블로 만들고 조회할 때 조인을 사용한다. JPA에서는 조인 전략이라고 한다.
- 통합 테이블로 변환 : 테이블을 하나만 사용해서 통합한다. JPA에서는 단일 테이블 전략이라고 한다.
- 서브타입 테이블로 변환 : 서브 타입마다 하나의 테이블을 만든다. JPA에서는 구현 클래스마다 테이블 전략이라고 한다.
조인 전략
- 엔티티 각각을 모두 테이블로 만들고 자식 테이블이 부모 테이블의 기본 키를 받아서 기본 키 + 외래 키로 사용하는 전략
- 조회할 때 조인을 자주 사용한다.
- 테이블에는 type의 개념이 없기 때문에 type을 구분하는 컬럼을 부모테이블에 추가해야 한다.
- 여기서는
DTYPE컬럼을 타입 구분 컬럼으로 사용한다.
- 여기서는
@Entity
@Inheritance(strategy=InheritanceType.JOINED)
@DisCriminatorColumn(name="DTYPE")
public abstract class Item {
@Id
@GeneratedValue
@Column(name="ITEM_ID")
private Long id;
private String name;
private int price;
...
}
@Entity
@DiscriminatorValue("A")
public class Album extends Item {
private String artist;
...
}
@Entity
@DiscriminatorValue("M")
@PrimaryKeyJoinColumn(name="MOVIE_ID")
public class Movie extends Item {
private String director;
private String actor;
...
}
@Inheritance(strategy=InheritanceType.JOINED)- 상속 매핑은 부모 클래스에
@Inheritance를 선언해줘야 한다. strategy속성으로 매핑 전략을 지정해준다.
- 상속 매핑은 부모 클래스에
@DiscriminatorColumn(name="DTYPE")- 부모 클래스에 타입 구분 컬럼을 지정할 때 사용한다.
- 이 컬럼으로 저장된 자식 테이블을 구분할 수 있다.
- 기본값이
DTYPE이므로 이 경우 생략해서@DiscriminatorColumn으로 선언해도 된다.
@DiscriminatorValue("A")- entity를 저장할 때 구분 컬럼에 입력할 값을 지정한다.
@PrimaryKeyJoinColumn(name="MOVIE_ID")- 기본적으로 자식테이블은 부모 테이블의 ID 컬럼명을 그대로 사용한다.
- 자식 테이블의 기본 키 컬럼명을 변경하고 싶을 때 사용한다.
name속성으로 재정의할 컬럼명을 지정해준다.
조인 전략의 장점과 단점
- 장점
- 테이블이 정규화된다.
- 외래 키 참조 무결성 제약조건을 활용할 수 있다.
- 저장공간을 효율적으로 사용한다.
- 단점
- 조회 시에 조인이 많이 사용되어 성능이 저하될 수 있다.
- 조회 쿼리가 복잡하다.
- 데이터를 등록할 때 INSERT SQL이 두번 실행된다.
단일 테이블 전략
- 테이블을 하나만 사용하고 구분 컬럼으로 어떤 자식 데이터가 저장되었는지 구분한다.
- 조회할 때 조인을 사용하지 않으므로 일반적으로 가장 빠르다.

- 자식 엔티티에 매핑한 컬럼은 모두
null을 허용해야 한다.- 예를 들어
Book엔티티는Movie엔티티의director나Album엔티티의artist컬럼은 사용하지 않기 때문이다.
- 예를 들어
@Entity
@Inheritance(strategy=InheritanceType.SINGLE_TABLE)
@DiscriminatorColumn(name="DTYPE")
public abstract class Item {
@Id
@GeneratedValue
@Column(name="ITEM_ID")
private Long id;
private String name;
private int price;
...
}
@Entity
@DiscriminatorValue("A")
public class Album extends Item { ... }
@Entity
@DiscriminatorValue("M")
public class Movie extends Item { ... }
@Entity
@DiscriminatorValue("B")
public class Book extends Item { ... }
InheritanceType.SINGLE_TABLE로 단일 테이블 전략을 사용함을 지정해준다.- 테이블 하나에 모든 것을 통합하므로 구분 컬럼을 필수로 사용해야 한다.
@DiscriminatorValue를 지정하지 않으면 기본으로 엔티티 이름을 사용한다.- 예를 들어
Movie,Album,Book으로 사용한다.
- 예를 들어
단일 테이블 전략의 장점과 단점
- 장점
- 조인이 필요 없으므로 일반적으로 조회 성능이 빠르다.
- 조회 쿼리가 단순하다.
- 단점
- 자식 엔티티가 매핑한 컬럼은 모두
null을 허용해야 한다. - 단일 테이블에 모든 것을 저장하므로 테이블이 커질 수 있다.
- 상황에 따라 조회 성능이 오히려 느려질 수 있다.
- 자식 엔티티가 매핑한 컬럼은 모두
구현 클래스마다 테이블 전략
- 자식 엔티티마다 테이블을 만들고 각 테이블에 필요한 컬럼이 모두 있다.
![]()
@Entity
@Inheritance(strategy=InheritanceType.TABLE_PER_CLASS)
public class abstract Item {
@Id
@GeneratedValue
@Column(name="ITEM_ID")
private Long id;
private String name;
private int price;
...
}
@Entity
public class Album extends Item { ... }
@Entity
public class Movie extends Item { ... }
@Entity
public class Book extends Item { ... }
InheritanceType.TABLE_PER_CLASS로 구현 클래스마다 테이블 전략을 사용함을 지정해준다.- 구현 클래스마다 테이블을 만들기 때문에 구분 컬럼을 사용하지 않는다.
구현 클래스마다 테이블 전략의 장점과 단점
- 장점
- 서브 타입을 구분해서 처리할 때 효과적이다.
not null제약 조건을 사용할 수 있다.
- 단점
- 여러 자식 테이블을 함께 조회할 때
UNION을 사용해야 하므로 성능이 느리다. - 자식 테이블을 통합해서 쿼리하기 어렵다.
- 여러 자식 테이블을 함께 조회할 때
- 일반적으로 이 전략은 추천하지 않는 전략이다.
@MappedSuperclass
- 부모 클래스는 테이블과 매핑하지 않고 부모 클래스를 상속받는 자식 클래스에게 매핑 정보만 제공하고 싶을 때 사용한다.
@Entity는 실제 테이블과 매핑되지만@MappedSuperclass는 실제 테이블과는 매핑되지 않는다.- 단순히 매핑 정보를 상속할 목적으로만 사용된다.

Member와Seller는 서로 관계가 없는 테이블(엔티티)이다.- 테이블은 그대로 두고 객체 모델의
id,name두 공통 속성을 부모 클래스로 모으고 객체 상속 관계로 만들 수 있다.
@MappedSuperclass
public abstract class BaseEntity {
@Id
@GeneratedValue
private Long id;
private String name;
}
@Entity
public class Member extends BaseEntity {
private String email;
...
}
@Entity
public class Seller extends BaseEntity {
private String shopName;
...
}
-
BaseEntity에는 객체들이 주로 사용하는 공통 매핑 정보를 정의한다.- 자식 엔티티들은 상속을 통해
BaseEntity의 매핑 정보를 물려받는다.
- 자식 엔티티들은 상속을 통해
-
BaseEntity는 실제 테이블과 매핑할 필요가 없고, 자식 엔티티에게 공통으로 사용되는 매핑 정보만 제공하면 된다. -
부모로부터 물려받은 매핑 정보를 재정의하려면
@AttributeOverride를 사용할 수 있다.@Entity @AttributeOverride(name="id", column=@Column(name="MEMBER_ID")) public class Member extends BaseEntity { ... }- 부모에게 상속받은
id속성의 컬럼명을MEMBER_ID로 재정의했다.
@Entity @AttributeOverrides( @AttributeOverride(name="id", column=@Column(name="MEMBER_ID")), @AttributeOverride(name="name", column=@Column(name="MEMBER_NAME")) ) public class Member extends BaseEntity { ... }- 둘 이상의 속성 매핑 정보를 재정의할 수 있다.
- 부모에게 상속받은
-
@MappedSuperclass로 지정한 클래스는 엔티티가 아니므로 JPQL에서 사용할 수 없다.- 이 클래스를 직접 생성해서 사용할 일은 거의 없으므로 추상 클래스로 만드는 것이 좋다.
-
@MappedSuperclass를 사용해서createdAt,modifiedAt같은 여러 엔티티에서 공통으로 사용하는 속성을 효과적으로 관리할 수 있다.
복합 키와 식별 관계 매핑
식별 관계 vs 비식별 관계
- 데이터베이스 테이블 사이에 관계는 외래 키가 기본 키에 포함되는지 여부에 따라 식별 관계와 비식별 관계로 구분한다.
식별 관계
- 식별 관계는 부모 테이블의 기본 키를 내려 받아서 자식 테이블의 기본 키 + 외래 키로 사용하는 관계다.
비식별 관계
-
비식별 관계는 부모 테이블의 기본 키를 받아서 자식 테이블의 외래 키로만 사용하는 관계다.
-
비식별 관계는 외래 키에
NULL을 허용하는지에 따라 필수적 비식별 관계와 선택적 비식별 관계로 나눈다.- 필수적 비식별 관계 : 외래 키에
NULL을 허용하지 않는다. 즉, 연관관계를 필수적으로 맺어야 한다. - 선택적 비식별 관계 : 외래 키에
NULL을 허용한다. 즉, 연관관계를 맺을지 말지 선택할 수 있다.
- 필수적 비식별 관계 : 외래 키에
복합 키 : 비식별 관계 매핑
- JPA에서 식별자가 두 개 이상일 때는 별도의 식별자 클래스를 만들어야 한다.
- JPA는 영속성 컨텍스트에 엔티티를 보관할 때 엔티티의 식별자를 키로 사용한다.
- 식별자를 구분하기 위해
equals와hashCode를 사용해서 동등성 비교를 한다. - 식별자 필드가 하나일 때는 보통 자바의 기본 타입을 사용하므로 문제가 없지만, 2개 이상일 때는 별도의 식별자 클래스를 만들고
equals와hashCode를 구현해야 한다.
- 식별자를 구분하기 위해
- JPA는 복합 키를 지원하기 위해
@IdClass와@EmbeddedId2가지 방법을 제공한다.
@IdClass
PARENT와CHILD테이블이 있고,PARENT테이블의 기본 키를PARENT_ID1,PARENT_ID2로 묶은 복합 키로 구성했다고 하자.CHILD테이블은PARENT테이블의 기본 키를 외래 키로 사용하는 비식별 관계이다.
@Entity
@IdClass(ParentId.class)
public class Parent {
@Id
@Column(name="PARENT_ID1")
private String id1;
@Id
@Column(name="PARENT_ID2")
private String id2;
...
}
- 각각의 기본 키 컬럼을
@Id로 매핑하고,@IdClass를 사용해ParentId클래스를 식별자 클래스로 지정했다.
public class ParentId implements Serializable {
private String id1;
private String id2;
public ParentId() {}
public ParentId(String id1, String id2) {
this.id1 = id1;
this.id2 = id2;
}
@Override
public boolean equals(Object o) { ... }
@Override
public int hashCode() { ... }
}
@IdClass를 사용할 때 식별자 클래스는 다음 조건을 만족해야 한다.- 식별자 클래스의 속성명과 엔티티에서 사용하는 식별자의 속성명이 같아야 한다.
Serializable인터페이스를 구현해야 한다.equals,hashCode를 구현해야 한다.- 기본 생성자가 있어야 한다.
- 식별자 클래스는
public이어야 한다.
저장
Parent parent = new Parent();
parent.setId1("myId1");
parent.setId2("myId2");
em.persist(parent);
- 엔티티를 저장하는 코드에서
ParentId클래스는 따로 사용되지 않는다. persist()메소드를 호출하면 영속성 컨텍스트에 엔티티를 등록하기 직전에 내부에서Parent.id1,Parent.id2값을 사용해서 식별자 클래스인ParentId인스턴스를 생성하고 영속성 컨텍스트의 키로 사용한다.
조회
ParentId parentId = new ParentId("myId1", "myId2");
Parent parent = em.find(Parent.class, parentId);
- 식별자 클래스인
ParentId인스턴스를 생성해서 엔티티를 조회하는 조건으로 사용한다.
자식 클래스
@Entity
public class Child {
@Id
private String id;
@ManyToOne
@JoinColumns({
@JoinColumn(name="PARENT_ID1", referencedColumnName="PARENT_ID1"),
@JoinColumn(name="PARENT_ID2", referencedColumnName="PARENT_ID2")
})
private Parent parent;
}
- 부모 테이블의 기본 키 컬럼이 복합 키이므로 자식 테이블의 외래 키도 복합 키다.
- 외래 키 매핑시 여러 컬럼을 매핑해야 하므로
@JoinColumns를 사용한다.- 각각의 외래 키 컬럼은
@JoinColumn으로 매핑한다.
- 각각의 외래 키 컬럼은
@EmbeddedId
@IdClass가 데이터베이스에 맞춘 방법이라면@EmbeddedId는 좀 더 객체지향적인 방법이다.
@Entity
public class Parent {
@EmbeddedId
private ParentId id;
private String name;
...
}
Parent엔티티에서 식별자 클래스를 직접 선언해@EmbeddedId어노테이션을 사용해준다.
@Embeddable
public class ParentId implements Serializable {
@Column(name="PARENT_ID1")
private String id1;
@Column(name="PARENT_ID2")
private String id2;
// equals and hashCode 구현
...
}
@EmbeddedId를 적용한 식별자 클래스는 식별자 클래스에 기본 키를 직접 매핑한다.@EmbeddedId를 적용한 식별자 클래스는 다음 조건을 만족해야 한다.@Embeddable어노테이션을 선언해야 한다.Serializable인터페이스를 구현해야 한다.equals,hashCode를 구현해야 한다.- 기본 생성자가 있어야 한다.
- 식별자 클래스는
public이어야 한다.
저장
Parent parent = new Parent();
ParentId parentId = new ParentId("myId1", "myId2");
parent.setId(parentId);
parent.setName("parentName");
em.persist(parent);
- 엔티티를 저장하는 코드에서 식별자 클래스
ParentId인스턴스를 직접 생성해서 사용한다.
조회
ParentId parentId = new ParentId("myId1", "myId2");
Parent parent = em.find(Parent.class, parentId);
- 엔티티를 조회하는 코드에서도 식별자 클래스
ParentId인스턴스를 생성해서 조회 조건으로 사용한다.
@IdClass vs @EmbeddedId
-
@EmbeddedId가@IdClass와 비교해서 더 객체지향적이고 중복도 없는 장점이 있지만 특정 상황에 JPQL이 조금 더 길어질 수 있다.em.createQuery("select p.id.id1, p.id.id2 from Parent p"); // @EmbeddedId em.createQuery("select p.id1, p.id2 from Parent p"); // @IdClass
복합 키 : 식별 관계 매핑
-
식별 관계에서 자식 테이블은 부모 테이블의 기본 키를 포함해서 복합 키를 구성해야 하므로
@IdClass나@EmbeddedId를 사용해서 식별자를 매핑해야 한다. -
아래와 같이 부모, 자식, 손자까지 기본 키를 계속 전달하는 식별 관계를 생각해보자.

@IdClass와 식별 관계
@Entity
public class Parent {
@Id
@Column(name="PARENT_ID")
private String id;
...
}
@Entity
@IdClass(ChildId.class)
public class Child {
@Id
@ManyToOne
@JoinColumn(name="PARENT_ID")
public Parent parent;
@Id
@Column(name="CHILD_ID")
private String childId;
...
}
public class ChildId implements Serializable {
private String parent;
private String childId;
// equals, hashCode
...
}
@Entity
@IdClass(GrandChildId.class)
public class GrandChild {
@Id
@ManyToOne
@JoinColumns({
@JoinColumn(name="PARENT_ID"),
@JoinColumn(name="CHILD_ID")
})
private Child child;
@Id
@Column(name="GRANDCHILD_ID")
private String id;
...
}
public class GrandChildId implements Serializable {
private ChildId child;
private String id;
// equals, hashCode
...
}
- 식별 관계는 기본 키와 외래 키를 같이 매핑해야 한다.
- 식별자 매핑인
@Id와 연관관계 매핑인@ManyToOne을 같이 사용한다.
- 식별자 매핑인
@EmbeddedId와 식별 관계
@EmbeddedId로 식별 관계를 구성할 때는@MapsId를 사용해야 한다.
@Entity
public class Parent {
@Id
@Column(name="PARENT_ID")
private String id;
private String name;
...
}
@Entity
public class Child {
@EmbeddedId
private ChildId id;
@MapsId("parentId")
@ManyToOne
@JoinColumn(name="PARENT_ID")
public Parent parent;
private String name;
...
}
@Embeddable
public class ChildId implements Serializable {
private String parentId;
@Column(name="CHILD_ID")
private String id;
// equals, hashCode
...
}
@Entity
public class GrandChild {
@EmbeddedId
private GrandChildId id;
@MapsId("childId")
@ManyToOne
@JoinColumns({
@JoinColumn(name="PARENT_ID"),
@JoinColumn(name="CHILD_ID")
})
private Child child;
private String name;
...
}
@Embeddable
public class GrandChildId implements Serializable {
private ChildId childId;
@Column(name="GRANDCHILD_ID")
private String id;
// equals, hashCode
...
}
@EmbeddedId는 식별 관계로 사용할 연관관계의 속성에@MapsId를 사용한다.@MapsId는 외래 키와 매핑한 연관관계를 기본 키에도 매핑하겠다는 의미이다.@MapsId의 속성 값은 식별자 클래스의 기본 키 필드를 지정하면 된다.
비식별 관계로 구현
CHILD테이블과GRANDCHILD테이블에서 부모 테이블로부터 받은 기본 키를 외래 키로만 사용함으로써 식별 관계를 비식별 관계로 변경할 수 있다.
@Entity
public class Parent {
@Id
@GeneratedValue
@Column(name="PARENT_ID")
private Long id;
private String name;
...
}
@Entity
public class Child {
@Id
@GeneratedValue
@Column(name="CHILD_ID")
private Long id;
private String name;
@ManyToOne
@JoinColumn(name="PARENT_ID")
private Parent parent;
...
}
@Entity
public class GrandChild {
@Id
@GeneratedValue
@Column(name="GRANDCHILD_ID")
private Long id;
private String name;
@ManyToOne
@JoinColumn(name="CHILD_ID")
private Child child;
...
}
- 식별 관계의 복합 키를 사용한 코드와 비교할 때 훨씬 매핑도 쉽고 코드도 간단하다.
- 복합 키가 없으므로 복합 키 클래스를 만들지 않아도 된다.
일대일 식별 관계

- 일대일 식별 관계는 자식 테이블의 기본 키 값으로 부모 케이블의 기본 키 값만 사용한다.
- 부모 테이블의 기본 키가 복합 키가 아니면 자식 테이블의 기본 키도 복합 키로 구성하지 않아도 된다.
@Entity
public class Board {
@Id
@GeneratedValue
@Column(name="BOARD_ID")
private Long id;
private String title;
@OneToOne(mappedBy="board")
private BoardDetail boardDetail;
...
}
@Entity
public class BoardDetail {
@Id
private Long boardId;
@MapsId
@OneToOne
@JoinColumn(name="BOARD_ID")
private Board board;
private String content;
...
}
- 식별자가 필드 하나인 경우
@MapsId를 선언하고 속성 값은 비워두면 된다.@Id를 사용해서 기본 키로 지정한BoardDetail.boardId와 매핑된다.
식별, 비식별 관계의 장단점
- 식별 관계는 부모 테이블의 기본 키를 자식 테이블로 전파하면서 자식 테이블의 기본 키 컬럼이 점점 늘어나게 된다.
- 조인할 때 SQL이 복잡해지고, 기본 키 인덱스가 불필요하게 커질 수 있다.
- 식별 관계를 사용할 때 기본 키로 비즈니스 의미가 있는 자연 키 컬럼을 조합하는 경우가 많다.
- 비즈니스 요구사항은 변할 수 있기 때문에 부모 테이블의 자연 키 컬럼들이 자식 테이블까지 전파되면 변경하기에 힘들다.
- 식별 관계는 부모 테이블의 기본 키를 자식 테이블의 기본 키로 사용하므로 테이블 구조가 유연하지 못하다.
- 식별 관계는 2개 이상의 컬럼을 묶은 복합 기본 키를 사용하기 때문에 JPA에서 복합 키 클래스를 만들어 사용해야 한다.
- 단일 기본 키를 매핑하는 것보다 많은 노력이 필요하다.
- 비식별 관계는 주로 대리 키를 사용하는데 JPA의
@GeneratedValue처럼 대리 키를 자동 생성해주는 기능을 사용하기 용이하다. - 식별 관계는 기본 키 인덱스를 활용하기 좋고, 특정 상황에 부모 테이블과의 조인 없이 하위 테이블만으로 검색을 완료할 수 있다는 장점도 있다.
- 될 수 있으면 비식별 관계를 사용하고 기본 키는
Long타입의 대리 키를 사용하는 것이 좋다.- 또한 선택적 비식별 관계보다는 필수적 비식별 관계를 사용하는 것이 좋다.
- 선택적 비식별 관계는
NULL을 허용하므로 조인할 때 outer join을 사용해야 하는 반면, 필수적 비식별 관계는NOT NULL로 항상 관계가 있다는 것을 보장하므로 inner join만 사용해도 된다.
조인 테이블
- 데이터베이스 테이블의 연관관계를 설계하는 방법은 조인 컬럼 사용 , 조인 테이블 사용 두 가지가 있다.
- 주로 테이블 간에 관계는 외래 키 컬럼을 사용해서 관리한다.
- 예를 들어 회원과 사물함의 관계를 생각해보자.
- 각각 테이블에 데이터를 등록했다가 회원이 원할 때 사물함을 선택할 수 있다고 가정하자.
- 회원이 사물함을 선택하기 전까지는 둘 사이에 관계가 없으므로
MEMBER테이블의LOCKER_ID외래 키에null값을 입력해두어야 한다.- 이렇게 외래 키에
null값을 허용하는 관계를 선택적 비식별 관계라고 한다.
- 이렇게 외래 키에
- 선택적 비식별 관계는 회원과 사물함을 조인할 때 OUTER JOIN을 사용해야 한다.
- INNER JOIN을 사용하면 사물함과 관계가 없는 회원은 조회되지 않기 때문이다.
- 회원과 사물함이 아주 가끔 관계를 맺는다면 외래 키 값이 대부분
NULL로 저장되는 단점이 있다.
- 조인 테이블을 사용해서 연관관계를 관리하면 두 테이블의 외래 키를 가지고 연관관계를 관리하게 된다.
MEMBER와LOCKER에는 연관관계를 관리하기 위한 외래 키 컬럼이 필요하지 않다.- 회원이 원할 때 사물함을 선택하면 조인 테이블인
MEMBER_LOCKER테이블에만 값을 추가하면 된다.
조인 테이블은 테이블을 하나 더 추가해야 하고, 연관관계인 두 테이블을 조인하기 위해서 조인 테이블까지 함께 조인해야 한다는 단점이 있다.
일대일 조인 테이블
- 일대일 관계를 만들려면 조인 테이블의 키 컬럼 각각에 UNIQUE 제약 조건을 걸어야 한다.
@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;
...
}
-
부모 엔티티에서
@JoinColumn대신에@JoinTable을 사용하여 조인 테이블을 지정해주었다.name: 매핑할 조인 테이블 이름joinColumns: 현재 엔티티를 참조하는 외래 키inverseJoinColumns: 반대방향 엔티티를 참조하는 외래 키
-
양방향으로 매핑하려면 다음 코드를 추가하면 된다.
public class Child { ... @OneToOne(mappedBy="child") private Parent parent; }
일대다 조인 테이블
- 일대다 관계를 만드려면 조인 테이블의 컬럼 중 다(N) 쪽 컬럼인
CHILD_ID에UNIQUE제약 조건을 걸어야 한다.
@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"),
inverseColumns=@JoinColumn(name="CHILD_ID"))
private List<Child> child = new ArrayList<Child>();
...
}
@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")
private List<Child> child = new ArrayList<Child>();
...
}
@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"),
inverseColumns=@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"),
inverseColumns=@JoinColumn(name="CHILD_ID"))
private List<Child> child = new ArrayList<Child>();
...
}
@Entity
public class Child {
@Id
@GeneratedValue
@Column(name="CHILD_ID")
private Long id;
private String name;
...
}
엔티티 하나에 여러 테이블 매핑
@SecondaryTable을 사용하여 한 엔티티에 여러 테이블을 매핑할 수 있다.
@Entity
@Table(name="BOARD")
@SecondaryTable(name="BOARD_DETAIL",
pkJoinColumns=@PrimaryKeyJoinColumn(name="BOARD_DETAIL_ID"))
public class Board {
@Id
@GeneratedValue
@Column(name="BOARD_ID")
private Long id;
private String title;
@Column(table="BOARD_DETAIL")
private String content;
...
}
@SecondaryTable을 사용해서BOARD_DETAIL테이블을 추가로 매핑했다.name: 추가로 매핑할 테이블의 이름pkJoinColumns: 추가로 매핑할 테이블의 기본 키 컬럼 속성
content필드는@Column(table="BOARD_DETAIL")를 사용해서BOARD_DETAIL테이블의 컬럼에 매핑했다.- 테이블을 지정하지 않으면 기본 테이블인
BOARD테이블에 매핑된다.
- 테이블을 지정하지 않으면 기본 테이블인
- 이 방법은 항상 두 테이블을 조회하므로 최적화하기 어려워 권장하지 않는다.