Home 객체(엔티티)와 DB의 매핑
Post
Cancel

객체(엔티티)와 DB의 매핑

데이터베이스를 사용하기 위해서는 객체와 DB, 즉 DB 테이블과의 매핑이 중요하다. JPA에서 사용하는 매핑 방법에 대해 알아본다.

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

사실 매핑을 위한 Member 클래스같은 경우를 보면 코드를 보고 어떤 쿼리를 만들어야 되는지, 어떤 테이블인지 알 수 있다.

JPA는 이러한 부분을 지원해준다.

  • DDL을 애플리케이션 실행 시점에 자동 생성해준다.
  • 테이블 중심 -> 객체 중심으로 코드를 짤 수 있다.
  • 데이터베이스 방언을 활용해 데이터베이스에 맞는 적절한 DDL을 생성해준다.
  • 이렇게 생성된 DDL은 개발 장비에서만 사용해야 한다.
    • 운영 서버에서는 사용하지 않거나, 적절히 다듬은 후 사용해야 한다.

설정 방법은 각각 다르다.

DDL?

Data Definition Language의 약자로 데이터 정의어를 뜻한다.

데이터를 생성하거나 수정, 삭제 등 데이터의 전체 골격을 결정하는 역할의 언어를 뜻한다.

  • CREATE: DB/테이블 생성
  • ALTER: 테이블 수정
  • DROP: DB/테이블 삭제
  • TRUNCATE: 테이블 초기화

데이터베이스 스키마 자동생성 속성

hibernate.hbm2ddl.auto의 속성을 보면 아래와 같다.

  • create: 기존 테이블 삭제 후 다시 생성 (DROP + CREATE)
  • create-drop: create와 동일하지만 종료 시점에 테이블을 DROP
    • 보통 테스트케이스 사용 시 테스트 종료 후 테이블을 날리고 싶을 때 사용하게 된다.
  • update: 변경 분만 반영한다. (운영 DB에 사용해서는 안된다.)
    • 추가만 가능하다.
    • 실수로 코드를 지웠을 때 컬럼이 제거된다면 문제가 발생할 것이다.
  • validate: 엔티티와 테이블이 정상 매핑되었는지 확인한다.
    • 정상적으로 매핑되지 않았으면 에러 발생.
  • none: 사용하지 않는다.

DDL 생성에 사용할 수 있는 추가 기능

  • 제약 조건 추가 가능
    • ex) 회원 이름 필수, 10자 초과 X
    • => @Column(nullable = false, length = 10)

DDL 생성 기능은 DDL을 자동 생성할 때만 사용되고 JPA의 실행 로직에는 영향을 끼치지 않는다.

주의 사항

  • 운영 장비에는 절대 create, create-drop, update를 사용하면 안된다.
    • 개발 초기 단계는 create 또는 update를 사용한다.
    • 테스트 서버는 update 또는 validate를 사용한다.
    • 스테이징과 운영 서버는 validate 또는 none을 사용한다.

로컬 PC에서만 사용하고 나머지는 웬만하면 직접 적용하는 것을 추천함.

잘못 생성된 스크립트로 어떠한 오류가 발생할 지 모르기 때문.

객체와 테이블 매핑

@Entity, @Table 애노테이션을 사용한다.

@Entity

  • @Entity가 붙은 클래스는 JPA가 관리하며 엔티티라고 한다.
  • JPA를 사용해 테이블과 매핑할 클래스라면 @Entity 애노테이션은 필수이다.
  • name 속성을 가지며 name 속성에 기본적으로는 클래스 이름을 그대로 사용한다.

주의

  • 기본 생성자가 반드시 필요하다.
    • 파라미터가 없는 public 또는 protected 생성자를 뜻한다.
  • final 클래스, enum, interface, inner 클래스에는 사용할 수 없다.
  • DB에 저장할 필드에 final 키워드를 사용하면 안된다.

@Table

  • @Table은 엔티티와 매핑할 테이블을 지정하는 애노테이션이다.
1
2
3
4
5
@Entity
@Table(name = "MBR")
public class Member {
	/...
}

엔티티와 매핑할 테이블을 “MBR” 이라는 테이블로 지정하는 것이다. 만약 따로 지정하지 않는다면 기본적으로 엔티티 이름인 “Member” 테이블에 매핑하게 된다.

참고로 @Table이 가지는 속성 값은 아래와 같다.

  • name: 매핑할 테이블 이름
  • catalog: 데이터베이스 catalog 매핑
  • schema: 데이터베이스 schema 매핑
  • uniqueConstraints: DDL 생성 시 유니크 제약 조건 생성

필드와 컬럼 매핑

자바 코드의 필드와 특정 컬럼을 매핑하는 방법에 대해 알아본다.

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
@Entity
public class Member {
	
	@Id
	private Long id;

	@Column(name = "name")
	private String uesrname;

	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;

	//Getter, Setter...
}
  • @Column(name = “name”)
    • 필드 이름은 username이지만 테이블의 name 컬럼에 매핑하고 싶을 때 사용한다.
  • @Enumerated(EnumType.STRING)
    • DB에는 Enum 타입이 없다. Enum 타입을 사용하고 싶을 때 사용한다.
  • @Temporal(TemporalType.TIMESTAMP)
    • 보통 DB에서는 DATE, TIME, TIMESTAMP를 구분해서 사용한다.
    • 따라서 매핑 정보를 제공해야 한다.
  • @Lob
    • 어떠한 큰 컨텐츠를 넣고 싶을 때 사용한다.

위와 같은 결과를 나타내는 것을 볼 수 있다.

애노테이션 정리

  • @Column: 컬럼 매핑
  • @Temporal: 날짜 타입 매핑
  • @Enumerated: enum 타입 매핑
  • @Lob: BLOB, CLOB 매핑
  • @Transient: 특정 필드를 컬럼에 매핑하지 않을 때 사용(매핑 무시)
    • DB에 매핑하지 않고 메모리 내에서만 사용한다.

@Column

  • name: 필드와 매핑할 테이블의 컬럼 이름
    • 기본값 - 객체의 필드 이름.
  • insertable, updatable: 등록 및 변경 가능 여부
    • 기본값 - true
  • nullable(DDL): null 값의 허용 여부를 설정한다. false 시 not null
  • unique(DDL): 하나의 컬럼에 간단하게 유니크 제약 조건을 걸 때 사용한다.
  • columnDefinition(DDL): 데이터베이스 컬럼 정보를 직접 줄 수 있다.
    • 기본값은 필드의 자바 타입과 방언 정보를 활용해 설정된다.
  • length(DDL): 문자 길이 제약 조건으로 String 타입에만 사용한다.
  • precision, scale(DDL): BigDecimal 타입에서 사용된다.
    • precision은 소수점을 포함한 전체 자릿수
    • scale은 소수의 자리수

@Enumerated

  • value
    • EnumType.ORDINAL: enum 순서를 데이터베이스에 저장
    • EnumType.STRING: enum 이름을 데이터베이스에 저장
    • 기본값 - ORDINAL

주의

ORDINAL을 사용하면 안된다.

ORDINAL을 사용하면 예를들어 enum에 USER, ADMIN이 있다고 가정하자.

어떤 엔티티에 USER을 주고, 어떤 엔티티에는 ADMIN을 주면 데이터베이스에는 각각 0과 1로 저장이 된다.

그런데 어느날 추가사항이 생겨 GUEST, USER, ADMIN의 구조로 변경해야 된다고 가정하자. 그렇다면 어떻게 될까?

이렇게 수정해도 GUEST로 추가한 컬럼은 0이 저장되고, 기존의 데이터는 변경 없이 0과 1 그대로 유지되어 GUEST, USER, GUEST의 정보가 저장된 셈이 되는 것이다.

@Temporal

날짜 타입(java.util.Date, java.util.Calendar)을 매핑할 때 사용한다.

하지만 지금은 LocalDate, LocalDateTime이 있는데, 이를 사용할 때는 생략 가능하다.

@Lob

  • @Lob에는 지정할 수 있는 속성이 없다.

매핑하는 필드 타입이 문자면 CLOB을 매핑하고 나머지는 BOLB을 매핑한다.

  • CLOB: String, char[], java.sql.CLOB
  • BLOB: byte[], java.sql.BLOB

기본 키 매핑

  • @Id
  • @GeneratedValue
1
2
@Id @GeneratedValue(strategy = GenerationType.AUTO)
private Long id;

기본 키 매핑 방법

  • 직접 할당: @Id 만 사용 (기본 키를 직접 할당하고 싶을 때 사용한다.)
  • 자동 생성: @GeneratedValue
    • IDENTITY: 데이터베이스에 위임, MYSQL에서 사용
    • SEQUENCE: 데이터베이스 시퀀스 오브젝트 사용, ORACLE에서 사용
      • @SequenceGenerator 필요
    • TABLE: 키 생성용 테이블 사용, 모든 DB에서 사용한다.
      • @TableGenerator 필요
    • AUTO: DB 방언에 따라 자동 지정, 기본 값이다.

IDENTITY

  • 기본 키 생성을 데이터베이스에 위임한다.
  • 주로 MYSQL, PostgreSQL, SQL Server, DB2에서 사용한다.

여기서 특이사항이 있다.

  • JPA는 보통 트랜잭션 커밋 시점에 INSERT SQL을 실행한다.
  • 그런데 AUTO_INCREMENT는 데이터베이스에 INSERT SQL을 실행한 이후에 ID값을 알 수 있다.
  • 따라서 IDENTITY 전략은 예외적으로 em.persist() 시점에 즉시 INSERT SQL을 실행하고 DB에서 식별자를 조회한다.
    • commit() 이전에 단순 persist()이후 getId()를 가져와도 id가 조회가 되는 것이다.

따라서 Insert 쿼리를 모아서 전달하는 이러한 부분에 제약이 있는 것이 IDENTITY 전략의 단점이다. 물론 큰 차이는 있지 않다.

SEQUENCE

  • 데이터베이스 시퀀스는 유일한 값을 순서대로 생성하는 특별한 데이터베이스 오브젝트이다.
    • ex) 오라클 시퀀스
  • 오라클, PostgreSQL, DB2, H2 데이터베이스에서 사용.
1
2
3
4
5
6
7
8
9
10
@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: 데이터베이스에 등록되어 있는 시퀀스 이름
    • 기본값: hibernate_sequence
  • initialValue(DDL): 시퀀스 DDL을 생성할 때 처음 1, 시작하는 수를 지정한다.
    • 기본값: 1
  • allocationSize: 시퀀스 한 번 호출에 증가하는 수(성능 최적화에 사용된다.)
    • 기본값: 50
  • catalog, schema: 데이터베이스 catalog, schema 이름.

allocationSize가 기본 50인데, 이는 성능때문에 그렇다.

SEQUENCE 전략을 사용해보면 persist() 호출 시 call next value가 수행된다. 시퀀스에서 next value를 불러오는 것이다.

em.persist()를 할 때 영속성 컨텍스트에 집어 넣으려면 PK가 필요하기 때문이다. IDENTITY 전략에서는 INSERT 쿼리가 날아가 PK를 얻는 방식인데 SEQUENCE 전략에서는 어떻게 처리했을까?

=> 쿼리 없이 DB에서 키 값만을 얻어와 영속성 컨텍스트에 영속하는데 사용한다.

이를 하나하나 얻어오는 과정은 성능상의 문제가 있을 것 같다. 이를 allocationSize 설정을 통해 해결하는 것이다.

50이라면 미리 메모리에 50만큼 올려두고 꺼내쓰는 느낌이다. 50이 다 사용되면 100으로 되는 방식이다. 여러 웹서버에서 동시성 이슈 없이 성능 문제를 해결해준다.

참고로 웹서버를 내리는 시점에 이 50의 공간도 사라지기 때문에 31번까지 사용하고 내려갔다면 32~51까지는 공백이 생긴다. 이 때문에 50정도를 기본값으로 사용하고 있는 것이다.

TABLE

  • 키 생성 전용 테이블을 하나 만들어서 데이터베이스 시퀀스를 흉내내는 전략이다.
  • 장점은 모든 데이터베이스에 적용할 수 있다는 점이다.
  • 하지만 성능 문제가 있다.

권장하는 식별자 전략

  • 기본 키 제약 조건을 생각해보자.
    • null이면 안된다.
    • 유일해야 한다.
    • 변하면 안된다. (이 부분이 충족되기 어렵다.)
  • 미래까지 위의 기본 키 제약 조건을 만족하는 자연키는 찾기 어렵다. 대리키를 사용하는 것이 권장된다.
    • 심지어 주민등록번호마저 기본 키로 적절하지 않다.
    • 주민번호가 변하는 것이 문제가 아닌, 갑자기 국가에서 주민번호를 저장하면 안된다 같은 경우가 생길 수 있기 때문.
  • 권장: Long형 + 대체 키 + 키 생성 전략 사용
    • Auto Increment, Sequence 전략 등
This post is licensed under CC BY 4.0 by the author.