자바 ORM 표준 JPA 프로그래밍 - 기본편 수업을 듣고 정리한 내용입니다.

 

🔔 엔티티 매핑

  • 객체와 테이블 매핑 : @Entity, @Table
  • 필드와 컬럼 매핑 : @Column
  • 기본 키 매핑 : @Id
  • 연관관계 매핑 : @ManyToOne, @JoinColumn

 

📚 1. 객체와 테이블 매핑

📖 A. @Entity

  • @Entity가 붙은 클래스는 JPA가 관리하며, 이를 엔티티라 한다.
  • JPA를 사용해서 테이블과 매핑할 클래스는 @Entity 필수이다.
  • 주의
    • 기본 생성자 필수 (파라미터가 없는 public 또는 protected 생성자)
    • final 클래스, enum, interface, inner 클래스는 사용하지 않는다. → 해당 클래스는 @Entity를 붙여서 DB Table과 매핑할 수 없다.
    • DB에 저장할 필드에 final 사용하지 않는다.

 

✔️ @Entity 속성 정리
속성 : name

  • JPA에서 사용할 엔티티 이름을 지정한다.
  • 기본 값 : 클래스 이름을 그대로 사용한다. ex) Member
  • 같은 클래스 이름이 없으면 가급적 기본 값을 사용한다.

 

📖 B. @Table

@Table은 엔티티와 매핑할 테이블 지정

속성기능기본값
name매핑할 테이블 이름엔티티 이름을 사용
catalog데이터베이스 catalog 매핑
schema데이터베이스 schema 매핑
uniqueConstraints (DDL)DDL 생성 시에 유니크 제약 조건 생성

 

스크린샷 2022-03-04 오후 5 45 32 스크린샷 2022-03-04 오후 5 45 41
  • nameMBR로 잡았다.
  • 출력 결과에서 테이블이름이 MBR로 출력된다.

 

📚 2. 데이터베이스 스키마 자동 생성

  • 애플리케이션 실행 시점에 DDL을 자동 생성
  • 테이블중심 → 객체중심
  • 데이터베이스 방언을 활용해서 데이터베이스에 맞는 적절한 DDL 생성
  • 이렇게 생성된 DDL은 개발 장비에서만 사용
  • 생성된 DDL은 운영서버에서는 사용하지 않거나, 적절히 다듬은 후 사용

 

✔️ 데이터베이스 스키마 자동 생성 - 속성
hibernate.hbm2ddl.auto

옵션설명
create기존테이블 삭제 후 다시 생성 (DROP + CREATE)
create-dropcreate와 같으나 종료시점에 테이블 DROP
update변경분만 반영(운영DB에는 사용하면 안됨)
validate엔티티와 테이블이 정상 매핑되었는지만 확인
none사용하지 않음

 

✔️ 실습

create
persistence.xml
<property name="hibernate.hbm2ddl.auto" value="create" /> 주석 해제 후, 실행시

스크린샷 2022-03-04 오후 6 25 15 스크린샷 2022-03-04 오후 6 25 26 스크린샷 2022-03-04 오후 6 27 56
  • 이전까지는 직접 테이블을 생성하고 실행했어야 했지만, 지금은 auto로 자동 생성된다.

 

update
persistence.xml
<property name="hibernate.hbm2ddl.auto" value="update" /> : update로 수정 후 실행시

스크린샷 2022-03-04 오후 6 32 50 스크린샷 2022-03-04 오후 6 32 54 스크린샷 2022-03-04 오후 6 33 04
  • 이전에는 테이블에 ID, NAME만 있었지만 number를 추가하였다.
  • 실행 결과를 보면 number가 추가된 것을 알 수 있다.

 

drop
persistence.xml<property name="hibernate.hbm2ddl.auto" value="drop" /> : drop로 삭제 실행시

스크린샷 2022-03-04 오후 6 36 03 스크린샷 2022-03-04 오후 6 36 55
  • SELECT 결과, MEMBER 클래스를 찾지 못했다는 것을 알 수 있다.

 

⚠️ 주의

  • 테스트 서버에서는 create를 사용하면 안된다. (이전 입력 했던 데이터들이 다 날라간다.)
  • 웹 애플리케이션에서는 alter, drop을 사용하면 안된다.
  • 운영 장비에는 절대 create, create-drop, update 사용하면 안된다.
  • 개발 초기 단계는 create 또는 update
  • 테스트 서버는 update 또는 validate
  • 스테이징과 운영 서버는 validate 또는 none

 

✔️ DDL 생성 기능
제약조건 추가 : 회원 이름은 필수다! 10자 초과는 안된다.
➡️ @Column(nullable = false, length = 10)

유니크 제약조건 추가
➡️ @Table(uniqueConstraints = {@UniqueConstraint( name = "NAME_AGE_UNIQUE", columnNames = {"NAME", "AGE"} )})

DDL 생성 기능은 DDL을 자동 생성할 때만 사용되고 JPA의 실행 로직에는 영향을 주지 않는다.

 

📚 3. 필드와 컬럼 매핑

✏️ 요구사항 추가
(1) 회원은 일반 회원과 관리자로 구분해야 한다.
(2) 회원 가입일과 수정일이 있어야 한다.
(3) 회원을 설명할 수 있는 필드가 있어야 한다. 이 필드는 길이 제한이 없다.

 

package hellojpa;
 import javax.persistence.*;
import java.time.LocalDate;
 import java.time.LocalDateTime;
 import java.util.Date;
 @Entity
 public class Member {
 @Id
 private Long id;
 @Column(name = "name")
 private String username;
private Integer age;
 @Enumerated(EnumType.STRING)
 private RoleType roleType;
 @Temporal(TemporalType.TIMESTAMP)
 private Date createdDate;
 @Temporal(TemporalType.TIMESTAMP)
 private Date lastModifiedDate;
 @Lob
 private String description;
}
스크린샷 2022-03-07 오후 12 21 51

 

📖 A. 매핑 애노테이션 정리

hibernate.hbm2ddl.auto

어노테이션설명
@Column컬럼 매핑
@Temporal날짜 타입 매핑
@Enumeratedenum 타입 매핑
@LobBLOB, CLOB 매핑
@Transient특정 필드를 컬럼에 매핑하지 않음(매핑 무시)

 

✔️ @Column

속성설명기본값
name필드와 매핑할 테이블의 컬럼 이름객체의 필드 이름
insertable, updatable등록, 변경 가능 여부TRUE
nullable(DDL)null 값의 허용 여부를 설정한다. false로 설정하면 DDL 생성 시에 not null 제약조건이 붙는다.
unique(DDL)@Table의 uniqueConstraints와 같지만 한 컬럼에 간단히 유니크 제 약조건을 걸 때 사용한다.
columnDefinition (DDL)데이터베이스 컬럼 정보를 직접 줄 수 있다. ex) varchar(100) default ‘EMPTY'필드의 자바 타입과 방언 정보를 사용해
length(DDL)문자 길이 제약조건, String 타입에만 사용한다.255
precision, scale(DDL)BigDecimal 타입에서 사용한다(BigInteger도 사용할 수 있다). precision은 소수점을 포함한 전체 자릿수를, scale은 소수의 자릿수다. 참고로 double, float 타입에는 적용되지 않는다. 아주 큰 숫자나 정밀한 소수를 다루어야 할 때만 사용한다.precision=19, scale=2

 

✔️ @Enumerated

  • 자바 enum 타입을 매핑할 때 사용
  • 주의! ORDINAL 사용하지 않는다.
속성설명기본값
valueEnumType.ORDINAL: enum 순서를 데이터베이스에 저장, EnumType.STRING: enum 이름을 데이터베이스에 저장EnumType.ORDINAL

 

✔️ @Temporal

  • 날짜 타입(java.util.Date, java.util.Calendar)을 매핑할 때 사용
  • LocalDate, LocalDateTime을 사용할 때는 생략 가능(최신 하이버네이트 지원)
속성설명기본값
valueTemporalType.DATE: 날짜, 데이터베이스 date 타입과 매핑(예: 2013–10–11), TemporalType.TIME: 시간, 데이터베이스 time 타입과 매핑(예: 11:11:11), TemporalType.TIMESTAMP: 날짜와 시간, 데이터베이 스 timestamp 타입과 매핑(예: 2013–10–11 11:11:11)

 

✔️ @Lob

  • 데이터베이스 BLOB, CLOB 타입과 매핑
    • @Lob에는 지정할 수 있는 속성이 없다.
    • 매핑하는 필드 타입이 문자면 CLOB 매핑, 나머지는 BLOB 매핑
      • CLOB: String, char[], java.sql.CLOB
      • BLOB: byte[], java.sql. BLOB

 

✔️ @Transient

  • 필드 매핑하지 않는다.
  • 데이터베이스에 저장와 조회를 하지 않는다.
  • 주로 메모리상에서만 임시로 어떤 값을 보관하고 싶을 때 사용한다.
  • @Transient private Integer temp;

 

📚 4. 기본 키 매핑

✔️ 기본 키 매핑 어노테이션

  • @Id
  • @GeneratedValue
@Id @GeneratedValue(strategy = GenerationType.AUTO) private Long id;

 

✔️ 기본 키 매핑 방법

  • 직접 할당 : @Id만 사용
  • 자동 생성(@GeneratedValue)
    • IDENTITY : 데이터베이스에게 위임, MYSQL
    • SEQUENCE : 데이터베이스 시퀀스 오브젝트 사용, ORACLE
      • @SequenceGenerator 필요
    • TABLE : 키 생성용 테이블 사용, 모든 DB에서 사용
      • @TableGenerator 필요
    • AUTO : 방언에 따라 자동 지정, 기본값

 

(1) IDENTITY 전략

🔔 특징

  • 기본 키 생성을 데이터베이스에 위임
  • 주로 MySQL, PostgreSQL, SQL Server, DB2에서 사용
    • 예: MySQLAUTO_ INCREMENT
  • JPA는 보통 트랜잭션 커밋 시점에 INSERT SQL 실행
  • AUTO_ INCREMENT는 데이터베이스에 INSERT SQL을 실행한 이후에 ID 값을 알 수 있음
  • IDENTITY 전략은 em.persist() 시점에 즉시 INSERT SQL 실행하고 DB에서 식별자를 조회

 

✔️ 매핑

@Entity
public class Member {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

 

(2) SEQUENCE 전략

🔔 특징

  • 데이터베이스 시퀀스는 유일한 값을 순서대로 생성하는 특별한 데이터베이스 오브젝트(예: 오라클 시퀀스)
  • 오라클, PostgreSQL, DB2, H2 데이터베이스에서 사용

 

✔️ 매핑

@Entity 
@SequenceGenerator( 
        name = “MEMBER_SEQ_GENERATOR", 
        sequenceName = “MEMBER_SEQ", //매핑할 데이터베이스 시퀀스 이름
        initialValue = 1, allocationSize = 1)
public class Member { 
    @Id 
    @GeneratedValue(strategy = GenerationType.SEQUENCE, 
                    generator = "MEMBER_SEQ_GENERATOR") 
    private Long id; 

 

✔️ @SequenceGenerator

  • 테이블마다 시퀀스를 따로 관리하고 싶을 때 사용한다.
  • 주의 : allocationSize 기본값 = 50
속성설명기본값
name식별자 생성기 이름필수
sequenceName데이터베이스에 등록되어 있는 시퀀스 이름hibernate_sequence
initialValueDDL 생성 시에만 사용됨, 시퀀스 DDL을 생성할 때 처음 1 시작하는 수를 지정한다.1
allocationSize시퀀스 한 번 호출에 증가하는 수(성능 최적화에 사용됨 데이터베이스 시퀀스 값이 하나씩 증가하도록 설정되어 있으면 이 값을 반드시 1로 설정해야 한다.50
catalog, schema데이터베이스 catalog, schema 이름

 

(3) TABLE 전략

🔔 특징

  • 키 생성 전용 테이블을 하나 만들어서 데이터베이스 시퀀스를 흉내내는 전략
  • 모든 데이터베이스에 적용이 가능하지만, 성능이 떨어진다.

 

✔️ 매핑

create table MY_SEQUENCES ( 
    sequence_name varchar(255) not null, 
    next_val bigint, 
    primary key ( sequence_name ) 
)
@Entity 
@TableGenerator( 
        name = "MEMBER_SEQ_GENERATOR", 
        table = "MY_SEQUENCES", 
        pkColumnValue = “MEMBER_SEQ", allocationSize = 1) 
public class Member {
    @Id 
    @GeneratedValue(strategy = GenerationType.TABLE, 
                    generator = "MEMBER_SEQ_GENERATOR") 
    private Long id;

 

✔️ @TableGenerator - 속성

속성설명기본값
name식별자 생성기 이름필수
table키생성 테이블명hibernate_sequences
pkColumnName시퀀스 컬럼명sequence_name
valueColumnNa시퀀스 값 컬럼명next_val
pkColumnValue키로 사용할 값 이름엔티티 이름
initialValue초기 값, 마지막으로 생성된 값이 기준이다.0
allocationSize시퀀스 한 번 호출에 증가하는 수(성능 최적화에 사용됨)50
catalog, schema데이터베이스 catalog, schema 이름
uniqueConstraint s(DDL)유니크 제약 조건을 지정할 수 있다.

SEQUENCE와 마찬가지로 initialValueallocationSize가 있다.

 

✏️ 권장하는 식별자 전략

  • 기본 키 제약 조건 : null 아님(NOT NULL), 유일(UNIQUE), 변하면 안된다.
  • 미래까지 이 조건을 만족하는 자연키는 찾기 어렵다. 대리키(대체키)를 사용하자.
  • 예를 들어 주민등록번호도 기본 키로 적절하지 않다.
  • 권장 : Long형 + 대체키 + 키 생성전략 사용

 

📚 5. 실전 예제 - 1. 요구사항 분석과 기본 매핑

✏️ 요구사항 분석

  • 회원은 상품을 주문할 수 있다.
  • 주문 시 여러 종류의 상품을 선택할 수 있다.

 

스크린샷 2022-03-07 오후 4 42 54

✔️ 기능 목록

  • 회원기능
    • 회원등록
    • 회원조회
  • 상품기능
    • 상품등록
    • 상품수정
  • 상품조회
    • 주문기능
    • 상품주문
    • 주문내역조회
    • 주문취소

 

✔️ 도메인 모델 분석

스크린샷 2022-03-07 오후 4 46 55
  • 회원과 주문의 관계 : 회원은 여러 번 주문할 수 있으므로 회원과 주문은 일대다 관계다.
  • 주문과 상품의 관계 : 주문할 때 여러 상품을 함께 선택할 수 있고, 같은 상품도 여러 번 주문될 수도 있으므로 둘은 다대다 관계다. 하지만 이런 다대다 관계는 관계형 데이터베이스는 물론이고 엔티티에서도 거의 사용하지 않는다. 따라서 주문상품이라는 연결 엔티티를 추가해서 다대다 관계를 일대다, 다대일 관계로 풀어냈다. 그리고 주문상품에는 해당 상품을 구매한 금액과 수량 정보가 포함되어 있다.

 

✔️ 테이블 설계

스크린샷 2022-03-07 오후 4 48 27
  • 회원(MEMBER) : 이름(NAME)과 주소 정보를 가진다. 주소는 CITY, STREET, ZIPCODE로 표현한다.
  • 주문(ORDERS) : 상품을 주문한 회원(MEMBER_ID)을 외래 키로 가진다. 그리고 주문날짜(ORDERDATE)와 주문 상태(STATUS)를 가진다. 주문 상태는 주문(ORDER)과 취소(CANCEL)를 표현할 수 있다.
  • 주문상품(ORDER_ITEM) : 주문(ORDER_ID)과 주문한 상품(ITEM_ID)을 외래 키로 가진다. 주문 금액(ORDERPRICE), 주문 수량(COUNT) 정보를 가진다.
  • 상품(ITEM) : 이름(NAME), 가격(PRICE), 재고수량(STOCKQUANTITY)을 가진다. 상품을 주문하면 재고수량이 줄어든다.

 

✔️ 엔티티 설계와 매핑

스크린샷 2022-03-07 오후 4 48 38

 

Member

package jpabook.model.entity;

import javax.persistence.*;
import java.util.ArrayList;
import java.util.List;

@Entity
public class Member {

    @Id @GeneratedValue
    @Column(name = "MEMBER_ID")
    private Long id;

    private String name;

    private String city;
    private String street;
    private String zipcode;

    //Getter, Setter
    ...
}
  • @GeneratedValue의 기본 생성 전략은 AUTO이므로 선택한 데이터베이스 방언에 따라 IDENTITY, SEQUENCE, TABLE 중 하나가 선택된다.

 

package jpabook.model.entity;

import javax.persistence.*;
import java.util.Date;

/**
 * Created by holyeye on 2014. 3. 11..
 */
@Entity
@Table(name = "ORDERS")
public class Order {

    @Id
    @GeneratedValue
    @Column(name = "ORDER_ID")
    private Long id;

    @Column(name = "MEMBER_ID")
    private Long memberId;

    @Temporal(TemporalType.TIMESTAMP)
    private Date orderDate;     //주문시간

    @Enumerated(EnumType.STRING)
    private OrderStatus status;//주문상태

    //Getter, Setter
    ...
}
  • 주문 날짜는 Date를 사용하고 년월일 시분초를 모두 사용하므로 @TemporalTemporalType.TIMESTAMP 속성을 사용해서 매핑했다. 참고로 @Temporal을 생략하면 @Temporal(TemporalType.TIMESTAMP)와 같으므로 예제에서는 생략해도 된다.
  • 주문 상태는 열거형을 사용하므로 @Enumerated로 매핑했고, EnumType.STRING 속성을 지정해서 열거형의 이름이 그대로 저장되도록 했다. 그리고 OrderStatus 열거형을 사용하므로 주문(ORDER)과 취소(CANCEL)를 표현할 수 있다.

 

@Entity
@Table(name = "ORDER_ITEM")
public class OrderItem {

    @Id
    @GeneratedValue
    @Column(name = "ORDER_ITEM_ID")
    private Long id;

    @Column(name = "ITEM_ID")
    private Long itemId;
    @Column(name = "ORDER_ID")
    private Long orderId;

    private int orderPrice; //주문 가격
    private int count;      //주문 수량

    //Getter, Setter
    ...
}
  • 주문상품은 주문(orderId)의 외래 키 값과 주문한 상품(itemId)의 외래 키 값을 가진다.
  • 주문 금액(orderPrice)과 주문 수량(count) 정보를 가진다.

 

@Entity
public class Item {

    @Id @GeneratedValue
    @Column(name = "ITEM_ID")
    private Long id;

    private String name;        //이름
    private int price;          //가격
    private int stockQuantity;  //재고수량


    //Getter, Setter
    ...
}

 

📖 A. 데이터 중심 설계의 문제점

  • 현재는 객체 설계를 테이블 설계에 맞춘 방법이다.
  • 특히, 테이블의 외래키를 객체에 그대로 가져온 부분이 문제이다! (관계형 데이터베이스는 연관된 객체를 찾을 때 외래 키를 사용해서 조인하면 되지만 객체에는 조인이라는 기능이 없다. 연관된 객체를 찾을 때는 참조를 사용해야 한다.)
  • 객체에 참조 대신에 데이터베이스에 외래 키를 가지고 있으므로 order.getMember()처럼 객체 그래프를 탐색할 수도 없고, 객체의 특성도 살릴 수 없다.
  • 객체는 외래 키 대신에 참조를 사용해야 한다.

외래 키만 가지고 있으면 연관된 엔터티를 찾을 때 외래 키로 데이터베이스를 다시 조회해야 한다.

Order order = em.find(Order.class, orderId);

// 외래 키로 다시 조회
Member member = em.find(Member.class, order.getMemberId());

객체는 참조를 사용해서 연관관계를 조회할 수 있다.

Order order = em.find(Order.class, orderId);
Member member = order.getMember();  // 참조 사용

따라서, 참조를 사용하는 것이 객체지향적인 방법이다.

 

✏️ 정리

  • 객체는 참조를 사용해서 연관된 객체를 찾는다.
  • 테이블은 외래 키를 사용해서 연관된 테이블을 찾는다.
  • JPA는 객체의 참조와 테이블의 외래 키를 매핑해서 객체에서는 참조를 사용하고 테이블에서는 외래 키를 사용할 수 있도록 한다.

 


참고

profile
"야, (오류 만났어?) 너두 (해결) 할 수 있어"

0개의 댓글