본문 바로가기
개발/Java

[JPA] Jakarta Spec(JSR 338) - (2)기본 키(PK) 및 Entity ID

by Mingvel 2023. 8. 23.

[이전글]

[JPA] Jakarta Spec(JSR 338) - (1)Entity, Field, Access Type과 Jakarta Spec을 보는 이유

 

 

Jakarta EE 로고

 

기본 키와 복합 키

  • 모든 Entity 반드시 기본키를 가져야 한다
  • 기본 키는 Entity 계층 구조에 정확히 한 번만 정의되어야 한다
  • 기본 키는 Entity 계층 구조의 Root인 Entity에 정의되어야 한다
  • 기본 키는 Entity 계층 구조에 있는 모든 Entity 클래스의 superclass에 정의되어야 한다
  • 기본 키는 Entity 클래스의 한개 혹은 한 개 이상의 필드를 조합하여 정의할 수 있다
  • 복합 키가 아닌 기본 키는 단일 필드에 정의가 가능하고, @Id 어노테이션을 사용하거나, XML에 정의해야 한다
  • 복합 키를 클래스로 정의할 수 있다
  • 복합 키는 여러 column 으로 구성할 수 있다
  • 복합 키는 @EmbeddedId 혹은 @IdClass 어노테이션으로 명시할 수 있다
  • 단일 기본 키 혹은 복합 키의 구성 항목들은 다음과 같은 데이터 타입을 가져야 한다
    • primitive type
    • primitive wrapper type
    • String
    • Date
    • BigDecimal
    • BigInteger
  • Entity의 ID의 데이터 타입은 반드시 DB 데이터 타입과 상응하는 값이어야 한다
    • ex) 기본 키가 java.util.Date 타입일 경우, 컬럼의 타입은 DATE여야 한다
  • 복합 키의 Rule
    • 복합 키 클래스는 반드시 public 이어야 하며, public인 인자 없는 생성자를 반드시 가져야 한다
    • 복합 키 클래스의 접근 유형(Access Type)은 별도 처리가 없는 한 기본적으로 해당 복합키를 사용하는 엔티티에 종속된다
    • 속성 기반 액세스(property-based-access)를 사용하는 경우 기본 키 클래스의 속성은 public 또는 protected로 선언해야 한다
    • 기본 키는 반드시 serializable 해야 한다
    • 기본 키 클래스는 반드시 equals , hashCode 메서드를 정의해야 한다
    • 복합 키는 반드시 embeddable 가능한 클래스로 표현되고 매핑되어야 하거나, ID 클래스로 표현되고 엔티티 클래스의 여러 속성에 매핑되어야 한다
  • 기본 키의 값은 영속성 컨텍스트 내에서 Entity 인스턴스를 고유하게 식별하는 데 사용된다
  • 어플리케이션에서 기본 키 값을 변경하려는 시도는 하지 않아야 한다

Primary Keys Corresponding to Derived Identities

  • 다른 상위 Entity와 다대일 또는 일대일 관계의 소유자이고, 상위로 관계를 매핑하는 경우 Entity의 기본 키는 다른 Entity의 기본 키에서 파생될 수 있다
  • 다대일, 일대일 관계 매핑의 경우, 관계를 포함하는 Entity의 기본 키는 참조된 Entity에서 파생되므로 반드시 대상 Entity를 포함해야 한다
  • 종속 Entity 클래스에 부모 Entity의 기본 키에 해당하는 속성 외에 기본 키 속성이 있거나, 부모에 복합 키가 있는 경우, EmbeddedId 또는 Id 클래스를 사용하여 종속 Entity의 기본 키를 지정해야 한다
  • ID 클래스의 속성 이름과 종속 Entity 클래스의 Id 속성은 다음과 같아야 한다
    • Entity 클래스의 Id 속성과 ID 클래스의 해당 속성은 이름이 같아야 한다
    • Entity 클래스의 Id 속성이 basic type일 경우, 대응하는 Id 속성도 같은 type이어야 한다
    • Entity의 Id 속성이 부모 엔티티와 다대일 혹은 일대일 관계인 겨우, Id 클래스의 해당 특성은 부모 Entity의 Id 클래스 또는 내장된 id 또는 부모 Entity의 Id 속성 유형과 동일한 Java 유형이어야 한다
  • 종속 Entity가 단일 기본 키를 가질 경우, 종속 Entity에 정의된 Id 클래스는 부모의 기본 키 클래스와 동일해야 한다
  • @Id 어노테이션은 상위 엔티티와의 관계에 적용된다
  • 종속 Entity에서 기본 키를 Embedded Id 클래스로 사용할 경우, 이는 부모 Entity에도 동일하게 적용되어야 하며, @MapsId 어노테이션을 명시해주어야 한다
  • @MapsId 어노테이션을 사용할 땐, `value` 값으로 대응하는 embedded Id 이름을 명시해줘야 한다
  • @MapsId 어노테이션을 사용할 때, 종속 Entity와 대응하는 부모 Entity의 타입이 모두 Java Type일 경우, `value` 값을 별도로 명시해 주지 않아도 된다
  • 상위 Entity의 ID에서 파생된 기본 키 속성은 해당 관계 속성에 의해 매핑된다
  • Default Mapping에서 정의하는 매핑정보가 적용되지 않는 경우엔 @JoinColumn, @JoinColumns 어노테이션으로 관계 속성을 정의할 수 있다
  • 종속 Entity가 기본 키를 나타내기 위해 Embedded ID를 사용하는 경우, @AttributeOverride 어노테이션을 사용하여 파생 ID를 매핑하는 관계 속성과 일치하지 않는 Embedded ID 속성의 기본 매핑을 재정의할 수 있다
  • 관계에 해당하는 Embedded ID 속성은 provider에 의해 `Read Only`로 처리 된다
    • Application 측의 모든 Update가 DB로 전파되지 않는다
  • 종속 Entity가 Id 클래스를 사용하는 경우, @Column 어노테이션으로 관계 속성이 아닌 Id 속성의 기본 매핑을 재정의할 수 있다

 

관계 매핑이 정의된 상황에서 ID 클래스 사용 예시

@Entity
public class Employee {
    @Id long empId;
    String empName;
}

@Entity
@IdClass(DependentId.class)
public class Dependent {
    @Id String name;

    // id attribute mapped by join column default
    @Id @ManyToOne
    Employee emp;
}

public class DependentId {
    String name; // Dependent의 name 과 매핑
    long emp; // Employee의 empId 와 매핑
}

 

관계 매핑이 정의된 상황에서 @EmbeddedId 사용 예시

@Embeddable
public class DependentId {
    String name;
    long empPK; // Employee의 PK 값과 매핑
}

@Entity
public class Dependent {
    @EmbeddedId DependentId id;

    // id attribute mapped by join column default
    @MapsId("empPK") // Embedded 된 DependentId 의 empPK 값과 매핑
    @ManyToOne
    Employee emp;
}

 

부모 Entity에서 @IdClass를 사용하고, 종속 Entity에서 @IDClass 활용 예시

@Entity
@IdClass(EmployeeId.class)
public class Employee {  // 부모 Entity
    @Id String firstName
    @Id String lastName
}

@Entity
@IdClass(DependentId.class)
public class Dependent {  // 종속 Entity
    @Id
    String name;

    @Id
    @JoinColumns({
        @JoinColumn(name="FK1", referencedColumnName="firstName"),
        @JoinColumn(name="FK2", referencedColumnName="lastName")
    })
    @ManyToOne
    Employee emp;
}

public class EmployeeId {
    String firstName; //Employee의 firstName과 매핑
    String lastName;  //Employee의 lastName과 매핑
}

public class DependentId {
    String name; // Dependent의 name과 매핑
    EmployeeId emp; // Dependent의 emp(firstName(FK1), lastName(FK2)) 와 매핑
}

 

부모 Entity에서 @IdClass를 사용하고, 종속 Entity에서 @EmbeddedId 활용 예시

@Embeddable
public class DependentId {
    String name;
    EmployeeId empPK; //Dependent의 MapsId 어노테이션에 명시
}

@Entity
public class Dependent {
    @EmbeddedId
    DependentId id;

    @MapsId("empPK")
    @JoinColumns({
        @JoinColumn(name="FK1", referencedColumnName="firstName"),
        @JoinColumn(name="FK2", referencedColumnName="lastName")
    })
    @ManyToOne
    Employee emp;
}

 

 

부모 Entity에서 @EmbeddedId를 사용하고, 종속 Entity에서 @IdClass 활용 예시

@Entity
public class Employee { // 부모 Entity (firstName, lastName)
    @EmbeddedId
    EmployeeId empId;
}

@Entity
@IdClass(DependentId.class)
public class Dependent { // 종속 Entity
    @Id
    @Column(name="dep_name") // default column name is overridden
    String name;

    @Id
    @JoinColumns({
        @JoinColumn(name="FK1", referencedColumnName="firstName"),
        @JoinColumn(name="FK2", referencedColumnName="lastName")
    })
    @ManyToOne Employee
    emp;
}

@Embeddable
public class EmployeeId {
    String firstName; 
    String lastName;
}

public class DependentId {
    String name; // Dependent 의 name과 매치
    EmployeeId emp; // Dependent에서 @Id 어노테이션이 붙은 Employee의 Id와 매치(firstName(FK1), lastName(FK2))
}

 

부모 Entity에서 @EmbeddedId를 사용하고, 종속 Entity 에서 @EmbeddedId 활용 예시

@Embeddable
public class DependentId {
    String name;
    EmployeeId empPK; // Dependent에서 MapsId로 매핑을 명시 
}

@Entity
public class Dependent {
    // default column name for "name" attribute is overridden
    @AttributeOverride(name="name", column=@Column(name="dep_name"))
    @EmbeddedId DependentId id;

    @MapsId("empPK") //DependentId의 empPK 필드를 매핑
    @JoinColumns({
        @JoinColumn(name="FK1", referencedColumnName="firstName"),
        @JoinColumn(name="FK2", referencedColumnName="lastName")
    })
    @ManyToOne
    Employee emp;
}

 

단일 기본 키를 가지는 부모 Entity 기본 키의 데이터 타입과

자식 Entity 기본 키가 부모 Entity의 FK 한 개이고 데이터 타입이 일치하는 경우 매핑 활용 예시

@Entity
public class Person { // 부모 Entity
    @Id
    String ssn;
}

@Entity
public class MedicalHistory { // 종속 Entity (문자열 타입의 PK 값을 가진다는 전제)
    // default join column name is overridden
    @Id
    @OneToOne
    @JoinColumn(name="FK") // Person의 ssn을 FK 로 매핑
    Person patient;
}

 

단일 기본 키를 가지는 부모 Entity 기본 키의 데이터 타입과 종속 Entity에서 @MapsId를 사용해 부모 Entity의 기본 키를 관계 속성에 매핑시키고자 할 경우 활용 예시

@Entity
public class Person { // 단일 기본키를 가지는 부모 Entity
    @Id
    String ssn;
}

@Entity
public class MedicalHistory { // 종속 Entity
    @Id
    String id;

    @MapsId	// 부모 Entity의 PK 값을 해당 관계 속성에 매핑
    @JoinColumn(name="FK")
    @OneToOne
    Person patient;
}

 

부모 Entity에서 @IdClass를 사용하고, 종속 Entity에서 매핑을 위해 동일한 @IdClass를 사용하는 경우 예시

@Entity
@IdClass(PersonId.class)
public class Person { // 부모 Entity
    @Id
    String firstName;

    @Id
    String lastName;
}

@Entity
@IdClass(PersonId.class)
public class MedicalHistory { // 종속 Entity
    @Id
    @JoinColumns({
        @JoinColumn(name="FK1", referencedColumnName="firstName"),
        @JoinColumn(name="FK2", referencedColumnName="lastName")
    })
    @OneToOne
    Person patient;
}

public class PersonId { // 해당 예시의 종속 Entity에서 FK1, FK2 로 매핑
    String firstName;
    String lastName;
}

 

종속 Entity에서 EmbeddedId 및 MapsId 어노테이션을 사용하는 경우 예시

@Entity
public class MedicalHistory {

    @EmbeddedId
    PersonId id;

    @MapsId
    @JoinColumns({
        @JoinColumn(name="FK1", referencedColumnName="firstName"),
        @JoinColumn(name="FK2", referencedColumnName="lastName")
    })
    @OneToOne Person patient;
}

@Embaddable
public class PersonId {
    String firstName;
    String lastName;
}

 

부모 Entity에서 @EmbeddedId를 사용하고, 종속 Entity에서 부모 Entity와 동일한 기본 키를 사용하는 경우 예시

@Entity
public class Person { // 부모 Entity
    @EmbeddedId PersonId id;
}

@Entity
@IdClass(PersonId.class)
public class MedicalHistory { // 종속 Entity
    @Id
    @OneToOne
    @JoinColumns({
        @JoinColumn(name="FK1", referencedColumnName="firstName"),
        @JoinColumn(name="FK2", referencedColumnName="lastName")
    })
    Person patient;
}

@Embeddable
public class PersonId {
    String firstName;
    String lastName;
}

 

부모 Entity에서 @EmbeddedId를 사용하고, 종속 Entity에서 @EmbeddedId를 사용하는 경우 예시

@Entity
public class MedicalHistory { // 종속 Entity
    // All attributes are mapped by the relationship
    // AttributeOverride is not allowed
    @EmbeddedId PersonId id;

    @MapsId
    @JoinColumns({
        @JoinColumn(name="FK1", referencedColumnName="firstName"),
        @JoinColumn(name="FK2", referencedColumnName="lastName")
    })
    @OneToOne
    Person patient;
}

 

 

 

 

 

반응형

댓글