[JPA] 객체와 테이블 매핑 :: 잡다한 프로그래밍
반응형

지난 시간에는 JPA의 영속성 컨텍스트(Persistence Context)와 1차 캐시에 대해 살펴보았습니다.

 

이번 글에서는 실제 설계된 도메인 객체를 RDB 테이블과 매핑하는 방법,

즉 JPA 엔티티 매핑의 기본 개념과 어노테이션 사용법을 정리 하겠습니다.

 

1. JPA 엔티티(@Entity)란?

@Entity

  • JPA가 관리하는 영속성 대상 클래스임을 선언합니다.
  • 이 어노테이션이 붙은 클래스만이 JPA의 CRUD, 변경 감지, 지연 로딩 등의 기능을 활용할 수 있습니다.
import javax.persistence.Entity;
import javax.persistence.Id;

@Entity
public class User {
    @Id
    private Long id;
    private String name;
    // ... getter, setter, 기본생성자 등
}

 

엔티티 클래스 제약 사항

  1. 기본 생성자
    • 파라미터가 없는 public 또는 protected 생성자가 반드시 필요합니다. (프록시 객체를 만들기 위해)
  2. final 클래스, enum, interface, inner 클래스 사용 불가
    • 엔티티는 런타임에 프록시로 확장될 수 있어야 하므로 final 클래스나 enum/interface/내부 클래스(inner class)로 선언할 수 없습니다.
  3. final 필드 사용 불가
    • JPA는 필드 값을 변경 감지하기 위해 리플렉션을 사용하므로, final 필드는 사용할 수 없습니다.
더보기

JPA의 지연 로딩과 프록시 객체

  1. 지연 로딩(Lazy Loading) 개념
    • 엔티티 조회 시, 연관된 다른 엔티티를 즉시 함께 가져오지 않고
    • 실제로 해당 연관 데이터를 사용할 때(getter 호출 시) 한 번 더 DB 조회를 수행하는 방식
@Entity
public class Post {
    @Id
    @GeneratedValue
    private Long id;

    private String title;

    @ManyToOne(fetch = FetchType.LAZY) // ← 지연 로딩 설정
    private User author;               // ← 이 부분이 지연 로딩 대상
}

프록시 객체(Proxy Object)

  • 지연 로딩을 구현하기 위해 JPA가 생성하는 “가짜” 엔티티 객체
  • 실제 엔티티 클래스를 상속해 만들고, 초기에는 최소한의 자리만 차지하다가
  • 필요한 시점에 원본 데이터를 로딩

- final class가 안 되는 이유

  • 프록시 객체를 만들려면 엔티티 클래스를 상속해야 하는데,
  • final class는 상속이 금지되어 있어 프록시 생성이 불가
  • 결과적으로 지연 로딩 기능 자체를 사용할 수 없으므로,
  • JPA 엔티티에는 final class를 허용하지 않음

- final 필드가 안 되는 이유

  • JPA는 리플렉션과 기본 생성자 + setter 호출을 통해 프록시 객체의 내부 값을 채움
  • final 필드는 한번 초기화된 뒤 변경이 불가능하므로,
  • 리플렉션을 통한 값 설정이 실패하고 정상 동작할 수 없음
  • 따라서 엔티티의 속성(컬럼 매핑 필드)에도 final 키워드를 사용하지 말아야 함

 

※ JAVA record 클래스와 JPA

Java 14에 도입된 record는 불변 데이터 캐리어(Immutable Data Carrier) 로, 컴파일러가 자동으로 equals/hashCode/toString/모든 필드를 초기화하는 생성자 등을 생성해 줍니다. 일견 JPA 엔티티처럼 데이터 보관용 DTO로 유용해 보이지만, 다음 이유로 엔티티로 사용할 수 없습니다.

  1. 불변성(final 필드)
    record의 모든 구성 요소(component)는 암묵적으로 private final 필드로 선언됩니다.
    → JPA는 영속성 컨텍스트에서 변경 감지를 위해 필드 값을 바꿀 수 있어야 하지만, final 필드이므로 불가능합니다.
  2. 기본 생성자 부재
    record는 전체 필드를 인자로 받는 생성자만 제공하고, 파라미터 없는 생성자를 생성하지 않습니다.
    → JPA 사양에서는 “파라미터 없는 기본 생성자”를 요구합니다.
  3. 프록시(subclass) 생성 불가
    JPA 구현체(예: Hibernate)는 프록시 생성이나 바이트코드 조작을 위해 엔티티 클래스를 상속하거나 바이트코드를 재작성해야 합니다.
    record는 final로 간주되어 상속이 불가능합니다.

 

2. 테이블 매핑 어노테이션: @Table

@Entity만 선언하면 엔티티 이름과 같은 테이블에 매핑됩니다. 테이블명, 스키마, 카탈로그, 제약 조건 등을 세밀하게 지정하려면 @Table 을 함께 사용합니다.

@Entity
@Table(
    name = "users",            // 매핑할 테이블명
    schema = "public",         // 스키마
    uniqueConstraints = {
        @UniqueConstraint(columnNames = {"email"})  // 이메일 컬럼에 유니크 제약 걸기
    }
)
public class User {

 

옵션 종류

 

  • name: 매핑할 테이블명
  • schema / catalog: DB 스키마/카탈로그
  • uniqueConstraints: DDL 생성 시 추가할 복합 유니크 제약 조건

 

 

반응형

'프로그래밍 > JPA' 카테고리의 다른 글

[JPA] 필드와 컬럼 매핑  (1) 2025.06.19
[JPA] 데이터베이스 스키마 자동 생성  (0) 2025.06.19
[JPA] JPA의 영속성 관리2  (0) 2025.06.10
[JPA] JPA 의 영속성 관리  (0) 2025.06.10
[JPA] JPA 동작 방식  (2) 2025.06.08

+ Recent posts