[Spring] Mapping

배창민·2025년 10월 23일
post-thumbnail

JPA Mapping


0) 프로젝트 기본 설정

build.gradle

dependencies {
    implementation 'org.springframework.boot:spring-boot-starter-data-jpa'
    runtimeOnly 'org.mariadb.jdbc:mariadb-java-client'
}

application.yml

spring:
  datasource:
    driver-class-name: org.mariadb.jdbc.Driver
    url: jdbc:mariadb://localhost:3306/menudb
    username: swcamp
    password: swcamp
  jpa:
    show-sql: true
    hibernate:
      ddl-auto: create
    properties:
      hibernate:
        format_sql: true
  • ddl-auto 옵션:
    create(매번 생성) / update(스키마 변경만 반영) / validate(검증만) / create-drop(종료 시 삭제)

1) Entity 매핑 핵심

@Entity

  • JPA 관리 대상 클래스 선언
  • 기본 생성자 필수, final/enum/interface/inner class 불가
  • 같은 프로젝트 내 동일 엔티티명 충돌 시 name으로 구분

기본 키: @Id, @GeneratedValue

  • IDENTITY: DB Auto Increment(MySQL/MariaDB)
  • SEQUENCE: 시퀀스(DB가 지원할 때)
  • TABLE: 키 생성 전용 테이블 사용
  • AUTO: DB에 맞춰 자동 선택

컬럼 매핑: @Column

자주 쓰는 속성만 요약

  • name, nullable, unique, length, insertable/updatable, columnDefinition

매핑 제외: @Transient

  • DB 컬럼과 매핑하지 않음(로직 전용 필드에 사용)

Enum 매핑: @Enumerated

  • ORDINAL(숫자) vs STRING(문자열)
    운영 안전성 측면에서 STRING 권장

예시: Member 엔티티

@Entity(name = "entityMember")
@Table(name = "tbl_member")
public class Member {
  @Id
  @GeneratedValue(strategy = GenerationType.IDENTITY)
  @Column(name = "member_no")
  private int memberNo;

  @Column(name = "member_id", unique = true, nullable = false, columnDefinition = "varchar(10)")
  private String memberId;

  @Column(name = "member_pwd", nullable = false)
  private String memberPwd;

  @Column(name = "member_name")
  private String memberName;

  @Transient
  private String phone;

  @Column(name = "address", length = 900)
  private String address;

  @Column(name = "enroll_date")
  private LocalDateTime enrollDate;

  @Enumerated(EnumType.STRING)
  @Column(name = "member_role")
  private MemberRole memberRole;

  @Column(name = "status", columnDefinition = "char(1) default 'Y'")
  private String status;

  protected Member() {}
  // 생성자 생략
}

Repository/Service 테스트로 테이블 생성 및 insert 확인

  • PK 우선 생성, 일반 컬럼은 유니코드 오름차순으로 생성
  • member_id에 유니크 제약 추가

2) @TableGenerator로 PK 생성(테이블 기반)

테이블을 이용해 키를 발급하는 방식.

@TableGenerator(
  name = "member_seq_tbl_generator",
  table = "tbl_my_sequences",
  pkColumnValue = "my_seq_member_no"
)
public class Member {
  @Id
  @GeneratedValue(strategy = GenerationType.TABLE, generator = "member_seq_tbl_generator")
  @Column(name = "member_no")
  private int memberNo;
  // ...
}

로그 흐름

  1. 엔티티 테이블 생성
  2. 시퀀스 테이블 생성 및 초기 레코드 삽입
  3. select for update → next_val 증가 → insert 진행

3) Access 전략(Field vs Property)

  • 기본값은 필드 접근(Field)
  • 프로퍼티 접근(Property)로 바꾸면 getter/setter를 통해 매핑
  • 클래스 단위/필드 단위로 혼용 가능
    주의: Property 접근 시 @Id는 getter 위에 둬야 함
@Entity(name = "entityMember")
@Table(name = "tbl_member")
@Access(AccessType.FIELD)
public class Member {

  // ...

  @Access(AccessType.PROPERTY)
  public String getMemberName() {
    System.out.println("getMemberName()으로 접근");
    return memberName + "님"; // 조회 시 가공
  }
}

Repository JPQL 예시(실전에서는 파라미터 바인딩 권장)

String jpql = "SELECT m.memberName FROM entityMember m WHERE m.memberId = :memberId";
return em.createQuery(jpql, String.class)
         .setParameter("memberId", memberId)
         .getSingleResult();

테스트에서 “님”이 붙은 값 반환 확인


4) Embedded 값 타입

공통 속성을 값 타입으로 분리해 재사용.

@Embeddable 값 타입

@Embeddable
public class Price {
  @Column(name = "regular_price")  private int regularPrice;
  @Column(name = "discount_rate")  private double discountRate;
  @Column(name = "sell_price")     private int sellPrice;

  protected Price() {}
  public Price(int regularPrice, double discountRate) {
    if (regularPrice < 0) throw new IllegalArgumentException("가격 음수 불가");
    if (discountRate < 0) throw new IllegalArgumentException("할인율 음수 불가");
    this.regularPrice = regularPrice;
    this.discountRate = discountRate;
    this.sellPrice = (int)(regularPrice - (regularPrice * discountRate));
  }
}

엔티티에 포함: @Embedded

@Entity
@Table(name = "tbl_book")
public class Book {
  @Id @GeneratedValue(strategy = GenerationType.IDENTITY)
  @Column(name = "book_no")
  private int bookNo;

  // ...

  @Embedded
  private Price price;
}

DDL에서 임베디드 필드는 소유 엔티티 테이블 컬럼으로 평탄화됨(regular_price, discount_rate, sell_price 등)


5) 복합 키 매핑

두 방식 모두 복합키 클래스는 Serializable 구현 필요, @GeneratedValue 사용 불가.
equals/hashCode 구현을 강력 권장(동등성/캐시 일관성 보장).

5-1) @EmbeddedId

@Embeddable
public class LikeCompositeKey implements Serializable {
  @Column(name = "liked_member_no") private int likedMemberNo;
  @Column(name = "liked_book_no")   private int likedBookNo;
  protected LikeCompositeKey() {}
  public LikeCompositeKey(int likedMemberNo, int likedBookNo) { ... }
  // equals/hashCode 권장
}

@Entity
@Table(name = "tbl_like")
public class Like {
  @EmbeddedId
  private LikeCompositeKey likeInfo;
  protected Like() {}
  public Like(LikeCompositeKey likeInfo) { this.likeInfo = likeInfo; }
}

DDL 예시

create table tbl_like (
  liked_book_no integer not null,
  liked_member_no integer not null,
  primary key (liked_book_no, liked_member_no)
)

5-2) @IdClass

public class CartCompositeKey implements Serializable {
  private int cartOwner;
  private int addedBook;
  protected CartCompositeKey() {}
  public CartCompositeKey(int cartOwner, int addedBook) { ... }
  // equals/hashCode 권장
}

@Entity
@Table(name = "tbl_cart")
@IdClass(CartCompositeKey.class)
public class Cart {
  @Id @Column(name = "cart_owner") private int cartOwner;
  @Id @Column(name = "added_book") private int addedBook;
  @Column(name = "quantity")       private int quantity;
  protected Cart() {}
  public Cart(int cartOwner, int addedBook, int quantity) { ... }
}

DDL 예시

create table tbl_cart (
  added_book integer not null,
  cart_owner integer not null,
  quantity integer,
  primary key (added_book, cart_owner)
)

6) 체크리스트/주의사항

  • ddl-auto(create/update)는 개발용. 운영 환경에서는 명시적 마이그레이션 도구 사용 권장
  • Enum은 STRING 권장(순서 변경 시 안전)
  • JPQL은 문자열 연결 대신 파라미터 바인딩 사용
  • Property 접근 사용 시 @Id 위치 주의
  • Embedded 값 타입은 불변(세터 지양)·자기완결적 검증 로직 권장
  • 복합 키 클래스는 Serializable + equals/hashCode 구현

참고 테스트 포인트

  • Member 저장/조회(필드 vs 프로퍼티 접근)
  • TableGenerator 시퀀스 테이블 동작
  • Book의 Embedded Price 평탄화 컬럼 생성
  • Like(@EmbeddedId)/Cart(@IdClass) 복합키 insert
profile
개발자 희망자

0개의 댓글