자바 ORM 표준 JPA 프로그래밍 섹션4. 엔티티 매핑
매핑 어노테이션
1. 객체와 테이블을 매핑하는 어노테이션 : @Entity, @Table
2. 필드와 컬럼을 매핑하는 어노테이션 : @Column
3. 기본키를 매핑하는 어노테이션 : @Id
4. 연관관계를 매핑하는 어노테이션 : @ManyToOne, @JoinColumn
객체와 테이블 매핑
@Entity
@Entity가 달린 클래스는 JPA가 관리한다. 따라서 JPA를 사용하여 테이블과 매핑할 클래스는 @Entity를 필수적으로 사용해야 한다. 이때 주의점은 파라미터가 없는 public 또는 protected 기본 생성자는 필수로 있어야 한다. final, enum, interface, inner 클래스는 @Entity를 붙여 사용할 수 없다. 또한 DB에 저장하고 싶은 필드에는 final 사용을 하면 안된다.
@Table
@Table은 엔티티와 매핑할 테이블을 지정해주는 어노테이션이다. 이때 속성들은 아래와 같다.
속성 | 기능 | 기본값 |
name | 매핑할 테이블 이름 | 엔티티 이름을 사용 |
catalog | 데이터베이스 catalog 매핑 | |
schema | 데이터베이스 schema 매핑 | |
uniqueConstraints(DDL) | DDL 생성 시에 유니크 제약 조건 생성 |
데이터베이스 스키마 자동 생성
JPA는 어플리케이션 실행/로딩 시점에 CREATE문으로 DB 테이블을 생성하는 기능도 지원한다. 이는 DDL 속성을 통해 실행할 수 있으며 JPA는 DDL을 어플리케이션 실행 시점에 자동으로 생성해준다. JPA는 DB 방언을 활용하여 DB에 맞는 적절한 DDL을 생성해준다. 이렇게 생성된 DDL은 개발 장비에서만 사용된다. 운영에서 사용하면 안된다.
이 기능은 아래와 같이 persistence.xml 파일에 선언해줄 수 있으며 value 값에 따라 동작하는 방식이 다르다.
<property name="hibernate.hbm2ddl.auto" value="" />
옵션 | 설명 |
create | 기존테이블 삭제 후 다시 생성 (DROP + CREATE) |
create-drop | create와 같으나 종료시점에 테이블 DROP |
update | 변경분만 반영 (운영DB에서는 사용X, 추가만 가능, 삭제는 불가능) |
validate | 엔티티와 테이블이 정상 매핑되었는지만 확인 |
none | 사용하지 않음 |
주의점은 운영 장비에는 절대 create, create-drop, update를 사용하면 안된다는 것이다. 주로 개발 초기 단계에는 create 또는 update를, 테스트 서버는 update, validate를, 스테이징과 운영 서버는 validate, none을 사용한다. 통틀어 DB에 직접 script를 짜 적용하는 것이 가장 베스트이다.
DDL 생성 기능에는 조건을 추가할 수 있는 것이 있는데 아래 2가지와 같다.
1. 제약조건 추가 : na,e nullable, length 등을 지정할 수 있다.
@Column(nullable=false, length=10)
2.유니크 제약조건 추가
@Table(uniqueConstraints = {@UniqueConstraint( name = "NAME_AGE_UNIQUE", columnNames = {"NAME", "AGE"} )})
위 제약조건은 JPA의 실행 메커니즘에 영향을 주지는 않고 DB에 영향을 주는 것이다. 이 기능은 DDL을 자동 생성할 때만 사용된다.
필드와 컬럼 매핑
매핑 어노테이션 정리
어노테이션 | 설명 |
@Column | 컬럼 매핑 |
@Temporal | 날짜 타입 매핑 |
@Enumerated | enum 타입 매핑 |
@lob | BLOB, 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 |
percision, scale(DDL) | BigDecimal 타입에서 사용한다. (BigInteger도 사용할 수 있다.) precision은 소수점을 포함한 전체 자릿수이고, scale은 소수의 자릿수이다. 참고로 double, float 타입에는 적용되지 않는다. 아주 큰 숫자나 정밀한 소수를 다루어야 할 때만 사용한다. | percision=19, sacle=2 |
이때 unique는 많이 사용하지 않는다. unique 제약조건의 이름이 랜덤으로 지정되어 이후 exception 발생 시 알아보기 어렵기 때문이다.
@Enumerated
자바 enum 타입을 매핑할 때 사용하는 어노테이션이다. value에는 두 가지가 있는데 이때 주의점은 ORDINAL을 사용하지 말아야 한다는 것이다. ORDINAL은 숫자로 들어가기 때문에 Integer 타입으로 DB에 들어간다. 그렇다면 예를 들어 Member의 RoleType을 관리자or사용자로 지정한 enum이 DB에 저장되지 않기 때문이다. 숫자 0,1 등으로 저장되며 이는 enum 선언 순서가 바뀌면 이전과 다른 값으로 저장이 된다. 따라서 필수적으로 EnumType은 STRING을 사용하여야 한다.
속성 | 설명 | 기본값 |
value | EnumType.ORDINAL : enum 순서를 DB에 저장 EnumType.STRING : enum 이름을 DB에 저장 |
EnumType.ORDINAL |
@Temporal
날짜 타입(java.util.Date, java.util.Calendar)을 매핑할 때 사용된다.
속성 | 설명 | 기본값 |
value | TemporalType.DATE: 날짜, 데이터베이스 date 타입과 매핑(예: 2013–10–11) TemporalType.TIME: 시간, 데이터베이스 time 타입과 매핑 (예: 11:11:11) TemporalType.TIMESTAMP: 날짜와 시간, 데이터베이스 timestamp 타입과 매핑 (예: 2013–10–11 11:11:11) |
최신 하이버네이트에서는 LocalDate, LocalDateTime을 사용할 때에는 생략이 가능하다. LocalDate는 연-월 정보까지 있고 LocalDateTime은 연-월-일이 모두 포함되어 있다.
@Lob
DB의 BLOB, CLOB 타입과 매핑되는 어노테이션이며 따로 지정할 수 있는 속성으 넝ㅄ다.
매핑하는 필드 타입이 문자이면 CLOB 매핑을, 나머지는 BLOB 매핑을 사용한다.
1. CLOB : String, char[], java.sql.CLOB
2. BLOB : byte[], java.sql.BLOB
@Transient
이 어노테이션은 필드 매핑을 하는 어노테이션이 아니다. 따라서 DB에 저장하거나 조회하지 않는다. 주로 메모리상에서 임시로 어떤 값을 보관하고 싶을 때 사용한다.
기본키 매핑
기본키 매핑 방법
1. 직접 할당하기 : @Id 을 사용한다.
2. 자동 생성/할당하기 : @GeneratedValue 사용 후 strategy를 설정한다.
@GeneratedValue(strategy = GenerationType.SEQUENCE)
strategy 종류는 아래와 같다.
(1) IDENTITY : 기본키 생성을 DB에 위임하는 것으로 MYSQL에서 사용한다.
(2) SEQUENCE : DB 시퀀스 오브젝트 사용하는 것으로 ORACLE에서 사용한다.
(3) TABLE : 키 생성용 테이블 사용하는 것으로 모든 DB에서 사용한다. @TableGenerator가 필요하다.
(4) AUTO : 방언에 따라 자동 지정되고 AUTO가 strategy의 기본값이다.
IDNETITY 전략
기본키 생성을 DB에 위임하는 것으로 내가 기본키 값을 넣으면 안된다. 주로 MySQL, PostgreSQL, SQL Server, DB2에서 사용된다. JPA는 보통 트랜잭션 커밋 시점에 INSERT SQL을 실행한다. 하지만 IDENTITY 전략은 em.persist(member); 에 INSERT 쿼리가 날라간다. 따라서 persist 이후에는 ID 값을 바로 조회할 수 있다. 예를 들어 MySQL의 AUTO_INCREMENT는 DB에 INSERT SQL을 실행한 이후에 ID 값을 알 수 있다. 즉, IDENTITY 전략은 em.persist() 시점에 즉시 INSERT SQL을 실행하고 DB에서 식별자를 조회한다.
@Entity
public class Member {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
SEQUENCE 전략
DB SEQUENCE는 유일한 값을 순서대로 생성하는 특별한 DB 오브젝트이다. 주로 오라클, PostgreSQL, DB2, H2 DB에서 사용한다. SEQUENCE 전략 사용시 commit 때 모아 쿼리를 날리는 버퍼링 기능이 가능하다.
@SequenceGenerator를 사용하여 매핑하면 table마다 Sequence를 따로 관리할 수 있다. 아래와 같이 사용할 수 있다.
@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;
속성 | 설명 | 기본값 |
name | 식별자 생성기 이름 | 필수 |
sequenceName | DB에 등록되어 있는 시퀀스 이름 | hibernate_sequence |
initialValue | DDL 생성 시에만 사용된다. 시퀀스 DDL을 생성할 때 처음 1 시작하는 수를 지정한다. |
1 |
allocationSize | 시퀀스 한 번 호출에 증가하는 수 (성능 최적화에 사용됨 데이터베이스 시퀀스 값이 하나씩 증가하도록 설정되어 있으면 이 값 을 반드시 1로 설정해야 한다 | 50 |
catalog, schema | DB catalog, schema 이름 |
allocationSize와 initialValue로 성능 최적화를 할 수 있다.
예를 들어, allocationSize = 50으로 설정하면 1번의 호출로 50 만큼 DB를 확보해둘 수 있다. 메모리는 그대로 1씩 사용할 수 있다. 이후 memory가 51에 도달하면 다시 1번의 호출을 통해 다시 50만큼 DB를 확보할 수 있다. 즉 DB를 직접 호출하지 않고 메모리만 호출하여 성능 최적화를 이룰 수 있다. 하지만 웹서버를 내리는 시점에 실제 저장 메모리와 확보 DB 간의 값 간격이 생겨 낭비가 될 수 있다.
TABLE 전략
TABLE 전략은 키 생성 전용 테이블을 하나 만들어 DB 시퀀스를 흉내내는 전략이다. 모든 DB에 적용 가능하나 table을 직접 사용하기 때문에 성능에 있어 단점이 있다.
@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;
}
속성 | 설명 | 기본값 |
name | 식별자 생성기 이름 | 필수 |
table | 키생성 테이블명 | hibernate_sequences |
pkColumnName | 시퀀스 컬럼명 | sequence_name |
valueColumnNa | 키로 사용할 값 이름 | 엔티티 이름 |
initialValue | 초기 값, 마지막으로 생성된 값이 기준 | 0 |
allocationSize | 시퀀스 한 번 호출에 증가하는 수 (성능 최적화에 사용된다.) | 50 |
catalog, schema | DB catalog, schema 이름 | |
uniqueConstraints(DDL) | 유니크 제약 조건 지정 |
권장하는 식별자 전략
기본 키 제약 조건은 null이 아니고, 유일하고, 변하지 않는 값이다. 하지만 미래까지 위와 같은 조건을 만족하는 자연키(ex.주민등록번호)를 찾기는 어렵다. 따라서 비즈니스와 관련이 없는 대리키를 사용하게 된다. 권장되는 사항은 Long 타입, 대체키, 키 생성전략을 사용하는 것이다.
예제
다대다 매핑
하나의 order에서 여러 개의 item 주문이 가능하고 하나의 item이 여러 개의 order이 될 수 있다면 아래와 같이 매핑할 수 있다.
Entity의 Getter와 Setter
Getter은 가급적 생성하는 것이 좋고 Setter는 생성을 고민해봐야 한다. Setter을 생성하게 되면 아무데서나 set하여 값을 바꿀 수 있기 때문에 코드 추적에 좋지 않아 유지보수성이 떨어질 수 있다. 따라서 가급적 생성자에서 값을 세팅하여 Setter의 사용을 최소화 하는 것이 유지보수성에 좋다.
데이터 중심 설계의 문제점
객체 설계를 테이블 설계에 맞추는 방식으로 개발하게 되면 테이블의 외래키를 객체에 그대로 가져오기 때문에 객체 그래프 탐색이 불가능하다.
'Spring > SpringBoot&JPA' 카테고리의 다른 글
[JPA] 다양한 연관관계 (다대일, 일대다, 일대일, 다대다) (0) | 2024.03.29 |
---|---|
[JPA] 연관관계 매핑 (단방향, 양방향, 연관관계의 주인) (0) | 2024.03.26 |
[자바 ORM 표준 JPA 프로그래밍] 섹션3. 영속성 관리 - 내부 동작 방식 (0) | 2024.03.18 |
[자바 ORM 표준 JPA 프로그래밍] 섹션2. JPA 시작하기 (0) | 2024.03.15 |
[자바 ORM 표준 JPA 프로그래밍] 섹션1. JPA 소개 (0) | 2024.03.15 |