[JPA] 4장 엔티티 매핑
자바 ORM 표준 JPA 프로그래밍
김영한
엔티티 매핑
목차
- @Entity
- @Table
- 다양한 매핑 사용
- 데이터베이스 스키마 자동 생성
- DDL 생성 기능
- 기본키 매핑
- 필드와 컬럼 매핑 : 레퍼런스
- 정리
실전 예제 요구사항 분석과 기본 매핑
✅ @Entity
JPA를 사용해 테이블과 매핑할 클래스에 필수적으로 붙히는 어노테이션
@Entity를 붙히면 JPA가 관리하게 된다.
- 기본 생성자가 필수적이다.
- JPA는 엔티티 객체를 생성할 때 기본 생성자를 사용한다.
- 생성자가 하나도 없는 경우는 에러가 안나지만(자바가 자동으로 만들어서),
- 어떤 생성자를 하나 이상 만들게 되면 직접 기본 생성자를 만들어줘야한다.
- final, enum, interface, inner 클래스에는 사용불가
- 저장할 필드에 final을 사용하면 안됨
https://stackoverflow.com/questions/3472438/why-cant-entity-class-in-jpa-be-final
✅ @Table
엔티티와 매핑할 테이블 지정
생략시 엔티티 이름을 테이블 이름으로 사용함.
유니크 제약조건을 만들어주는 속성인 uniqueConstraints만 따로 알고 가면 될것 같다.
catalog와 schema는 아직 입문자로서 알아야하나 싶다.
✅ 데이터베이스 스키마 자동 생성
JPA는 데이터베이스 스키마를 자동으로 생성하는 기능을 지원한다.
클래스의 매핑 정보를 통해 어떤 테이블이 어떤 컬럼을 사용하는지 알수 있다.
application.yml
현재 진행중인 프로젝트에서 사용하는 속성인데
ddl-auto:create = 어플리케이션 실행 시점에 DB table을 자동으로 생성한다.
show-sql : true = 콘솔에 실행되는 DDL (데이터 정의어)를 출력할 수 있다.
jpa:
hibernate:
ddl-auto: create
properties:
hibernate:
format_sql: true
show-sql: true
open-in-view: false
스키마 자동생성으로 만들어진 DDL은 운영환경에서 사용할만큼 완벽하지는 않다
따라서 개발환경에서 사용하거나, 매핑을 어떻게 해야하는지 참고하는 정도로만 사용하는것이 좋다.
> 객체, 테이블 매핑이 익숙하지 않을 경우 이기능을 적극활용하면 된다
생성된 DDL을 보고 엔티티, 테이블이 어떻게 매핑되는지 이해하기 쉽다.
⏺ ddl-auto의 속성
옵션 | 설명 |
create | 기존 테이블을 삭제하고 새로 생성함 (Drop + Create) |
create-drop | create 속성에 추가로, 어플리케이션 종료할 떄 생성한 DDL을 제거 ( Drop + Create + Drop ) |
update | DB 테이블과 엔티티 매핑정보를 비교해서 변경사항만 수정함 |
validate | DB 테이블과 엔티티 매핑정보를 비교해서 차이가 있으면 경고를 남기고 어플리케이션 실행을 안함. (이 설정은 DDL을 수정하지 않음) |
none | 자동생성 기능을 사용하지 않기 위해 속성자체를 안주거나, 유효하지 않은 옵션값(none)을 주면 된다. |
* 운영서버에선 create, create-drop, update와 같이 DDL 수정 옵션은 사용하면 안된다.
이 옵션들은 운영중인 DB의 테이블이나 컬럼이 삭제될 수 있기 때문이다.
- 개발 초기 단계 - create / update
- 초기화 상태로 자동화된 테스트 진행하는 개발자 환경, CI 서버 - create / create-drop
- 테스트서버 - update / validate
- 스테이징과 운영서버 - validate / none
✅ Unique 제약조건
Unique 제약조건을 추가하면, PK가 아닌 특정 열에 중복 값이 입력되지 않도록 할 수 있다.
✔️ 한개 컬럼에 UNIQUE 설정
@Column(name="column" , unique=true)
long column
✔️ 두개 컬럼을 묶어서 UNIQUE를 설정하면
@Table의 uniqueConstraints 사용
@Entity
@Table(
name="tableName",
uniqueConstraints={
@UniqueConstraint(
name={"contstraintName"}
columnNames={"col1", "col2"}
)
}
)
@Data
public class Entity{
@Column(name="col1")
int col1;
@Column(name="col2")
int col2;
}
@Table의 uniqueConstraints 속성은 추가시 DDL 자동 생성할 떄만 사용되고,
JPA 실행 로직에는 영향을 주지 않는다.
자동생성기능을 사용하지 않고, 직접 DDL을 만든다면 @Table의 uniqueConstraints를 사용할 이유가 없다
✅ 기본키 매핑
영속성 컨텍스트는 Entity를 식별자 값으로 구분하기 때문에
엔티티를 영속 상태로 만들기 위해 식별자 값이 반드시 필요하다.
@GeneratedValue
기본키 생성 전략을 결정한다.
- 직접 할당 : 기본키를 Application에서 직접 할당한다.
- @Id 를 사용해서, 엔티티 저장 전에 application에서 직접 할당 setId("id1")
- 자동 생성 : 대리키 사용 방식이다.
- 데이터베이스 벤더마다 기본키를 생성하는 방식이 달라서 자동생성전략이 여러가지 존재한다.
- 오라클은 시퀀스를 제공한다.
- MySQL은 AUTO_INCREMENT 기능을 제공한다.
- IDENTITY 전략 - 기본키 생성을 DB에 위임한다.
- 데이터를 DB에 INSERT한 이후에 기본키값을 조회할 수 있다.
- 엔티티가 영속상태가 되려면 식별자가 반드시 필요하기 때문에 IDENTITY 전략을 사용할 경우, em.persist()가 호출되는 즉시 INSERT SQL이 DB로 전달되고, <트랜잭션을 지원하는 쓰기지연>은 동작하지 않는다.
- MySQL, PostgreSQL, SQL Server, DB2
- SEQUENCE 전략 - Database 시퀀스를 사용해서 기본키를 할당한다
- @SequenceGenerator 를 붙혀서 시퀀스 생성기 등록, sequence
- 식별자값을 획득한 후 영속성 컨텍스트에 저장한다.
- em.persist() 호출시 먼저 DB 시퀀스를 사용해서 식별자를 조회하고, 조회한 식별자를 엔티티에 할당한 후에 영속성 컨텍스트에 저장한다.
이후 트랜잭션 commit해서 flush가 일어나면 엔티티를 DB에 저장한다. - 오라클, PostgreSQL, DB2, H2
- TABLE : 키 생성 테이블을 사용
- 키생성 전용 테이블을 하나 만들고, 이름과 값으로 사용할 컬럼을 만들어 시퀀스를 흉내내는 전략임
- 테이블을 사용하므로 모든 데이터베이스에 적용 가능하다.
- AUTO : 선택한 DB 방언에 따라 방식을 자동으로 선택해준다 (default)
- 오라클 선택시 SEQUENCE, MySQL선택시 IDENTITY 사용됨
- 디비를 변경해도 코드를 수정할 필요가 없는게 장점이다.
- TABLE, SEQUENCE 전략 선택시를 대비해 미리 시퀀스나, 테이블을 만들어 놓아야한다.
- IDENTITY 전략 - 기본키 생성을 DB에 위임한다.
✔️ @SequenceGenerator
* allocationSize의 defualt가 50인데, 시퀀스를 호출할떄마다 값이 50씩 증가한다.
데이터베이스 시퀀스 값이 하나씩 증가하도록 설정되어 있다면 이 값을 반드시 1로 설정해야한다!!!
✅ 식별자 선택 전략
DB에서 사용할 기본키는 다음 3가지 조건을 모두 만족해야함
1. not null
2. unique
3. 변하면 안됨
테이블의 기본키를 선택하는 전략은 2가지가 있는데
자연키 (natural key) : 비지니스 적으로 의미가 있는 키
>> 주민등록번호, 이메일, 전화번호
대리키 (surrogate key) : 비지니스와 관련없는 임의 키 (대체 키)
>> 오라클 시퀀스, auto_increment, 키생성 테이블 사용
* 자연키보다는 대리키를 권장함
(만약 전화번호를 기본키로 선택하면, 번호가 유일할수는 있다. 그러나 번호가 없는 경우도 존재하며, 번호가 변경될 수도 있기 때문에 기본키로 적당하지 않다. )
(주민번호는 기본키의 3가지 조건을 모두 만족하는 것 같지만, 언젠가 주민번호도 여러 이유로 변경될 수 있기때문에 적절하지 않다.)
* 비지니스 환경은 언젠가 변한다는것을 인지하자.
회원테이블에 주민등록번호가 기본키였던 적이 있었고, 그걸 외래키로 가져서 조인이 되어있었다.
어느날 정부 정책이 변하면서 주민등록번호를 저장할 수 없게 되었고, 엄청난 로직의 수정이 일어났다.
처음 설계했을 떄부터 대리키를 사용했다면 수정을 안해도 되었을텐데..!
* JPA는 모든 엔티티에 일관된 방식으로 대리키 사용을 권장함
(비지니스 외부적인 풍파에 쉽게 흔들리지 않는 대리키가 일반적으로 좋은 선택이다)
* 기본키는 변하면 안되기 떄문에 setId()와 같이 식별자를 수정하는 메서드는 외부에 공개하지 않는것도 문제를 예방하는 하나의 방법이다.
✅ 필드와 컬럼 매핑 : 레퍼런스
@Column
필드를 테이블의 컬럼으로 매핑한다.
속성 | 설명 | defualt |
name | 필드와 매핑할 테이블 컬럼 이름 | 객체의 필드 이름 |
(DDL) nullable | null 허용여부, false = not null (안전) | true |
(DDL) unique | 한 컬럼에만 유니크 제약 조건 설정 | |
(DDL) columnDefinition | 컬럼정보를 직접 설정 | 필드의 자바타입, 방언정보를 사용해서 적절한 컬럼타입 생성함. |
(DDL) Length | 문자 길이 제약조건 (String 에만 사용한다) | 255 |
(DDL) percision , scale | BigDemical, BigInteger에서 사용, 아주 큰 숫자 정밀한 소수를 다룰 때 사용한다. percision 는 소수점을 포함한 전체 자릿수, scale은 소수의 자릿수이다. (double, float에 적용 x) |
percision = 19 , scale = 2 |
@Column(nullable = false)
private String data;
@Column(unique = true)
private String data;
@Column(columnDefinition = "varchar(100) default 'EMPTY'")
private String data;
@Column(length = 400)
private String data;
@Column(precision = 10, scale = 2)
private BigDecimal data;
@Column을 생략할 경우 대부분 기본값이 사용된다.
그러나 int data; 와 같이 자바 기본타입에는 null이 입력할 수 없다.
따라서 JPA가 기본타입에는 NOTNULL제약조건을 추가해주고, 객체타입의 경우엔 NOT NULL을 설정하지 않는다.
@Enumerated
자바의 enum 타입 매핑시 사용
EnumType.ORIDINAL : enum에 정의된 순서대로 데이터베이스에 저장 됨 (순서가 바뀌는 경우 안전하지 않다)
EnumType.STRING : enum 이름 그대로 데이터베이스에 저장됨 > ORDINAL에 비해 데이터 크기가 더 크게 저장되지만 안전하다.
* defualt가 ORDINAL인데 STRING으로 꼭 바꿔주자
@Temporal
날짜타입 매핑시 사용 (java.util.Date, java.util.Calendar)
자바의 Date엔 년월일시분초가 존재하지만, DB에는 date, time, timestamp가 별도로 존재해서
@Temporal 생략시 Date와 가장 유사한 timestamp/datetime로 정의된다.
mySQL : datetime
H2, Oracle, PostgreSQL : timestamp
- @CreationTimestamp : INSERT 시 현재시간을 저장
- @UpdateTimestamp : UPDATE 시 현재시간을 저장
@Lob
- 데이터베이스 BLOB, CLOB 타입과 매핑
- 매핑하는 필드 타입이 문자면 CLOB을 매핑하고 나머지는 BLOB을 매핑한다.
- CLOB : String, char[]
- BLOB : byte[]
@Lob
private String lobString;
@Lob
private byte[] lobByte;
@Transient
- 필드에 매핑되지 않음
- DB에 저장, 조회 아무것도 안함
- 객체에 임시로 어떤 값을 보관하고 싶을 떄 사용
@Access
- JPA가 엔티티 데이터에 접근하는 방식을 지정
- 필드 접근 : AccessType.FIELD 로 지정
- 필드에 직접 접근 (private도 접근 가능)
- 프로퍼티 접근 : AccessType.PROPERTY 로 지정
- 접근자 Getter를 사용해 접근한다.
@Id가 필드에 있으므로 FIELD로 설정한것과 같다, -- @Access 생략해도 된다.
@Entity
@Access(AccessType.FIELD)
public class Member {
@Id // id가 필드에 있으므로 @Access(AccessType.FIELD) 생략 가능
private String id;
}
@Id가 프로퍼티에 있기 떄문에 PROPERTY로 설정한것과 같다 -- @Access 생략해도 된다.
@Entity
@Access(AccessType.PROPERTY)
public class Member {
private String id;
@Id // id가 프로퍼티에 있으므로 @Access(AccessType.PROPERTY)와 같다
public String getId() {
return id;
}
}
@Id를 필드에 넣어 필드 접근방식을 사용하고, getFullName()메서드만 프로퍼티 접근방식을 사용해도된다.
@Entity
public class Member {
@Id // 기본은 필드 접근 방식
private String id;
@Transient
private String firstName;
@Transient
private String lastName;
@Access(AccessType.PROPERTY) // 이 메소드만 프로퍼티 접근 방식
public String getFullName {
return firstName + lastName;
}
}
회원 엔티티를 저장하면 회원 테이블의 FULLNAME 컬럼에 firstName + lastName의 결과가 저장된다
나중에 볼것
출처
자바 ORM 표준 JPA 프로그래밍 - 김영한,
https://dev-coco.tistory.com/75
https://kudolove.tistory.com/1340