노트

JPA 엔티티 기본 매핑

JPA Entity Mapping

백엔드#jpa#spring · 연결된 개념 7개

쉽게 말하면

JPA 엔티티 매핑은 자바 클래스에 '이 클래스는 이 표, 이 필드는 이 칸'이라고 이름표를 붙여 두는 일이에요. 그러면 행을 객체로 옮겨 담는 코드를 직접 짜지 않아도 JPA가 알아서 읽고 써 줘요.

비유의 한계 이름표를 안 붙인 필드도 기본 규칙으로 칸에 연결돼요. 그래서 저장하면 안 되는 필드는 @Transient로 직접 빼 줘야 해요.

엔티티 매핑(entity mapping)은 자바 클래스와 필드를 테이블과 컬럼에 대응시키는 JPA(Jakarta Persistence)의 기본 설정이다. 애너테이션은 jakarta.persistence 패키지에 있다. 테이블 사이의 외래키를 참조로 옮기는 일은 JPA 연관관계 매핑에서 다룬다.

@Entity
@Table(name = "contact_msg")
public class Contact {
 
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;
 
    @Column(name = "mobile_num", nullable = false, length = 20)
    private String mobileNum;
 
    @Enumerated(EnumType.STRING)          // 'OPEN', 'CLOSED' 같은 이름으로 저장
    private Status status;
 
    @Transient                             // 컬럼과 연결하지 않음
    private String confirmEmail;
 
    protected Contact() {}                 // JPA가 쓰는 인자 없는 생성자
}

클래스와 테이블

  • @Entity: 이 클래스가 엔티티라는 표시다. 명세는 public 또는 protected인 인자 없는 생성자를 요구하고, 클래스와 영속 필드가 final이면 안 된다고 정한다. Hibernate는 이보다 느슨하지만, final이면 지연 로딩용 프록시를 만들 수 없다
  • @Table: 테이블 이름·스키마를 따로 정할 때 붙인다. 없으면 엔티티 이름을 테이블 이름으로 쓴다
  • @Column: 컬럼 이름과 nullable·length·unique·insertable·updatable 같은 속성을 정한다. 붙이지 않은 필드도 영속 필드가 되고, 컬럼 이름은 필드 이름에서 정해진다
  • @Transient: 저장하지 않을 필드에 붙인다. 비밀번호 확인 입력처럼 화면에서만 쓰는 값이 예다

기본키 생성 전략

@Id로 기본키 필드를 정하고, 값을 자동으로 만들려면 @GeneratedValue(strategy = ...)를 붙인다.

전략동작
IDENTITYDB의 identity(자동 증가) 컬럼. INSERT를 해야 id를 알 수 있다
SEQUENCEDB 시퀀스에서 값을 받는다. 이름을 안 주면 Hibernate는 테이블명_seq를 쓴다
TABLE키 값을 보관하는 별도 테이블(Hibernate 기본 이름 hibernate_sequences)을 쓴다
AUTO기본값. 구현체가 고른다. Hibernate는 숫자 타입이면 시퀀스, DB가 시퀀스를 지원하지 않으면 테이블 방식을 쓴다

IDENTITY는 행을 실제로 넣어야 id가 정해지므로, Hibernate가 그 엔티티의 INSERT를 JDBC 배치로 묶지 못한다. 새 행을 한꺼번에 많이 넣는 엔티티라면 SEQUENCE를 검토한다.

enum은 STRING으로

@Enumerated를 붙이지 않거나 타입을 지정하지 않으면 EnumType.ORDINAL로 저장된다(변환기나 @EnumeratedValue가 없을 때). ORDINAL은 enum 선언 안에서의 위치(0부터)를 정수로 넣는다. 그래서 상수 사이에 새 값을 끼우거나 순서를 바꾸면 이미 저장된 숫자가 다른 상수를 가리키게 된다. EnumType.STRING은 상수 이름을 저장하므로 순서 변경에 안전하고, DB를 직접 볼 때도 읽기 쉽다. 대신 상수 이름을 바꾸면 기존 데이터를 같이 고쳐야 한다.

JdbcTemplate에서 옮길 때 바뀌는 점

  • DAO와 RowMapper가 하던 "행 → 객체" 변환을 엔티티 매핑이 맡고, DAO는 리포지터리 인터페이스로 바뀐다(JDBC와 JdbcTemplate)
  • 필드 mobileNum이 컬럼 mobile_num에 붙는 것은 JPA 규칙이 아니라 스프링 부트의 기본 설정 덕분이다. 부트는 물리 이름 전략으로 CamelCaseToUnderscoresNamingStrategy를 써서 카멜 표기를 밑줄로 바꾸고 소문자로 만든다. Hibernate 기본 규칙은 컬럼 이름을 필드 이름과 같게 둔다
  • 부트는 @SpringBootApplication이 정하는 자동 구성 패키지(auto-configuration package)에서 엔티티와 리포지터리를 찾는다. @EntityScan과 @EnableJpaRepositories는 엔티티·리포지터리가 그 밖에 있을 때만 붙인다
  • 생성·수정 시각 같은 공통 컬럼은 @MappedSuperclass로 모으고 자동 기록은 JPA Auditing을 쓴다
  • 엔티티는 영속성 컨텍스트가 관리하므로, 바뀐 필드는 UPDATE를 직접 쓰지 않아도 커밋 때 반영된다(더티 체킹)

엔티티에 Lombok @Data를 쓰지 않는 이유는 Lombok과 JPA 연관관계 매핑을 본다.

명세: Jakarta Persistence 3.2 §2.1 The Entity Class

출처: Hibernate ORM User Guide: POJO Models · Generated identifier values · Using IDENTITY columns · @Enumerated · Jakarta Persistence API: Enumerated · Transient · Java SE: Enum.ordinal() · Naming strategies · Spring Boot 문서: Configure Hibernate Naming Strategy · Entity Classes · Spring Data JPA Repositories · Auto-configuration Packages

연결된 개념

이 노트를 가리키는 문서

뜻이 가까운 노트

  • JPQL과 @Query

    JPQL(Jakarta Persistence Query Language)은 테이블이 아니라 엔티티와 필드를 대상으로 쓰는 JPA의 객체 지향 쿼리 언어다. 실행할 때 연결된 DB의 SQL로 번역된다. Spring Data에서는 @Query로 리포지터리 메서드에 직접 붙인다.

  • 의존성 주입 (DI)

    객체가 필요한 협력 객체를 직접 new로 만들지 않고, 바깥(스프링 컨테이너)에서 넣어 받는 방식. 무엇을 언제 만들고 어떻게 연결할지의 제어가 내 코드에서 컨테이너로 넘어가므로 제어의 역전(Inversion of Control, IoC)이라고도 부른다.

  • 지연 로딩

    연관된 엔티티를 처음 접근할 때 조회하는 방식. 그 전까지는 그 자리에 프록시(컬렉션이면 Hibernate 컬렉션 래퍼)가 들어 있다.

  • Bean Validation

    Bean Validation은 객체 필드에 제약 어노테이션(constraint annotation)을 붙여 입력을 검증하는 자바 표준이다(Jakarta Validation, 구현체는 Hibernate Validator). 스프링은 컨트롤러 파라미터에 @Valid가 붙으면 바인딩 직후 검증을 실행한다.

  • 영속성 전이 (cascade)

    영속성 전이(cascade, transitive persistence)는 부모 엔티티에 한 영속성 작업(저장·병합·삭제 등)을 연관된 자식 엔티티에도 자동으로 적용하는 JPA 옵션이다. 기본값은 아무것도 전이하지 않는 것이다.

보기 옵션