JPA 기본 - 엔티티 매핑
1. @Entity
- @Entity가 붙은 클래스는 JPA가 관리
- JPA를 사용해서 테이블과 매핑할 클래스는 @Entity 필수
- JPA 스펙상 파라미터가 없는 public 또는 protected 기본 생성자 필수
- 임베디드 타입에도 기본 생성자 필수
- 생성자에 파라미터 줄 경우 기본 생성자가 자동으로 생성안되기 때문에 따로 만들어줘야 한다. 기본 생성자를 사용할 일이 없고 그냥 스펙상 만들어 준 경우라면 public 대신 protected로 범위를 지정한다. 이렇게 해두면 나중에 사용할 때 기본 생성자는 스펙상 둔거구나 하고 알 수 있다.
- 이런 제약을 두는 이유는 JPA 구현 라이브러리가 객체를 생성할 때 프록시나 리플랙션 같은 기술을 사용할 수 있도록 지원해야 하기 때문
- final 클래스, enum, interface, inner 클래스에는 사용 불가능
- DB에 저장하고 싶은 필드는 final 사용 불가
2. @Table
name 속성으로 엔티티와 매핑할 테이블명을 지정한다. JPA는 Entity를 대상으로 쿼리를 날리므로 이에 따른 결과를 어떤 테이블에 지정할 건지 선택하는 거라고 보면 된다. default는 클래스명이랑 같은 테이블로 매핑된다.
@Entity
@Table(name="테이블명")
public class Member{
}
- name 속성 : 매핑할 테이블 이름
- catalog : 데이터베이스 catalog 매핑
- schema : 데이터베이스 schema 매핑
- uniqueConstraints : DDL 생성 시에 유니크 제약 조건 생성
3. 데이터베이스 스키마 자동 생성
persistence.xml 에서 옵션으로 데이터베이스 스키마를 자동 생성하도록 할 수 있다.
<property name="hibernate.hbm2ddl.auto" value="create" />
value 속성값으로 다음이 올 수 있다.
- create : 기존테이블 삭제 후 다시 생성(DROP + CREATE)
- create-drop : create와 같으나 종료시점에 테이블 drop
- update : 변경내용만 반영
- validate : 엔티티와 테이블이 정상 매핑되었는지만 확인
- none : 사용하지 않음
다음과 같은 속성을 사용하면 DDL(create, alter, drop, truncate)을 애플리케이션 실행 시점에 자동 생성시켜준다. 하지만 이렇게 생성된 DDL은 반드시 개발 단계에서만 사용하고 운영 단계에서는 사용하면 안된다. 정리하면, 개발 초기 단계에는 create, update 정도는 사용하되 테스트 서버, 스테이징과 운영 서버에서는 validate, none만 사용해야 한다.
DDL 생성 기능
- 제약조건 추가
- @column(nullable=false,length=10)
- 유니크 제약 조건 추가
DDL 생성 기능은 DDL을 자동 생성할 때만 사용되고 JPA 실행 로직에는 영향을 주지 않는다. @Table(name=”TBL”) 같은 경우는 insert,update 쿼리에 영향을 주는데 제약 조건같은 것은 실행에 영향을 주지 않고 단순히 DDL을 생성하는데만 영향을 준다.
4. 매핑 애노테이션
@Column
컬럼 매핑할 때 사용하는 애노테이션이고 속성값은 다음과 같다.
- name : 필드와 매핑할 테이블의 컬럼 이름 지정, 기본값은 객체의 필드 이름
- insertable, updatable : 등록, 변경 가능 여부, 기본값은 TRUE
- nullable : null값 허용 여부 -> false로 설정하면 not null제약조건이 걸린다.
- unique : @Table의 uniqueConstraints와 같지만 한 컬럼에 간단한 유니크 제약 조건을 걸 때 사용한다.
- 잘 사용하지 않는다. unique 제약조건을 만들 때 이름이 랜덤으로 나오기 때문에 운영에서 사용하기 어렵다. 운영에서 이름을 보면 무엇때문에 발생한지 확인이 가능해야 하기 때문이다. 따라서 이름을 설정해주기 위해서는 @Table에서 uniqueConstraints로 거는게 낫다.
- @Table(uniqueConstraints={@UniqueConstraint(columnNames = {“A”, “B”})})
- columnDefinition : DB 컬럼 정보를 직접 줄 수 있다.
- ex) columnDefinition = “varchar(50) default ‘EMPTY’”
- length : 문자 길이 제약 조건으로 String 타입에만 사용 가능, 기본값은 255
- precision, scale : double 같은 타입에는 적용되지 않고 아주 큰 BigDecimal타입 에서 사용한다. precision은 소수점을 포함한 전체 자리수, scale은 소수의 자리수다. 기본값은 precision=19, scale=2
@Enumerated
자바 Enum 타입을 매핑할 때 사용한다. 속성은 다음과 같다.
- value = EnumType.ORDINAL : 순서를 db에 저장
- value = EnumType.STRING : 이름을 db에 저장
기본값은 ORDINAL이다. 하지만 중간에 수정이나 추가를 고려해서 반드시 STRING으로 사용해야 한다. value는 생략해도 된다.
@Temporal
Date 타입을 매핑할 때 사용하는데 속성은 다음과 같다.
- value = TemporalType.DATE : 날짜, db의 date타입과 매핑
- value = TemporalType.TIME : 시간, db의 time타입과 매핑
- value = TemporalType.TIMESTAMP : 날짜와 시간, db의 timestamp타입과 매핑
과거에는 위처럼 애노테이션에 속성값을 주고 사용했는데 최신 하이버네이트부터는 애노테이션을 사용하지 않고 아래와 같이 사용한다.
LocalDate date; // 날짜 -> date
LocalTime date; // 시간 -> time
LocalDateTime date; // 날짜 시간 -> timestamp
@Lob
varchar의 범위를 넘어서는 큰 것일 때 사용하며, 속성은 없다. 필드 타입이 문자면 CLOB로 매핑, 나머지는 BLOB로 매핑된다.
@Transient
데이터베이스에 저장, 조회를 하지 않고 필드 매핑을 하지 않도록 하는 애노테이션, 주로 메모리상에서만 임시로 어떤 값을 보관하고 싶을 때 사용한다.
5. 기본 키 매핑
- 직접 할당 : @Id만 사용
- 자동 생성 : @GeneratedValue
- IDENTITY : 데이터베이스에 위임, MYSQL(auto_increment)
- SEQUENCE : 데이터베이스 시퀀스 오브젝트 사용, @SequenceGenerator 필요, ORACLE
- TABLE : 키 생성용 테이블 사용, 모든 DB 사용, @TableGenerator 필요
- AUTO : 기본값으로 방언에 따라 자동 지정
IDENTITY 전략
- 기본 키 생성을 데이터베이스에 위임(mysql이 대표적)
- em.persist 시점에 즉시 쿼리문을 DB에 날리고 식별자를 가져온다.
- DB에 쿼리문을 날려야만 AUTO_INCREMENT로 pk값이 만들어지기 때문에 쿼리문을 날린 후에야 ID값을 알 수 있다. 따라서 IDENTITY 전략은 트랜잭션 커밋 시점에 쿼리문을 날리지 않고 persist 시점에 바로 날린다.
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
SEQUENCE 전략
유일한 값을 순서대로 생성하는 특별한 DB 오브젝트(oracle이 대표적)로 속성은 다음과 같다.
- name : 식별자 생성기 이름
- sequenceName : 데이터베이스에 등록되어 있는 시퀀스 이름
- initialValue : DDL 생성 시에만 사용 -> 시퀀스 DDL을 생성할 때 처음 1 시작하는 수를 지정, 기본값 1
- allocationSize : 시퀀스 한 번 호출에 증가하는 수, 기본값 50
- catalog, schema : 데이터베이스 catalog, schema 이름
@Entity
@SequenceGenerator( // SEQUENCE 전략 사용시 GENERATOR을 먼저 만들고 사용 시 식별자로 지정해서 사용한다.
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;
SEQUENCE 오브젝트도 결과적으로 IDENTITY와 마찬가지로 DB에 가봐야 안다. 하지만 insert 쿼리를 날리는게 아니라 그냥 call로 DB에서 값을 가져온다. 즉, em.persist 시점에 영속성 컨텍스트에 넣으려면 pk가 필요한데 jpa는 persist 시점에 sequence 전략임을 확인했다면 db에서 sequenceName인 Member_seq를 db에서 가져와 id값(pk)에 넣어준다. 그리고 나서야 영속성 컨텍스트에 저장한다. 이후에 트랜잭션 커밋 시점에 쿼리가 db로 날라간다. IDENTITY와 다르게 쿼리를 저장해뒀다가 날리는게 가능한 것이다.
그럼 매번 네트워크를 왔다갔다 해야해서 성능이 떨어지지 않을까? jpa는 allocationSize라는 속성으로 성능을 개선할 수 있다. 기본값은 50인데 이것은 한 번에 50개를 땡겨온다는 뜻이다. persist 시점에 db에 한번 call 했을 때 1번부터 51번까지 만들어 놓고 애플리케이션으로 이것을 한 번에 가져와 메모리에서 이 갯수만큼을 사용한다는 뜻이다. 그리고 51번이 되면 다시 콜로 50개를 db에 만들어 놓고 한 번에 땡겨오는 것이다. 보통 50~100정도가 적당하다.
본 포스팅은 인프런 김영한님의 ‘자바 ORM 표준 JPA 프로그래밍 - 기본편’ 강의를 듣고 정리한 내용을 바탕으로 복습을 위해 작성하였습니다. [강의 링크]