식별 관계 - 식별 관계는 부모 테이블의 기본 키를 내려받아서 자식 테이블의 기본 키 + 외래 키로 사용하는 관계이다
비식별 관계 - 부모 테이블의 기본 키를 받아서 자식 테이블에서 단지 외래 키로만 사용하는 관계
필수적 비식별 관계 - 외래 키에 null을 허용하지 않는 경우이기 때문에 연관관계를 필수적으로 맺어야함
선택적 비식별 관계 - 외래 키에 null을 허용한 경우이기 때문에 연관관계를 맺든 안맺는 선택
복합 키에서 비식별 관계를 매핑하는 방법
단순하게 기본 키가 하나면 @Id를 사용해서 적용해주면 되지만, 만약에 2개 이상으로 기본 키가 이루어진 복합 기본 키인 경우에는 @Id를 2개 써야하나? 근데 당연히 에러가 나오겠지여
일단 JPA는 영속성 컨텍스트에 엔티티를 보관할 때 엔티티의 식별자를 키로 사용한다. 그리고 그 식별자를 구분하기 위해서 사용하는 것이 equals, hashCode 이렇게 2가지를 사용해서 동등성을 비교한다. 자 그럼 만약에 식별자 필드가 2개 이상이면 어떻게 해야하나 → 당연히 equals, hashCode를 새로 구현을 해줘야한다.
그럼 JPA에서 지원해주는 복합 키 구현 방식으로는 → @IdClass, @EmbeddedId 이렇게 2가지의 방법을 제공해주고 있다.
@IdClass
이건 그래도 관계형 데이터베이스에 가까운 방법
package com.example.jpa_project.ch07Example;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.Id;
import javax.persistence.IdClass;
import java.io.Serializable;
import java.util.Objects;
public class MultiId_IdClass {
@Entity
@IdClass(ParentId.class)
public class Parent{
@Id
@Column(name = "PARNET_ID")
private String id;
@Id
@Column(name = "PARENT_ID2")
private String id2;
}
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) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
ParentId parentId = (ParentId) o;
return Objects.equals(id1, parentId.id1) && Objects.equals(id2, parentId.id2);
}
@Override
public int hashCode() {
return Objects.hash(id1, id2);
}
}
}
@Id를 모든 복합 기본 키에 붙혀주고 대신 엔티티 클래스에 @IdClass를 붙혀주되,
식별자 클래스(ParentId)를 만들어주어야 한다. 이 식별자 클래스에는 충족해야 하는 조건들이 존재함
식별자 클래스의 속성명과 엔티티에서 사용하는 식별자의 속성명이 같아야 한다.
Serializable 인터페이스를 구현해야 한다.
equals, hashCode를 구현해야 한다.
기본 생성자가 있어야 하고
클래스의 접근 타입은 public 이어야 한다.
이렇게 만들고 자식 클래스
package com.example.jpa_project.ch07Example;
import javax.persistence.*;
public class MultiId_IdClass_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 MultiId_IdClass_Parent.Parent parent;
}
}
부모 테이블의 기본 키가 복합 키이기 때문에 자식 테이블의 외래 키도 복합키로 가져와야 하고 이렇게 가져오는 방식은 @JoinColumns으로 매핑해주고 내부에는 @JoinColumn으로 구현해준다. 만약 @JoinColumn의 name속성과 referencedColumnName이 같다면 뒤의 referencedColumnName은 생략할 수 있다.
@EmbeddedId
이건 객체지향에 가까운 방법
package com.example.jpa_project.ch07Example;
import javax.persistence.Column;
import javax.persistence.Embeddable;
import javax.persistence.EmbeddedId;
import javax.persistence.Entity;
import java.io.Serializable;
import java.util.Objects;
public class MultiId_EmbeddedClass_Parent {
@Entity
public class Parent{
@EmbeddedId
private ParentId id;
private String name;
}
@Embeddable
public class ParentId implements Serializable {
@Column(name = "PARENT_ID1")
private String id1;
@Column(name = "PARENT_ID2")
private String id2;
public ParentId() {
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
ParentId parentId = (ParentId) o;
return Objects.equals(id1, parentId.id1) && Objects.equals(id2, parentId.id2);
}
@Override
public int hashCode() {
return Objects.hash(id1, id2);
}
}
}
진짜 단순하게 Parent에서 그냥 식별자 클래스로 적용해주고 @EmbeddedId을 붙혀준다.
그리고 @Embeddable으로 해당 아이디를 받아서 구현해줘야 한다. 구현할 때 조건
구현해야 하는 클래스에 @Embeddable을 붙히자
Serializable 인터페이스를 구현
equals, hashCode를 구현
기본 생성자 필요
식별자 클래스는 public이여야 함
equals(), hashCode()
영속성 컨텍스트는 엔티티의 식별자로 키를 사용해서 엔티티를 관리한다. 그리고 해당 식별자를 비교할 때 equals(), hashCode()를 사용해서 비교하게 된다. 만약 이것을 정의하지 않게 되면 구별이 불분명해져 엔티티에 대한 접근에 문제가 생기게 된다.
그리고 식별자 클래스의 equals()와 hashCode()는 모든 필드를 사용해서 구현한다.
여기서 만약 하나의 자식이 더 추가된다면...?
@IdClass
package com.example.jpa_project.ch07Example;
import javax.persistence.*;
import java.io.Serializable;
import java.util.Objects;
public class MultiId_IdClass_Family {
@Entity
public class Parent{
@Id
@Column(name = "PARENT_ID")
private String id;
private String name;
}
@Entity
@IdClass(ChildId.class)
public class Child{
@Id
@ManyToOne
@JoinColumn(name = "PARENT_ID")
public Parent parent;
@Id
@Column(name = "CHILD_ID")
public String childId;
private String name;
}
public class ChildId implements Serializable{
private String parent;
private String childId;
public ChildId() {
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
ChildId childId1 = (ChildId) o;
return Objects.equals(parent, childId1.parent) && Objects.equals(childId, childId1.childId);
}
@Override
public int hashCode() {
return Objects.hash(parent, childId);
}
}
@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;
public GrandChildId() {
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
GrandChildId that = (GrandChildId) o;
return Objects.equals(child, that.child) && Objects.equals(id, that.id);
}
@Override
public int hashCode() {
return Objects.hash(child, id);
}
}
}
자식의 자식같은 경우에도 구현은 같지만 항상 자신보다 바로 하나 위의 부모의 키를 가져와서 구현해야 한다는 점? 이정도? 구현자체는 비슷하다고 본다.
@EmbeddedId의 구성으로 확인
구조는 같지만 @MapsId를 사용하는 점이 다른점이라고 보인다.
package com.example.jpa_project.ch07Example;
import javax.persistence.*;
import java.io.Serializable;
import java.util.Objects;
public class MultiId_EmbeddedClass_Family {
@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;
public ChildId() {
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
ChildId childId = (ChildId) o;
return Objects.equals(parentId, childId.parentId) && Objects.equals(id, childId.id);
}
@Override
public int hashCode() {
return Objects.hash(parentId, id);
}
}
@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;
public GrandChildId() {
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
GrandChildId that = (GrandChildId) o;
return Objects.equals(childId, that.childId) && Objects.equals(id, that.id);
}
@Override
public int hashCode() {
return Objects.hash(childId, id);
}
}
}
기존과의 차이는 @MapsId를 @Id대신에 넣어줬다는 것이다. @MapsId는 외래키와 매핑한 연관관계를 기본 키에다가도 매핑하겠다는 의미이다.
비식별 관계로 구현하는건 더더더더더욱 단순한다. 단순하게 Serializable을 가지고 있는 복합 키 클래스를 만들지 않아도 매핑이 가능하다는 점..!
그래서 식별, 비식별 관계의 장단점
결과적으로는 비식별 관계를 많이 사용한다네요 왜와이
식별관계는 자식 테이블로 갈 수록 기본 키 컬럼이 점점 늘어나면 사고가 되어간다.
식별 관계는 2개 이상의 컬럼을 합해서 복합 기본 키를 만들어야 하는 경우가 있다.
식별 관계를 맺어두면 나중에 변경사항이 있을 때 힘들다는점?
식별 관계는 자식 테이블에서 부모 테이블의 기본 키를 가져와서 사용하기 때문에 테이블 구조가 유연하지 않다.
이렇게나 단점이 많으면 그럴만도하다
그럼 왜 비식별 관계를 더 많이 사용하냐
그냥 식별 함수를 만들지 않아도 괜찮기 때문에 무조건 단순하니까
비식별 관계의 기본 키는 주로 대리 키를 사용하게 되는데, 여기서는 JPA에서는 @GeneratedValue를 사용해서 대리 키를 많이 사용하기 때문에 편리하게 사용할 수 있다. - 대부분 Long 타입의 대리 키를 사용한다.