요약
- Entity LifeCycle
- Entity Mapping
1. Entity LifeCycle (생명주기)
1.1 종류
- 비영속 : Persistence Context와 전혀 관계 없는 상태
- 영속 : Persistence Context에 저장된 상태
- 준영속 : Persistence Context에 저장되었다가 분리된 상태
- 삭제 : 실제 DB 삭제를 요청한 상태
1.2 영속과 준영속
- 영속상태
- entityManager.persist(); 로 Persistence Context에 저장된 상태
- entityManager.find(); 로 조회할 때 Persistence Context 1차 cash에 없어서 DB에서 조회한 후 해당 Entity를 1차 캐시에 올린 상태(Entity Manager 가 관리하는 상태)
- 준영속 상태
- 영속 상태의 엔티티가 영속성 컨텍스트에서 분리(detached)된 상태
- 준영속 상태에서는 Persistence Context가 제공하는 기능을 못 씀(Dirty Checking, Query)
- EX)
Member member = entityManager.find(Member.class, 150L);
member.setName("AAAAA");
transaction.commit();
- entityManager.find() : 1차 캐시에 없으므로 DB에서 조회한 Entity를 1차 캐시에 넣음(영속)
- Dirty Checking (변경감지)
- 이름 변경에 대한 Entity 데이터 변화를 감지 (Snapshot)
- 1차 캐시의 Entity와 Snapshot의 차이를 감지하고 Update Query를 날림
1.3 준영속 상태로 만드는 법(영속 -> 준영속)
- entityManager.detach(entity) : 특정 Entity만 준영속 상태로 전환
- entityManager.clear() : Persistence Context 를 완전 초기화
- entityManager.close() : Persistence Context 를 종료
Detach
Member findMember = entityManager.find(Member.class, 150L);
findMember.setName("AAAAA");
entityManager.detach(findMember);
tx.commit();
- entityManager.detach(); : 해당 Entity를 Persistence Context에서 분리(JPA가 관리하지 않는 객체 상태)
- Transaction Commit 에서 아무 일도 발생하지 않음
- Entity가 변경되었는데 실제로 UPDATE Query 가 나가지 않음
- 직접 쓸 일이 거의 없음
Clear
Member findMember = entityManager.find(Member.class, 150L);
findMember.setName("AAAAA");
entityManager.clear();
Member findMember2 = entityManager.find(Member.class, 150L);
tx.commit();
- entityManager.clear(); " EntityManager 안에 있는 Persistence Context를 모두 지움(초기화)
= clear 후 Entity를 조회할 때 SELECT Query 가 두 번 발생
- clear는 1차 캐시에 상관없이 쿼리를 확인하고 싶을 때 즉, testcase 작성 시에 도움이 된다.
Close
- Persistence Context 종료
- JPA의 관리 대상이 아니게 됨
2. Entity Mapping
JPA 핵심
- Persistence Context, JPA 내부 동작 매커니즘
- 객체와 RDB 매핑
2.1 Mapping 종류
- 객체와 테이블 매핑 ( @Entity / @Table )
- 필드와 컬럼 매핑 ( @Column )
- 기본키 매핑 ( @ID )
- 연관관계 매핑 ( @ManyToOne / @JoinColumn )
2.2 객체와 테이블 매핑
@Entity
- @Entity 가 붙은 클래스는 JPA가 관리하는 클래스로, 해당 클래스를 Entity라 함 (필수)
- 기본 생성자가 필요하다.
- final 클래스, enum, interface, inner 클래스는 Entity로 사용 불가
- DB에 저장하고 싶은 필드에는 final 사용 불가
- Entity 속성
- @Entity(name = "Member") - JPA레서 사용할 Entity 이름 지정
- 기본값 : 클래스 이름 그대로 사용
@Table
- @Table은 Entity와 Mapping할 Table 지정
- Table 속성 [ @Table(name = "MBR") ]
- name : 매핑할 테이블 이름 (기본값으로 Entity 이름 사용)
- catalog : DB catalog 매핑
- schema : DB schema 매핑
- uniqueConstraints(DDL) : DDL 생성시 유니크 제약 조건 생성
2.3 DB와 Schema 자동 생성
2.3.1 체크사항
-
application 로딩 시점에 DDL을 자동으로 생성
-
테이블 중심 -> 객체 중심
-
이터베이스 방언을 활용해서 데이터베이스에 맞는 적절한 DDL을 생성한다.
Ex) oracle: varchar2, MySQL: varchar 등
-
생성된 DDL은 로컬에서만 사용 ... ?
<property name="hibernate.hbm2ddl.auto" value="create"/>
-
속성 값(value)
옵션 | 설명 |
---|
create | 기존 테이블 삭제 후 다시 생성(DROP + CREATE) |
create-drop | create와 같으나 종료 시점에 테이블 DROP(Test에서 자주 사용) |
update | 변경 분만 반영(운영 DB에서는 사용하면 안됨) alter table ~ |
validate | 엔티티와 테이블이 정상 매핑되었는지만 확인 |
none | 속성 사용 X |
[주의]
-
운영 장비에는 절대 create-drop, update 를 사용하면 안된다.
-
개발 초기 단계(로컬 개발 서버) - create / update
-
테스트 서버 - update / validate / none
= 데이터가 많은 상태에소 alter 했을 때 시스템 중단될 수도 있다함 -> DBA에게 검수 받고 적용 추천!
-
스테이징과 운영서버 - validate / none
2.3.2 DDL 생성 기능
-
제약 조건 추가 기능
- @Column(nullable = false, length = 10)
= 회원 이름 필수 / 10글자 초과 x
- @Column(unique = true)
= 해당 필드는 unique
- @Table(uniqueConstraints = {@UniqueConstraint(name = "NAME_AGE_UNIQUE",
columnNames = {"NAME", "AGE"})})
= unique 제약 조건 추가
-
DDL 생성 기능은 DDL을 자동 생성할 때만 사용되고 JPA 실행 로직에는 영향을 주지 않는다.
- 즉, DB에만 영향을 주는 것이지 애플리케이션에 영향을 주는 것이 아니다.
- JPA의 실행 매커니즘에 영향을 주는 것이 아니라 alter table과 같은 스크립트가 실행되는 것을 말한다.
Cf) insert, update는 Runtime에 영향을 준다.
2.4 필드와 컬럼 매핑
2.4.1 요구사항 추가
- 회원은 일반 회원과 관리자로 구분
- 회원 가입일과 수정일이 있어야 함
- 회원을 설명할 수 있는 필드가 있어야 함
public enum RoleType {
USER, ADMIN
}
@Entity
@Table(name="MEMBER", uniqueConstraints = {@UniqueConstraint(
name = "NAME_AGE_UNIQUE",
columnNames = {"NAME", "AGE"} )})
public class Member {
@Id
private Long id;
@Column(name = "NAME", nullable = false, length = 10) private String username;
private Integer age;
@Enumerated(EnumType.String)
private RoleType roleType;
@Temporal(TemporalType.TIMESTAMP)
private Date createDate;
@Temporal(TemporalType.TIMESTAMP)
private Date lastModifiedDate;
@Lob
private String description;
@Transient
private int temp;
public Member() {
}
}
create table Member (
id bigint not null,
age integer,
createdDate timestamp,
description clob,
lastModifiedDate timestamp,
roleType varchar(255),
name varchar(255),
primary key (id)
)
@Id
@Column
- 컬럼 매핑
- @Column 속성
- name [ @Column(name = "name") ] : 객체명과 DB컬럼을 다르게 하고싶은 경우 name에 정의
- insertable
- updatable
- 컬럼 수정했을 때 DB에 추가할 것인지 여부
- update 문이 나갈 때 해당 컬럼을 반영할 것인지 여부
- @Column(updatable = false) : 변경이 되어도 DB에 반영하지 않는다.
- nullable
- @Column(nullable = false) : NOT NULL 제약 조건
- unique
- columnDefinition
- @Column(columnDefinition = "varchar(100) default 'EMPTY'") : 컬럼 정보 직접 입력
- length
- String 타입에만 사용하는 문자 길이 제약 조건
- precision
- 숫자가 엄청 큰 BigDecimal 의 경우 (EX - private BigDecimal age;)
@Enumerated
-
Enum Type 매핑
- Enum 객체 사용 시 해당 annotation을 사용
- DB에는 Enum Type 이 없음
-
EnumType.String
@Target({ElementType.METHOD, ElementType.FIELD})
@Retention(RetentionPolicy.RUNTIME)
public @interface Enumerated {
EnumType value() default EnumType.ORDINAL;
}
public enum EnumType {
ORDINAL, STRING;
private EnumType() { }
}
@Temporal
- 날짜 Type (java.util.Date, java.util.Calendar) 매핑
- 날짜 타입 사용 시 해당 annotation을 사용
- java의 Date에는 날짜와 시간이 모두 존재한다. 그러나 DB의 Date에는 아래의 세 가지로 구분해서 사용한다
- TemporalType enum class에는 세 가지가 존재한다.
- DATE: 날짜, DB의 date 타입과 매핑
Ex. 2019-08-13
- TIME: 시간, DB의 time 타입과 매핑
Ex. 14:20:38
- TIMESTAMP: 날짜와 시간, DB의 timestamp 타입과 매핑
Ex. 2019-08-13 14:20:38
@Lob
- DB에서 varchar를 넘어서는 큰 내용을 넣고 싶은 경우에 해당 annotation을 사용
- @Lob에는 지정할 수 있는 속성이 없다.
- 매핑하는 필드의 타입에 따라 DB의 BLOB, CLOB과 매핑된다.
- 필드의 타입이 문자열: CLOB
- String, char[], java.sql.CLOB
- 그 외 나머지: BLOB
@Transient
- 특정 필드를 컬럼에 매핑하지 않음.
- DB에 관계없이 메모리에서만 사용하고자 하는 객체에 해당 annotation을 사용
- 즉, 주로 메모리상에서만 임시로 어떤 값을 보관하고 싶을 때 사용한다.
- 해당 annotation이 붙은 필드는 DB에 저장, 조회가 되지 않는다.
참조
https://gmlwjd9405.github.io/2019/08/11/entity-mapping.html