Reference. 자바 ORM 표준 JPA 프로그래밍
책 목차 및 이전 글
더보기
들어가기 전 JPA 특징, Q&A
1. JPA 소개
3. 영속성 관리
4. 엔티티 매핑
4.1 - 4.3 @Entity, @Table, 다양한 매핑
4.4 - 4.5 데이터베이스 스키마 자동 생성, DDL 생성 기능
5. 연관관계 매핑 기초
6. 다양한 연관관계 매핑
7. 고급매핑
7.3 복합 키와 식별 관계 매핑
7.3.1 식별 관계 vs 비식별 관계
식별관계
- 부모 테이블의 기본 키를 내려받아 자식 테이블의 기본 키 + 외래 키로 사용하는 관계
비식별 관계
- 부모 테이블의 기본 키를 받아서 자식 테이블의 외래 키로만 사용하는 관계
- 필수적 비식별 관계(Mandatory): 외래 키에 NULL을 허용하지 않음, 연관관계가 필수적
- 선택적 비식별 관계(Optional): 외래 키에 NULL을 허용, 연관관계가 선택적
7.3.2 복합 키: 비식별 관계 매핑
- JPA에서 식별자를 둘 이상 사용하려면 별도의 식별자 클래스가 필요
@Entity public class Hello { @Id private String id; @Id private String id2; //오류 발생 }
- JPA는 복합 키를 지원하기 위해 @IdClass와 @EmbeddedId 2가지 방법을 제공
- @IdClass는 관계형 데이터베이스에 가까운 방법
- @EmbeddedId는 객체지향에 가까운 방법
@IdClass
PARENT
테이블의 기본키가PARENT_ID1
,PARENT_ID2
의 복합키로 식별자 클래스가 필요
Parent
@Entity
@IdClass(ParentId.class)
public class Parent {
@Id
@Column(name = "PARENT_ID1")
private String id1; //ParentId.id1과 연결
@Id
@Column(name = "PARENT_ID2")
private String id2; //ParentId.id2과 연결
...
}
식별자 클래스
public class ParentId implements Serializable {
private String id1; //Parent.id1 매핑
private String id2; //Parent.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() {...}
}
식별자 클래스의 만족 조건
- 식별자 클래스의 속성명과 엔티티에 사용하는 식별자 속성명이 동일해야 함
Parent.id1
=ParentId.id1
,Parent.id2
=ParentId.id2
Serializable
인터페이스를 구현
equals
,hashCode
를 구현
- 기본 생성자가 필요
- 식별자 클래스는
public
으로 선언
실제 사용 코드
//저장
Parent parent = new Parent();
parent.setId1("myId1"); //식별자
parent.setId2("myId2"); //식별자
parent.setName("parentName");
em.persist(parent);
//조회
ParentId parentId = new ParentId("myId1", "myId2");
Parent parent = em.find(Parent.class, parentId);
Child
@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
으로 매핑
@JoinColumn
의name
과referencedColumnName
값이 같으면referencedColumnName
생략 가능
@EmbeddedId
@EmbeddedId
는 객체지향적인 방법
Parent
@Entity
public class Parent {
@EmbeddedId
private ParentId id;
...
}
ParentId
@Embeddable
public class ParentId implements Serializable {
@Column(name = "PARENT_ID1")
private String id1;
@Column(name = "PARENT_ID2")
private String id2;
//equals and hashCode 구현
...
}
@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 = new ParentId("myId1", "myId2");
Parent parent = em.find(Parent.class, parentId);
복합 키와 equals()
, hashCode()
- 복합 키는
equals()
와hashCode()
를 필수로 구현
ParentId id1 = new ParentId();
id1.setId1("myId1");
id1.setId2("myId2");
ParentId id2 = new ParentId();
id2.setId1("myId1");
id2.setId2("myId2");
id1.equals(id2) -> ?
- id1과 id2는 같은 값(동등성)을 가지지만 인스턴스(동일성)는 다름
- 위에 결과는 거짓. Object 클래스의 equals()를 기본 사용하므로 ==비교(동일성)를 사용
- 식별자 객체의 동등성(equal 비교)이 지켜지지 않으면 엔티티를 관리하는데 심각한 문제 발생
- 이런 문제를 해결하기 위해 복합 키는
equals()
와hashCode()
를 필수로 구현
@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
<참조>
- 복합 키에는 @GenerateValue를 사용 못함, 복합 키를 구성하는 여러 컬럼 중 하나에도 사용 못함
7.3.3 복합 키: 식별 관계 매핑
- 부모, 자식, 손자까지 기본 키를 전달하는 식별 관계 구조
@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;
...
}
//자식 ID
public class ChildId implements Serializable {
private String parent; //Child.parent 매핑
private String childId; //Child.childId 매핑
//equlas, 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;
...
}
//손자 ID
public class GrandChildId implements Serializable {
private ChildId child; //GrandChild.child 매핑
private String id; //GrandChild.id 매핑
//equlas, hashCode
...
}
- 식별 관계는 기본 키와 외래 키를 같이 매핑,
@Id
와@ManyToOne
을 같이 사용@Id @ManyToOne @JoinColumn(name = "PARENT_ID") public Parent parent;
@EmbeddedId와 식별 관계
- 식별 관계를 구성할 때 @MapsId를 사용
//부모
@Entity
public class Parent {
@Id @Column(name = "PARENT_ID")
private String id;
...
}
//자식
@Entity
public class Child {
@EmbeddedId
private ChildId id;
@MapsId("parentId") //ChildId.parentId 매핑
@ManyToOne
@JoinColumn(name = "PARENT_ID")
public Parent parent;
...
}
//자식 ID
@Embeddable
public class ChildId implements Serializable {
private String parentId; //@MapsId("parentId")로 매핑
@Column(name = "CHILD_ID")
private String id;
//equlas, hashCode
...
}
//손자
public class GrandChild {
@EmbeddedId
private GrandChildId id;
@MapsId("childId") //GrandChildId.childId 매핑
@ManyToOne
@JoinColumns({
@JoinColumn(name = "PARENT_ID"),
@JoinColumn(name = "CHILD_ID")
})
private Child child;
...
}
//손자 ID
@Embeddable
public class GrandChildId implements Serializable {
private ChildId childId; //@MapsId("childId")로 매핑
@Column(name = "GRANDCHILD_ID")
private String id;
//equlas, hashCode
...
}
@IdClass
와 다른 점은@Id
대신@MapsId
를 사용
@MapsId
는 외래 키와 매핑한 연관관계를 기본 키에도 매핑한다는 의미
@MapsId
의 속성 값은@EmbeddedId
를 사용한 식별자 클래스의 기본 키 필드를 지정
7.3.4 비식별 관계로 구현
- 복합 키가 없으므로 복합 키 클래스 없이 구현 가능 (매핑, 코드가 단순)
비식별 관계 매핑
//부모
@Entity
public class Parent {
@Id @GeneratedValue
@Column(name = "PARENT_ID")
private Long id;
...
}
//자식
@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;
...
}
7.3.5 일대일 식별 관계
- 자식 테이블의 기본 키 값으로 부모 테이블의 기본 키 값만 사용
일대일 식별 관계 매핑
//부모
@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 //BoardDetail.boardId 매핑
@OneToOne
@JoinColumn(name = "BOARD_ID")
private Board board;
...
}
- BoardDetail처럼 식별자가 단순히 컬럼하나면 @MapsId의 속성없이 사용 가능
- @MapsId는 @Id를 사용해서 식별자로 지정한 BoardDetail.boardId와 매핑
일대일 식별 관계 저장
public void save() {
Board board = new Board();
board.setTitle("제목");
em.persist(board);
BoardDetail boardDetail = new BoardDetail();
boardDetail.setContent("내용");
boardDetail.setBoard(board);
em.persist(boardDetail);
}
7.3.6 식별, 비식별 관계의 장단점
비식별 관계의 장점
[ 데이터 베이스 설계 관점 ]
- 식별 관계는 부모 테이블 기본 키를 자식 테이블로 전파하며 자식 테이블의 기본 키 컬럼 증가
- 2개 이상의 컬럼을 합해 복합 기본 키를 만들어야 하는 경우가 빈번
- 식별 관계는 비즈니스 의미가 있는 자연 키 컬럼을 조합 반면, 비식별 관계의 기본키는 비즈니스와 관계없는 대리 키를 사용 → 비즈니스 요구사항은 시간이 지나면 변경되므로 자연키가 전파되면 변경이 어려움
- 식별 관계는 비식별 관계보다 테이블 구조가 유연하지 못함
[ 객체 관계 매핑의 관점 ]
- 식별 관계는 2개 이상의 컬럼을 묶은 복합키를 주로 사용 → JPA에선 복합 키 클래스를 만들어야 하는 번거로움이 발생
- 비식별 관계는 대리 키를 주로 사용하므로 @GeneratedValue 등 키생성에 편리한 방법 제공
식별 관계의 장점
- 기본 키 인덱스 활용이 좋음
- 상위 테이블들의 기본 키 컬럼을 가지고 있어 특정 상황에 조인 없이 검색 가능
- 부모 아이디가 A인 모든 자식 조회
SELECT * FROM CHILD WHERE PARENT_ID = 'A'
- 부모 아이디가 A고 자식 아이디가 B인 자식 조회
SELECT * FROM CHILD WHERE PARENT_ID = 'A' AND CHILD_ID = 'B'
정리
- 비식별 관계를 사용하고 기본 키는 Long 타입의 대리 키를 사용하는 것을 추천
- 대리 키는 비즈니스와 관련이 없으므로 유연한 대처가 가능하다는 장점
- 식별자의 데이터 타입은 Long을 추천 (Integer는 20억, Long은 920경)
- 선택적인 비선별 관계(외부 조인) 보단 필수적 관계(내부 조인)를 사용하는 것을 추천
'개발서적 > 자바 ORM 표준 JPA' 카테고리의 다른 글
[자바 ORM 표준 JPA 프로그래밍] 7.5 엔티티 하나에 여러 테이블 매핑 (0) | 2021.08.18 |
---|---|
[자바 ORM 표준 JPA 프로그래밍] 7.4 조인 테이블 (0) | 2021.08.18 |
[자바 ORM 표준 JPA 프로그래밍] 7.2 @MappedSuperclass (0) | 2021.08.18 |
[자바 ORM 표준 JPA 프로그래밍] 7.1 상속 관계 매핑 (0) | 2021.08.18 |
[자바 ORM 표준 JPA 프로그래밍] 6.4 다대다 (0) | 2021.08.18 |