이번편에서는 2번째 객채와 관계형 데이터베이스를 매핑하는 부분에 대해 살펴보도록 하겠습니다.
- 객체와 테이블 매핑 : @Entity, @Table
- 필드와 컬럼 매핑 : @Column
- 기본 키 매핑 : @Id
- 연관관계 매핑 : @ManyToOne, @JoinColumn
@Entity
@Table(name="")으로 관계형데이터베이스와 매핑할때 테이블의 이름을 작성할수 있음
필드의 있는 값을 바탕으로 데이터베이스가 매핑되기에, 매핑뿐만 아닌 실행시점에 jpa에서 테이블을 만들어주는 기능도 지원한다(DDL자동생성기능).
데이터베이스 방언에 맞춰 적절한 DDL을 생성해주기에 매우 편리하지만 운영서버에서 사용을 지양해라!
ddl은 persistence.xml을 통해 설정이 가능하다.
<property name="hibernate.hbm2ddl.auto" value="create" />
// create는 해당하는 테이블을 삭제하고, 생성하는 옵션
Member테이블이 존재한다면 삭제하고, 생성하는 쿼리를 볼수 있다.
-> 운영서버에서 이렇게 된다면 데이터가 다 날아가게되기때문에 지양한다.Hibernate: drop table Member if exists Hibernate: ------------------------------ create table Member ( id bigint not null, name varchar(255), primary key (id) )
create-drop : 생성했다 지우기때문에, 테스트를 진행할때 주로 사용
update : 추가되는 필드는 컬럼 추가가되지만, 존재하던 필드를 삭제한 것은 반영되지 않는다
validate : 현재 존재하는 테이블과 같은지 확인
none : ddl을 사용하지 않을때 사용하는데, 사실 none이라는 옵션이 있는게 아닌 위의 종류 외의 아무문자나 적으면 매칭되는게 없어서 실행되지 않는것
운영장비에는 절대 create, create-drop, update를 사용하면 안된다.
DDL생성에만 영향을주기때문에 JPA 실행에는 영향을 주지않는다. 하지만 @Table과 같은 어노테이션은 쿼리를 날릴때 실제 TABLE과 매핑하는 과정등에서 사용되기 때문에 jpa실행에 영향 끼침
실제 상황을 가정하여 진행
멤버
@Entity
public class Member {
@Id
private Long id;
@Column(name = "name")
private String username;
private Integer age;
@Enumerated(EnumType.STRING)
private RoleType roleType;
@Temporal(TemporalType.TIMESTAMP)
private Date createdDate;
@Temporal(TemporalType.TIMESTAMP)
private Date lastModifiedDate;
@Lob
private String description;
@Transient
private int temp;
}
- @Column(name="") : 데이터베이스의 컬럼으로 설정추가 가능(데이터베이스의 설정되어있는 명칭이 필드명과 다를때 name을 통해 설정가능)
- @Enumerated(EnumType.STRING) : 객체에서 ENUM타입을 쓰고싶을때, DB에는 ENUM타입이 기본적으로 없기때문에 해당 어노테이션을 통해 사용
- @Temporal : 세가지 타입(date,time,timestamp)이 있다. 자바는 date타입에는 날짜와 시간이 전부 다 있지만, db는 3가지를 구별해서 사용
- @Lob : 데이터베이스에 varchar를 넘어서는 큰값을 사용하고싶을때 사용
- @Transient : 특정 필드를 컬럼에 매핑하지 않을때, db와 상관없이 메모리에서만 무언가를 하고싶을때 사용
- nullable을 false를 하게되면 NotNull제약조건이 걸리게됨
- 간단하게 unique제약조건을 만들고싶을때 unique옵션을 쓰지만, 랜덤의 긴 문자열이 제약값으로 되기때문에 운영에서 사용이 힘듦. 따라서 @Table의 옵션으로 uniqueConstraints를 통해 제약조건을 이름까지 설정할수 있게된다.
기본이 Ordinal이지만 Ordinal사용 X
-> 요구사항이 달라져서 enum값이 추가되거나 삭제되었을때 인덱스가 달라져서 값의 혼동이 올수있기에, Oridinal사용금지
기본키매핑
@Id
@GeneratedValue
두가지 어노테이션을 사용한다.
기본키 매핑 방법
IDENTITY 전략
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
jpa는 영속성 컨텍스트를 통해 객체를 관리하게되는데 이때 pk값이 무조건 있어야하는데, 그러면 영속성 컨텍스트에서 어떻게 관리하지?
그래서 Identity전략사용시 em.persist()를 통해 영속성 컨텍스트에 저장하기 전 DB에 insert쿼리를 날려 저장시킨다. 그리고 바로 DB에서 pk값과 함께 불러오는데, 이때 select 쿼리는 날아가지 않는다. 왜냐하면 JDBC가 저장한 값을 return받는 알고리즘이 짜여있어서 select쿼리를 사용하지 않아도 괜찮다. 하지만 이런 방식때문에 Identity는 모아서 한번에 버퍼하여 보낼수 없다.
각 객체의 시퀀스를 다르게 관리하고싶을때 @SequenceGenerator를 이용하면 sequeceName에 따라 테이블을 따로 매핑해준다.
@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; @Column(name = "name",nullable = false) private String username; }
시퀀스의 값도 결국 DB에 가봐야 알수있다.
따라서 em.persist()실행시, DB에서 시퀀스의 값을 먼저 가져와서 객체에 값을 넣어주고 영속성 컨텍스트에 저장하게된다. 그리고 커밋 시점에 insert쿼리가 날아가게됨. insert쿼리를 바로 날리는 Identity전략과 달리 시퀀스의 경우 버퍼에 저장했다가 한번에 보내는거 가능하다.
이때 성능상의 걱정이될수있다(네트워크를 계속 타야하기 때문에).
그래서 @SequenceGenerator의 옵션에 allocationSize의 설정을 통해 한번에 설정된 만큼의 시퀀스의 크기를 가져올수 있다. 호출시 DB의 시퀀스가 시퀀스 크기만큼 올린다. 여러 서버가 동시에 호출하여도 그냥 크기만큼 주고, 올리기때문에 크게 상관없다.
@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;
@Column(name = "name",nullable = false)
private String username;
}
다음과 같이 생성된것을 볼수있다.
운영에서 사용하기는 힘들다.DB에서 관례로 쓰는것들이 있기때문에 굳이 사용할필요 없다.