BE/JPA

[Spring] 자바 ORM 표준 JPA 프로그래밍 4. 엔티티 매핑

hyu_na 2024. 4. 7. 17:28

JPA가 지원하는 다양한 어노테이션 중 대표 어노테이션

-객체와 테이블 매핑 :@Entity, @Table

-기본 키 매핑 :@Id

-필드와 컬럼 매핑 :@Column

-연관관계 매핑 :@ManyToOne, @JoinColumn

 

객체와 테이블 매핑, 기본 키 매핑, 필드와 칼럼 매핑에 대해 알아볼 것 

 

4.1 @Entity

JPA를 사용해서 테이블과 매핑할 클래스는 @Entity 어노테이션을 필수로 붙여야함

@Entity가 붙은 클래스 = 엔티티 (JPA가 관리함)

 

 속성 기능 기본값
name JPA에서 사용할 엔티티의 이름을 지정
보통 기본값인 클래스 이름을 사용함 
만약 다른 패키지에 이름이 같은 엔티티 클래스가 있다면 이름을 지정해서 충돌하지 않도록 해야함 
설정하지 않으면 클래스의 이름 사용

 

@Entity 적용 시 주의사항

-기본 생성자(파라미터가 없는 public 혹은 protected 생성자)는 필수

-final 클래스,enum,interface,inner 클래스에는 사용할 수 없음

-저장할 필드에 final을 사용하면 안됨

 

JPA가 엔티티 객체를 생성할 때 기본 생성자를 사용하므로 이 생성자는 반드시 있어야함

자바는 생성자가 하나도 없으면 자동으로 기본 생성자를 만들지만, 

임의의 생성자를 하나 이상 만들면 기본 생성자를 자동으로 만들지 않기 때문에 이때, 기본 생성자를 직접 만들어야함

 

 

4.2 @Table

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

속성 기능 기본값
name 매핑할 테이블 이름 엔티티 이름 사용
catalog catalog 기능이 있는 데이터베이스에서 catalog를 매핑  
schema schema 기능이 있는 데이터베이스에서 schema를 매핑  
uniqueConstraints DDL 생성 시에 유니크 제약 조건을 만듬
2개 이상의 복합 유니크 제약조건도 가능
이 기능은 스키마 자동 생성 기능을 사용해서 DDL을 만들때만 사용
(뒤에서 자세히 ..)

 

4.3 다양한 매핑 사용

예시) 회원 관리 프로그램에 요구사항 추가 

1. 회원은 일반 회원과 관리자로 구분해야함

2. 회원 가입일과 수정일이 있어야 함

3. 회원을 설명할 수 있는 필드가 있어야하며 이 필드는 길이 제한이 없음

 

//RoleType.java 파일 생성
public enum RoleType{
	ADMIN, USER//관리자와 일반회원
}

//public class Member에 추가 
@Enumerated(EnumType.STRING)
private RoleType roletype; //1

@Temporal(TemporalType.TIMESTAMP)
private Date createdDate;  //2

@Temporal(TemporalType.TIMESTAMP)
private Date lastModifiedDate;  //2

@Lob
private String description;  //3

 

1. roleType : 자바의 enum을 사용해서 회원의 타입을 구분

일반 회원 - USER / 관리자 - ADMIN  

자바의 enum을 사용하려면 @Enumerated 어노테이션으로 매핑해야 함

2. createdDate, lastModifiedDate : 자바의 날짜 타입은 @Temporal을 사용해서 매핑

3. description : 회원을 설명하는 필드는 길이 제한이 없으므로 데이터베이스의 VARCHAR타입 대신 CLOB타입으로 지정해야함. @Lob을 사용하면 CLOB,BLOB 타입을 매핑할 수 있음

 

 

4.4 데이터베이스 스키마 자동 생성

JPA - 데이터베이스 스키마를 자동으로 생성하는 기능을 제공함 

클래스의 매핑 정보를 보면 어떤 테이블에 어떤 컬럼을 사용하는 지 알 수 있음

JPA는 이 매핑 정보와 데이터베이스 방언을 사용해서 데이터베이스 스키마를 생성 가능

 

persistence.xml에 

<property name="hibernate.hbm2ddl.auto" value="create"/> 을 추가하면

어플리케이션 실행 시점에 데이터베이스 테이블을 자동으로 생성

 

<property name="hibernate.show_sql" value="true" /> 을 추가하면 

콘솔에 실행되는 테이블 생성 DDL(data definition language)을 출력 가능

 

스키마 자동 생성 기능을 사용하면 

어플리케이션 실행 시점에 데이터베이스 테이블이 자동으로 생성되므로 개발자가 테이블을 직접 생성하는 수고를 덜 수 있음 하지만 스키마 자동 생성 기능이 만든 DDL은 운영 환경에서 사용할 만큼 완벽하지 않으므로 개발환경에서 사용하거나 매핑을 어떻게 해야하는지 참고하는 용도로 사용하는 것이 좋음 

객체와 테이블을 매핑하는데에 아직 익숙하지 않다면 데이터베이스 스키마 자동 생성을 적극 활용하여 생성된 DDL을 보고 엔티티와 테이블이 어떻게 매핑되는지 쉽게 이해할 수 있음

 

<hibernate.hbm2ddl.auto의 속성>

옵션  설명  
 create 기존 테이블을 삭제하고 새로 생성한다 DROP + CREATE  
create-drop create 속성에 추가로, 어플리케이션을 종료할때 생성한 DDL을 제거한다 DROP + CREATE + DROP  
update 데이터베이스 테이블과 엔티티 매핑정보를 비교해서 변경 사항만 수정한다  
validate 데이터베이스 테이블과 엔티티 매핑정보를 비교해서 차이가 있으면 경고를 남기고 어플리케이션을 실행하지 않는다. 이 설정은 DDL을 수정하지 않음  
none 자동 생성 기능을 사용하지 않으려면 hibernate hbm2ddl.auto 속성 자체를 삭제하거나 유효하지 않은 옵션값을 주면 됨 (none도 유효하지 않은 옵션 값)  

 

더보기

HBM2DDL 주의 사항

운영 서버에서 create, create-drop, update와 같이 ddl을 수정하는 옵션을 사용하면 안된다. 오직 개발 서버나 개발 단계에서만 사용해야한다. 이 옵션들은 운영중인 데이터베이스의 테이블이나 칼럼을 삭제 할 수 있다.

-개발 초기 : create or update

-초기화 상태로 자동화된 테스트를 진행하는 개발자 환경과 cl 서버 : create or create-drop

-테스트 서버 : update or validate

스테이징과 운영 서버 : validate or none

 

 

4.5 DDL 생성 기능

회원 이름은  필수로 입력되어야하고, 10자를 초과하면 안된다는 제약 조건 추가되었을때,

@Column(name = "NAME", nullable = false , length =10)

-> NAME varchar(10) not null, 

 

유니크 제약 조건이 추가되었을때

@Table (name = "MEMBER", uniqueConstraints = {@UniqueConstraint(..)})

-> ALTER TABLE MEMBER ADD CONSTRAINT .. UNIQUE ..

 

 

 

4.6 기본 키 매핑

@Id 어노테이션을 사용해 회원의 기본키를 어플리케이션에 직접 할당함

-> 데이터베이스가 생성해주는 값을 사용하려면 어떻게 매핑 해야할까

 

데이터베이스마다 기본 키를 생성하는 방식이 다르기 때문에 해결하기 어려움 

JPA가 제공하는 데이터베이스 기본 키 생성 전략은

  • 직접 할당 : 기본 키를 어플리케이션에 직접 할당함 / @Id 어노테이션만 있으면됨
  • 자동 생성 : 대리 키 사용 방식 / @Id + @GeneratedValue 어노테이션을 사용하고 식별자 생성 전략을 선택해야 함

     -  IDENTITY : 기본 키 생성을 데이터베이스에 위임함

     - SEQUENCE : 데이터베이스 시퀀스를 사용해서 기본 키를 할당함

     - TABLE : 키 생성 테이블을 사용함

 

자동 생성 전략을 사용하려면 @Id 에 @GeneratedValue 를 추가하고 원하는 키 생성 전략을 선택

*키 생성 전략을 사용하려면 persistence.xml에 hibernate.id.new_generator_mappings=true 속성을 반드시 추가해야함

 

 

 

4.6.1 기본 키 직접할당 전략

@Id 

@Column (name="id")

private String id;

 

@Id 적용 가능한 자바 타입

- 자바 기본형

- 자바 래퍼Wrapper 형

- String

- java.util.Date

- java.sql.Date

- java.math.BigDecimal

- java.math.BigInteger

 

기본 키 직접 할당 전략은 em.persist()로 엔티티를 저장하기 전에 어플리케이션에서 기본 키를 직접 할당하는 방법

Board board = new Board();

board.setId("id1"); //기본 키 직접할당 

em.persist(board);

 

 

4.6.2 IDENTITY 전략

IDENTITY는 기본 키 생성을 데이터베이스에 위임하는 전략

주로, MySQL / PostgreSQL / SQL server / DB2 에서 사용함

 

MySQL의 AUTO_INCREMENT 기능은 데이터베이스가 기본 키를 자동으로 생성해줌

CREATE TABLE BOARD (
	ID INT NOT NULL AUTO_INCREMENT PRIMARY KEY,
    	DATA VARCHAR(255)
);

INSERT INTO BOARD(DATA) VALUES('A');
INSERT INTO BOARD(DATA) VALUES('B');

테이블을 생성할 때 기본 키 칼럼인 ID 에 AUTO_INCREMENT를 추가함

-> 데이터베이스에 ID 없이 DATA 값만 저장 - 데이터베이스가 순서대로 값을 채워줌 

 

JPA는 기본 키 값을 얻어오기 위해 데이터베이스를 추가로 조회함 

//<IDENTITY 매핑 코드>
@Entity
public class Board {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;
    ..
}
//<IDENTITY 사용 코드>
private static void logic(EntityManager em){
    Board board = new Board();
    em.persist(board);
    System.out.println(board.getId());
}

em.persist()를 호출해서 엔티티를 저장한 직후에 할당된 식별자 값을 출력함

-> 출력된 값은 저장 시점에 데이터베이스가 생성한 값을 JPA가 조회해 반환한것 

더보기

IDENTITY 전략과 최적화 

IDENTITY 전략은 데이터를 데이터베이스에 INSERT 한 후에 기본 키 값 조회 가능 

-> 엔티티에 식별자 값을 할당하려면 JPA는 추가로 데이터베이스를 조회해야함

JDBC3에서 추가된 Statement.getGeneratedKeys()를 사용하면 데이터를 저장하면서 동시에 생성된 기본 키 값도 얻어올 수 있다. 하이버네이트는 이 메소드를 사용해서 데이터베이스와 한번만 통신함

*주의) 엔티티가 영속 상태가 되려면 식별자가 반드시 필요함

but // IDENTITY 식별자 전략은 엔티티를 데이터베이스에 저장해야 식별자를 구할 수 있으므로, em.persist()를 호출하는 즉시 INSERT SQL이 데이터베이스에 전달되고 데이터베이스에서 id값(식별자값)을 할당받아서 persist로 저장하는 것 

=> 트랜잭션을 지원하는 쓰기 지연이 동작 XX

 

 

4.6.3 SEQUENCE 전략

데이터베이스 시퀀스는 유일한 값을 순서대로 생성하는 특별한 데이터베이스 오브젝트

주로, 오라클 / PostgreSQL / DB2 / H2 에서 사용함

 

CREATE TABLE BOARD (
	ID INT NOT NULL PRIMARY KEY,
    	DATA VARCHAR(255)
);

//시퀀스 생성 
CREATE SEQUENCE BOARD_SEQ START WITH 1 INCREMENT BY 1;
//<SEQUENCE 매핑 코드>
@Entity
@SequenceGenerator(//시퀀스 생성기
    name ="BOARD_SEQ_GENERATOR".
    sequenceName = "BOARD_SEQ",//매핑할 데이터베이스 시퀀스 이름
    initialValue =1, allocationSize =1)
public class Board {

    @Id
    @GeneratedValue(strategy = GenerationType.SEQUENCE,
    				generator = "BOARD_SEQ_GENERATOR")
    private Long id;
    ..
}

id 식별자 값은 BOARD_SEQ_GENERATOR 시퀀스 생성기가 할당

//<SEQUENCE 사용 코드>
private static void logic(EntityManager em){
    Board board = new Board();
    em.persist(board);
    System.out.println(board.getId());
}

사용 코드는 IDENTITY와 같지만 내부 동작 방식은 다름

 

SEQUENCE 전략은 em.persist()를 호출할 때 먼저 데이터베이스 시퀀스를 사용해서 식별자를 조회

조회한 식별자를 엔티티에 할당한 후에 엔티티를 영속성 컨텍스트에 저장

이후 트랜잭션을 커밋해서 플러시가 일어나면 엔티티를 데이터베이스에 저장

 

IDENTITY 전략은 먼저 엔티티를 데이터베이스에 저장한 후에 식별자를 조회해서 엔티티의 식별자에 할당함 

 

<@SequenceGenerator의 속성>

속성 기능 기본값
name 식별자 생성기 이름 필수 
sequenceName 데이터베이스에 등록되어 있는 시퀀스 이름 hibernate_sequence
initialValue DDL 생성시에만 사용됨, 시퀀스 DDL을 생성할때 처음 시작하는 수를 지정함 1
allocationSize 시퀀스 한 번 호출에 증가하는 수(성능 최적화에 사용) 50
catalog, schema 데이터베이스 catalog, schema 이름  

 

매핑할 DDL 

create sequence [sequenceName]

start with [initalValue] increment by [allocatioSize]

 

*주의) allocationSize의 기본값이 50 즉, start with 1  increment by 50 이므로 시퀀스를 호출할 때마다 값이 50씩 증가함

데이터베이스 시퀀스 값이 하나씩 증가하도록 설정되어 있으면 이 값을 반드시 1로 지정해야함 

더보기

SEQUENCE 전략과 최적화 

SEQUENCE 전략은 데이터베이스 시퀀스를 통해 식별자를 조회하는 추가 작업이 필요함

->데이터베이스와 2번 통신

1.식별자를 구하려고 데이터베이스 시퀀스를 조회함

2.조회한 시퀀스를 기본 키 값으로 사용해 데이터베이스에 저장함 

 

JPA는 시퀀스에 접근하는 횟수를 줄이기위해 @SequenceGenerator.allocationSize를 사용함

여기에 설정한 값만큼 한 번에 시퀀스 값을 증가시키고 나서 그만큼 메모리에 시퀀스 값을 할당함

시퀀스 값을 선점하므로 여러 JVM이 동시에 동작해도 기본 키 값이 충돌하지 않음

반면, 데이터베이스에 직접 접근해서 데이터를 등록할 때 시퀀스 값이 한번에 많이 증가한다는 점을 염두해야함

(INSERT 성능이 중요하지 않으면 allocationSize를 1로 변경하면 됨)

 

 

4.6.4 TABLE 전략

TABLE 전략은 키 생성 전용 테이블을 하나 만들고

이름과 값으로 사용할 칼럼을 만들어 데이터베이스 시퀀스를 흉내내는 전략

*테이블을 사용하므로 모든 데이터베이스에 적용가능

 

//키 생성 용도로 사용할 테이블 생성
create table MY_SEQUENCE(
    sequence_name varchar(255) not null,
    next_val bigint,
    primary key (sequence_name)
)

sequence_name 칼럼을 시퀀스 이름으로

next_val 칼럼을 시퀀스 값으로 사용 

 

//<TABLE 매핑 코드>
@Entity
@TableGenerator(//테이블 키 생성기
    name ="BOARD_SEQ_GENERATOR",
    table = "MY_SEQUENCES",
    pkColumnValue ="BOARD_SEQ", allocationSize =1)
public class Board {

    @Id
    @GeneratedValue(strategy = GenerationType.TABLE,
    				generator = "BOARD_SEQ_GENERATOR")
    private Long id;
    ..
}

id 식별자 값은 BOARD_SEQ_GENERATOR 테이블 키 생성기가 할당

 

//<TABLE 사용 코드>
private static void logic(EntityManager em){
    Board board = new Board();
    em.persist(board);
    System.out.println(board.getId());
}

TABLE 전략은 시퀀스 대신에 테이블을 사용한다는 것만 제외하면 SEQUENCE 전략과 내부 동작 방식이 같다 

 

<@TableGenerator 의 속성 >

속성 기능  기본값
name 식별자 생성기 이름 필수
table 키 생성 테이블 명 hibernate_sequence
pkColumnName 시퀀스 칼럼 명 sequence_name
valueColumnName 시퀀스 값 칼럼 명 next_val
pkColumnValue 키로 사용할 값 이름 엔티티 이름
initialValue 초기 값, 마지막으로 생성된 값이 기준 0
allocationSize 시퀀스 한번 호출에 증가하는 수  50
catalog, schema 데이터베이스 catalog,schema 이름  
uniqueConstraints(DDL) 유니크 제약 조건 지정가능  

 

 

매핑할 DDL, 테이블명

{pkColumnName}  {valueColumnName}
{pkColumnValue}  {initialValue}

 

더보기

TABLE 전략과 최적화

TABLE전략은 값을 조회하면서 SELECT 쿼리를 사용하고 다음 값으로 증가시키기 위해 UPDATE 쿼리를 사용함

이 전략은 SEQUENCE 전략과 비교해 데이터베이스와 한 번 더 통신하는 단점

-> 최적화하려면 @TableGenerator.allocationSize사용

 

4.6.5 AUTO 전략

AUTO는 데이터베이스 방언에 따라 IDENTITY, SEQUENCE, TABLE전략 중 하나를 자동으로 선택함

@GeneratedValue(strategy = GenerationType.AUTO) / @GeneratedValue *기본값이 auto임

 

AUTO 전략의 장점 - 데이터베이스를 변경해도 코드 수정 필요 XX

AUTO 사용시 SEQUENCE 나 TABLE전략이 선택되면 시퀀스나 키 생성용 테이블을 미리 만들어두어야 함

만약 스키마 자동 생성 기능 사용시, 하이버네이트가 기본값을 사용해 적절한 시퀀스 혹은 키 생성용 테이블 만들어줌

 

 

4.6.6 기본 키 매핑 정리

영속성 컨텍스트는 엔티티를 식별자 값으로 구분하므로 엔티티를 영속 상태로 만들려면 식별자 값이 반드시 존재해야함

em.persist를 호출한 직후에 발생하는 일을 식별자 할당 전략별로 정리

  • 직접 할당 :  em.persist()를 호출하기 전에 어플리케이션에서 직접 식별자 값을 할당해야함. 만약 식별자 값이 없으면 에러 발생
  • SEQUENCE : 데이터베이스 시퀀스에서 식별자 값을 획득한 후 영속성 컨텍스트에 저장
  • TABLE : 데이터베이스 시퀀스 생성용 테이블에서 식별자 값을 획득한 후 영속성 컨텍스트에 저장
  • IDENTITY : 데이터베이스에 엔티티를 저장해서 식별자 값을 획득한 후 영속성 컨텍스트에 저장

 

더보기

권장하는 식별자 선택 전략

데이터베이스 기본 키의 만족 조건

1. NULL값 허용 안함

2.유일해야함

3.변해선 안됨

 

테이블의 기본 키를 선택하는 전략 2가지

  • 자연 키 natural key : 비즈니스에 의미가 있는 키 / 예) 주민등록번호, 이메일 ..
  • 대리 키 surrogate key : 비즈니스와 관련 없이 임의로 만들어진 키(대체 키) / 예) 오라클 시퀀스, auto_increment

자연 키보다는 대리 키를 권장

 

4.7 필드와 칼럼 매핑 : 래퍼런스

JPA가 제공하는 필드와 칼럼 매핑용 어노케이션들 - 직접 사용시 찾아볼것 

분류 

-필드와 칼럼 매핑

  • @Column : 칼럼을 매핑함
  • @Enumerated : 자바의 enum 타입을 매핑함
  • @Temporal : 날짜 타입을 매핑함
  • @Lob : BLOB, CLOB 타입을 매핑함
  • @Transient : 특정 필드를 데이터베이스에 매핑하지 않음

- 기타

  •  @Access : JPA가 엔티티에 접근하는 방식을 지정함

 

4.7.1 @Column

객페 필드를 테이블 칼럼에 매핑함 

name 고 nullable이 주로 사용되고 나머지는 잘 사용되지 않음

 

속성 기능 기본값
name 객체 필드와 매핑할 테이블의 칼럼 이름  객체의 필드 이름
insertable 거의 사용x 엔티티 저장 시 이 필드도 같이 저장함 
false로 설정(읽기 전용일때)
-> 이 필드는 데이터베이스에 저장 안함
true
updatable 거의 사용x 엔티티 수정 시 이 필드도 같이 수정함
false로 설정(읽기 전용일때)
-> 이 필드는 데이터베이스에 저장 안함
true
table 거의 사용x 하나의 엔티티를 두 개 이상의 테이블에 매핑할때 사용, 지정한 필드를 다른 테이블에 매핑할 수 있음 현재 클래스가 매핑된 테이블
     
     

 

4.7.2 @Enumerated

4.7.3 @Temporal

4.7.4 @Lob

4.7.5 @Transient

4.7.6 @Access

 

4.8 정리

객체와 테이블 매핑, 기본 키 매핑, 필드와 칼럼 매핑에 대해 알아봄 

데이터 베이스 스키마 자동 생성하기 기능 - 엔티티 객체를 먼저 만들고 테이블을 자동 생성할 수 있음