Programming-[Backend]/JPA

[JPA기본] 4. DDL 자동 생성과 엔티티 매핑 어노테이션

컴퓨터 탐험가 찰리 2021. 9. 26. 21:10
728x90
반응형

JPA를 잘 사용하기 위해서는 자바 객체인 엔티티와 RDB의 테이블간 매핑을 잘하는 것이 중요하다. 이 내용을 잘 모르면 에러가 발생할 경우가 많아지고 시간을 많이 허비하게 될 수 있다.

 


 

1. 데이터베이스 스키마 자동생성

 

DDL 자동 생성

데이터베이스 스키마 자동생성은 객체에 정의한 정보를 바탕으로 테이블을 만들어주는 기능이다. JPA가 DDL(Data Definition Language, 데이터 베이스의 스키마(테이블명, 열 이름 등)을 만들어주는 SQL문 집합) 을 자동으로 생성해준다.

 

단순히 생성해주는 것이 아니라, persistence.xml에서 지정한 SQL dialect에 맞는 적절한 DDL을 생성해준다. 예를 들어 String값은 일반적으로 테이블에서는 varchar 타입으로 분류되지만, Oracle 데이터베이스로 지정한 경우 varchar2로 해당 데이터베이스 언어에 맞게 DDL을 생성해주는 것이다.

 

편리한 기능이지만 실제 운영 서버에 사용하기에는 한계점이 있기 때문에 생성된 DDL을 바탕으로 적절히 다듬어서 사용해야 한다.

 

 

DDL 생성 속성

DDL을 생성하는 속성값을 지정하는 옵션에 따라서 다른 형태로 테이블을 조작할 수 있다. 우선 persistence.xml 파일에 다음 옵션을 추가한다.

 

<property name="hibernate.hbm2ddl.auto" value="create" />

 

여기서 변경할 값은 value 값이다. 이 value값의 옵션은 다음과 같다.

 

1. create : 기존 테이블을 삭제 후 다시 생성한다.

2. create-drop : 1. create 이후 테이블을 DROP 한다.

3. update : 변경 부분만 반영한다.

4. validate : 엔티티와 테이블이 정상 매핑 되었는지만 확인한다.

 

 

시험을 위해 create 모드로 하고, 객체를 persist 해보면 기존 Member 테이블이 DROP 되고 새로 생성한 member 객체만 저장하는 DDL이 적용됨을 확인할 수 있다.

 

 

다음으로 Member 객체에 Integer age 값을 추가해보자. 그리고 옵션은 update인채로 실행하면 아래와 같은 alter table 쿼리가 나가는 것을 확인할 수 있다.

 

 

 

validate는 엔티티상의 필드값과 테이블상의 컬럼값들이 일치하는지 확인해준다. 혹시라도 엔티티에서 필드값을 잘못 지정한 경우가 있을 수 있으므로 이 옵션을 사용하여 정상 매핑을 확인하는 용도로 사용한다. 보통 실무에서 이 기능을 많이 사용한다.

 

 

주의 사항 : 최대한 사용을 자제하자

여러 기능들이 지원되지만, 개발 초기 단계에서만 사용하는 것이 좋다. 혹시라도 운영 중인 서비스에서 테이블을 DROP하거나 CREATE 하는 것은 정말 위험한 일이다.

 

UPDATE를 사용하더라도, alter table이 되는 동안 데이터베이스가 lock이 걸려서 서비스가 멈출 수 있다. 정말 치명적인 오류가 될 수 있으므로 아무리 주의를 기울여 옵션을 조절하더라도 굳이 편의를 위해 위험을 감수하는 것은 옳지 않은 결정일 수 있다.

 

validate 정도만 개발단계에서 사용하고, 최대한 사용을 자제해야한다.

 

 


 

2. 필드와 컬럼 매핑

 

JPA에서의 매핑은 엔티티에 여러가지 어노테이션 및 속성값을 지정하여 설정할 수 있다. 차례대로 살펴보자. 먼저 Member 엔티티를 다음과 같이 설정해준다.

 

src/main/java/hellojpa/Member.java

 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
@Entity
@Getter
@Setter
@Table(name="members", uniqueConstraints = {@UniqueConstraint(name = "UK_name_age", columnNames = {"name""age"})})
@DynamicInsert
public class Member {
 
  @Id
  private Long id;
 
  @Column(name="name", columnDefinition = "varchar(100) default 'USER'")
  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;
 
}
cs

 

실행된 쿼리

 

 

 

@Entity

@Entity가 붙은 클래스는 JPA가 엔티티 객체로 인식하고 관리한다. 테이블과 매핑할 클래스는 이 어노테이션을 필수적으로 붙여주어야 한다.

 

유의사항은 다음과 같다.

1. 기본 생성자가 필수이다. 나중에 프록시 객체 등으로 조회할 때 필요하다고 한다.

2. final, enum, interface, inner 클래스에는 사용할 수 없다.

3. final값인 필드는 사용할 수 없다.

 


@Table

name 속성

이름 값을 속성으로 지정할 수 있다. 실제 데이터베이스상 테이블의 이름이 members라면, 여기에 맞춰서 테이블의 이름을 작성해주어야 한다. 보통 DDL로 테이블을 만드는 것이 아니고, 각 서비스마다 테이블 네이밍 규칙이 있으므로 이 옵션을 이용하여 엔티티 클래스의 이름과 테이블의 이름 불일치 문제를 해결한다.

 

uniqueConstraints 속성

이 속성을 이용해서 특정 필드들을 Unique 값으로 지정할 수 있고, 그 이름도 지정할 수 있다. 원래는 뒤에서 다룰 @Column 어노테이션의 unique 속성을 사용할 수도 있는데, 이 경우 unique 값의 이름을 직접 지정할 수 없으므로 @Table의 uniqueConstraints 속성을 사용한다. 코드를 보면 @UniqueConstraint 어노테이션을 사용하고, unique key로 사용할 이름을 name 속성으로 지정, unique key로 사용할 필드값들을 columnNames ={}로 지정한다. 실행 쿼리문 맨 마지막에 unique key가 지정된 것을 확인할 수 있다.

 


 

@Column

 

@Column 어노테이션이 가장 중요하다. 많은 속성값을 지원한다.

 

 

name 속성

필드값은 username인데, 테이블상의 이름은 name인 경우 이를 변경해주기 위해서 설정한다.

 

insertable, updatable 속성

이 속성값을 변경하면 데이터베이스에 객체의 지정값을 넣을 것인지(insert), 변경할 것인지(update)를 설정해줄 수 있다. 만약 insertable=false로 설정하면 서비스 코드에서 해당 객체의 값을 set해주고 DB에 넣으려고 해도 DB에 해당 값을 넣을 수 없도록 막아준다. updatable=false로 지정하면 기존 DB에 저장된 값을 유지하고 변경값이 있더라도 변경값을 적용하지 않는다. 기본값은 TRUE이다.

 

nullable 속성(DDL용)

nullable=false 로 지정하면 DDL 생성 시에 not null 제약 조건이 붙는다.

 

unique 속성(DDL용)

위에서 다룬 @Table의 uniqueConstraints와 같지만 여러 컬럼값을 지정할 수 없고 이름을 지정할 수 없으므로 거의 사용하지 않는다.

 

columnDefinition 속성(DDL용) : @DynamicInsert

데이터베이스의 컬럼 속성을 직접 지정해줄 수 있다. 예를 들어서 "varchar(100) default 'USER'"라고 지정하면 타입은 varchar(100), 미입력시 기본값은 USER가 된다. 테이블을 생성하는 DDL 문을 보면 default값이 지정된 것을 확인할 수 있다. 

 

그런데, 단순히 member 객체를 생성하고 name 필드값을 setUsername()으로 지정하지 않는다고 해서 default값이 데이터베이스에 저장되지 않는다! 기본적으로 아무런 값을 넣지 않고 쿼리문을 작성하더라도 쿼리값에 해당 필드가 포함되서 나가기 때문에, null값으로 저장된다.

@DynamicInsert를 엔티티에 적용하지 않은 상태인 경우 쿼리문은 다음과 같이 나가서, default로 설정한 값도 null값으로 DB에 저장된다.

 

@DynamicInsert 어노테이션 미적용

 

그러나 @DynamicInsert를 적용하면 null인 값은 쿼리가 나가지 않아서 실제 null이 적용되어 default로 설정한 값이 들어가게 된다. 따라서 default로 지정한 값을 이용하고자 한다면, @DynamicInsert, 또는 상황에 따라 @DynamicUpdate 어노테이션을 적용해주는 것이 좋다.

 

@DynamicInsert 적용, setId()로 id값만 지정
default값이 반영됨

 

length 속성(DDL용)

문자 길이 제약조건을 설정할 수 있다. String 타입에만 사용한다.

 

precision, scale 속성 (DDL용)

BigDecimal 타입에서 사용한다. precision은 소수점을 포함한 전체 자릿수, scale은 소수점 자릿수를 지정한다.

 

 

 

 

 

 


 

@Enumerated

EnumType 속성

@Enumerated 어노테이션은 Enum값을 필드값으로 지정해주고 싶을 때 사용한다. 원래 테이블상에 Enum 타입이라는 것은 존재하지 않으므로, 해당 Enum을 Enum이 정의된 순서인 Integer값으로 적용하거나, 이름값인 String으로 지정할 수 있다. 타입은 다음 2가지로 설정할 수 있다.

EnumType.ORDINAL

EnumType.STRING

그런데, 반드시 EnumType.STRING으로만 사용해야한다. 왜냐하면 순서는 ENUM값이 추가되어 바뀔 수 있기 때문이다. 예를 들어 위에서 지정한 RoleType Enum 값은

 

src/main/java/hellojpa/RoleType.java

 

1
2
3
public enum RoleType {
  ADMIN, USER
}
cs

이런 식으로 지정되어있는데, EnumType.ORDINAL로 지정 후 DDL을 실행하면 ADMIN은 0, USER는 1값으로 지정되어 테이블에 저장된다. 그런데 ADMIN 앞에 CUSTOMER라는 Enum이 추가되면 어떻게 될까? CUSTOMER가 0, ADMIN이 1로 지정된다. 즉 기존 데이터와 추가 데이터가 중복 적용되어 값을 구분할 수 없어 데이터베이스에 치명적인 오류를 발생시킨다.

 

 


 

@Temporal

TemporalType 속성

@Temporal은 날짜 타입에 적용해준다. @Temporal 어노테이션에 들어가보면 Temporal 타입이 DATE, TIME, TIMESTAMP 3가지 형식으로 지정되있는 것을 볼 수 있다.

 

 

이것은 원래 데이터베이스에는 날짜인 DATE, 시간인 TIME, 날짜와 시간인 TIMESTAMP 값을 넣을 수 있기 때문이다. 따라서 TemporalType 속성을 이용하여 어떤 값을 데이터베이스에 매핑할 것인지 지정해준다.

 

Java8 LocalDate, LocalDateTime

객체의 필드 타입을 Date가 아니라 Java8 이상부터 지원되는 LocalDate, LocalDateTime으로 지정하면 Date 인지, TIMESTAMP값인지 명확해지기 때문에 굳이 @Temporal 어노테이션을 사용하지 않아도 된다.

 

 

 


 

@Lob

LOB은 Large Object의 약자로, 크기가 큰 문자열, 바이트 정보 등을 데이터베이스에 담기 위한 타입을 의미한다. 대표적으로 String 타입을 지원하는 CLOB, 바이트 타입을 지원하는 BLOB이 있다. 매핑하는 필드의 타입이 String이면 CLOB, 그 외에는 BLOB을 적용한다.

 


 

@Transient

필드를 데이터베이스에 매핑하지 않고 메모리상에서 어떤 값을 보관만 하는 경우 사용한다. 금액의 백분율 등을 굳이 데이터베이스에 저장하지 않고 엔티티에서 계산하여 서비스 코드에서 사용할 때 필요할 것이다.


 

참조

 

 

1. 인프런_자바 ORM 표준 JPA 프로그래밍 - 기본편_김영한 님 강의

https://www.inflearn.com/course/ORM-JPA-Basic/lecture/21735?tab=curriculum

728x90
반응형