[JPA] JPA의 엔티티 퍼시스턴스 매핑

JPA의 매핑 어노테이션

JPA가 제공하는 자바 퍼시스턴스 어노테이션(java persistence annotation)을 사용한 객체 관계 매핑은 크게 두 가지로 나뉜다.

  • 논리적 매핑: 엔티티 클래스들 간 연관 관계 설정과 관련된 어노테이션
    • 다중성(카디널리티) 설정: @ManyToOne, @OneToMany, @OneToOne, @ManyToMany
    • 외래키 매핑 (논리적 외래키 제약 조건 설정): @JoinColumn
  • 물리적 매핑: 테이블과 엔티티 간 매핑, 테이블 컬럼과 엔티티 프로퍼티(필드) 간 매핑 어노테이션, 데이터베이스 스키마 자동 생성(DDL 생성) 어노테이션
    • 테이블 매핑 및 생성: @Table
    • 기본키 매핑 및 생성: @Id
    • 컬럼 매핑 및 생성: @Column
    • 인덱스 생성: @Index


엔티티 식별자 매핑 및 생성 전략

@Id 어노테이션은 엔티티의 특정 프로퍼티를 엔티티의 식별자로 정의하기 위해 사용한다. 식별자 프로퍼티는 애플리케이션 자체에 의해 설정되거나 하이버네이트에 의해 생성될 수 있다. @GeneratedValue 어노테이션은 이 식별자 생성 전략을 정의한다.

JPA에는 다섯 가지 유형의 식별자 생성 전략이 있다.

  • AUTO: 기본 데이터베이스 따른 ID 컬럼, 시퀀스 또는 테이블
  • TABLE: ID 갖는 테이블
  • IDENTITY: ID 컬럼
  • SEQUENCE: 시퀀스
  • ID 복사: 다른 엔티티에서 복사된 ID

@GeneratedValue 어노테이션을 설정하지 않을 경우 @GeneratedValue(strategy = GenerationType.AUTO) 생성 전략이 사용된다. 이 경우 애플리케이션에서 식별자 값을 설정해야 한다. @GeneratedValue(strategy = GenerationType.AUTO) 생성 전략을 문자열 타입과 함께 사용할 수는 없으며 문자열을 식별자 값으로 사용하려면 수동으로 할당해야 한다.


테이블 외래키 제약 조건과 JPA 엔티티 연관 관계

테이블 간 연관 관계 설정과 엔티티 간 연관 관계 설정 유형은 다음과 같다.

  1. 테이블 간 외래키를 설정하지 않은 경우: 테이블 간 연관 관계 없음
    1. 엔티티 객체 간 참조하지 않고 논리적 매핑 어노테이션을 사용하지 않는 경우: 테이블 간 연관 관계와 엔티티 간 연관 관계 모두 없음
    2. 엔티티 객체 간 참조하고 논리적 매핑 어노테이션을 사용하는 경우: 테이블 간 연관 관계는 없지만 엔티티 간 연관 관계 존재
  2. 테이블 간 외래키를 설정하는 경우: 테이블 간 양방향 연관 관계 존재
    1. 엔티티 객체 간 참조하지 않고 논리적 매핑 어노테이션을 사용하지 않는 경우: 테이블 간 연관 관계는 있지만 엔티티 간 연관 관계가 없음
    2. 엔티티 객체 간 참조하고 논리적 매핑 어노테이션을 사용하는 경우: 테이블 간 연관 관계와 엔티티 간 연관 관계 모두 존재


데이터베이스 상에서 테이블 간 외래키 제약 조건을 설정하는 것을 물리적 외래키 제약 조건 설정, JPA 어노테이션을 이용하여 애플리케이션 상에서 객체(엔티티 객체) 간 외래키 제약 조건을 설정하는 것을 논리적 외래키 제약 조건 설정이라고도 한다.

엔티티 객체 간 연관 관계를 설정하는 것은 연관 관계의 방향과 다중성을 정하고 외래키를 매핑하는 것이다. 이를 위한 어노테이션은 다음과 같다.

  1. 방향 설정
  2. 다중성(카디널리티) 설정
  3. 외래키 매핑 (논리적 외래키 제약 조건 설정)


한 테이블이 다른 테이블의 기본키에 해당하는 값을 컬럼에 가지고 있도록 외래키 제약 조건을 설정한 경우 두 테이블은 양방향 연관 관계를 갖는다. 이 경우 두 테이블에 해당하는 두 엔티티의 연관 관계는 객체의 참조(한쪽 또는 모두) 여부, 다중성 설정에 따라 달라지게 된다.

  1. 객체 참조가 없을 경우 경우 연관 관계는 설정되지 않는다.
  2. 객체 참조가 있고 다중성을 설정하는 경우 연관 관계가 설정된다.


데이터베이스 상의 명시적인 물리적 외래키 제약 조건 설정이 없는 두 테이블에 대해서도 해당 엔티티 간의 연관 관계를 설정할 수 있다. 하지만 이 경우 데이터베이스가 외래키를 통해 제공하는 데이터의 무결성과 일관성이 유지되지 못할 수 있다. 실제 데이터베이스에 저장된 데이터 중 무결성 및 일관성이 위반된 데이터가 존재하는 경우 논리적 외래키 제약 조건 설정을 통해 엔티티 간의 연관 관계를 설정하더라도 적절한 외래키 매핑이 되지 않아 엔티티 객체 간 연관 관계를 기반으로 수행하는 객체 그래프 탐색이 정상적으로 수행되지 않게 된다. 이 경우 JPA가 물리적으로 테이블 상에 존재하지 않는 외래 키 참조를 무시하도록 할지 아니면 예외를 던지도록 할지 정의함으로써 애플리케이션 코드 수준에서 외래키 참조가 깨진 엔티티를 처리할 수 있다. 즉, 외래키를 사용하지 않음으로써 위반될 수 있는 데이터 무결성을 지키기 위해서는 데이터를 사용하는 애플리케이션에서의 검증이 필요하다.

외래키를 사용하여 데이터베이스가 제공하는 참조 무결성 제약 조건을 활용하지 않는 이유는 일반적으로 다음과 같다.

  • 외래키 컬럼 삭제가 연관된 모든 테이블에 영향을 주므로 컬럼 변경에 따른 영향도가 크다.
  • 대용량 데이터의 경우 데이터베이스 성능이 저하된다.


테이블의 물리적 외래키 제약 조건 설정 여부와 관계 없이 논리적 외래키 제약 조건을 설정하려면 외래키를 가지고 있는 테이블에 해당하는 엔티티 필드에 @JoinColumn 어노테이션을 사용하여 연관 관계를 설정한다. @JoinColumn 어노테이션은 두 엔티티 간 논리적 외래키 매핑을 위해 사용하며 생략할 경우 필드명_참조하는 테이블의 컬럼명을 외래키로 사용한다.

논리적 외래키 제약 조건 설정을 위한 @JoinColumn 어노테이션의 프로퍼티는 다음과 같다.

  • name: 현재 테이블에서 외래키로 설정할 컬럼명
  • referencedColumnName: 외래키가 참조하는 대상 테이블의 컬럼명 (기본키 컬럼명)
  • foreignKey: 테이블의 물리적 외래키 제약 조건 설정을 위한 프로퍼티
    • 테이블에 물리적 외래키 제약 조건이 설정되어 있지 않은 상태에서 프로퍼티 값을 @jakarta.persistence.ForeignKey(value = ConstraintMode.NO_CONSTRAINT))으로 설정할 경우 테이블 상에 물리적 외래키 제약 조건을 생성한다.


테이블 상에 물리적 외래키 제약 조건 설정 여부와 관계 없이 엔티티 간 논리적 외래키 제약 조건을 설정하는 엔티티 객체 코드는 다음과 같다.

@Entity
@Table(name = "테이블명")
public class EntityA {
  @OneToOne
  @JoinColumn(name = "외래키컬럼명", 
    referencedColumnName = "참조대상테이블의컬럼명", 
      foreignKey = @jakarta.persistence
        .ForeignKey(value = ConstraintMode.NO_CONSTRAINT))
        
  private EntityB entityB;
}


@NotFound 어노테이션

@NotFound 어노테이션은 외래키 참조가 깨진 경우 엔티티 객체 탐색 시 발생하는 EntityNotFoundException 예외를 무시하도록 하거나 연관 관계 엔티티 객체를 인스턴스화할 때 FetchNotFoundException 예외를 던지도록 지시한다. action 파라미터를 설정하여 처리 방법을 선택할 수 있다.

@NotFound(action = NotFoundAction.IGNORE) 어노테이션은 외래 키 참조를 해결할 수 없는 경우(연관 관계 객체가 테이블에 존재하지 않는 경우) 널을 반환함으로써 예외가 발생하지 않도록 한다. 하지만 이 설정을 사용하는 경우 연관 관계가 설정되지 않았는지, 존재하지 않는 데이터를 참조하려고 하는지 구분할 수 없다. 두 경우에 대해 서로 다른 처리를 하려는 경우에는 위 설정을 사용할 수 없다.

@NotFound(action = NotFoundAction.EXCEPTION) 어노테이션은 외래 키 참조를 해결할 수 없는 경우 FetchNotFoundException 예외를 던지도록 지시한다.


참고

Comments