[JPA]엔티티 매핑

Inung_92·2023년 10월 26일
1

JPA

목록 보기
5/7
post-thumbnail

해당 포스팅의 내용은 김영한 강사님의 자바 ORM 표준 JPA 프로그래밍 책의 내용을 정리하여 작성하였습니다.


엔티티 매핑

📖데이터베이스의 테이블과 엔티티 클래스를 매핑하는 것이다. JPA에서 가장 중요한 부분에 해당하며 엔티티 매핑이 제대로 이루어지지 않을 경우 수많은 오류가 발생 할 수 있다.

엔티티 매핑은 XML과 어노테이션을 선택하여 사용 가능하다. 나는 어노테이션을 이용해서 매핑하는 방법에 대해 공부했기 때문에 어노테이션을 이용해서 하겠다.

분류

매핑에 필요한 어노테이션을 큰 개념에서 4가지로 분류하면 다음과 같다.

  • 객체와 테이블 매핑 : @Entity, @Table
  • 기본 키 매핑 : @Id
  • 필드와 컬럼 매핑 : @Column
  • 연관관계 매핑 : @ManyToOne, @JoinColumn

위에서 분류한 4가지 이외에도 많은 어노테이션이 있지만 엔테테 매핑에 기본이자 핵심으로 사용되는 어노테이션들이니 먼저 알아보도록 하자.

@Entity

JPA를 사용하여 테이블로 매핑할 클래스는 해당 어노테이션을 필수로 붙여야한다. 이렇게 @Entity 어노테이션이 붙은 클래스를 엔티티 클래스라고 한다.

속성

  • name : 사용할 엔티티의 이름을 지정한다. 기본값은 클래스명을 사용하지만 다른 패키지에 동일한 이름이 같은 엔티티 클래스가 있는 경우 명시적으로 부여하여 충돌을 방지해야한다.

주의 사항

@Entity을 사용 할 경우 다음과 같은 사항을 조심해야한다.

  • 기본 생성자는 필수다.(public 또는 protected)
  • final 클래스, enum, interface, inner 클래스에는 사용 할 수 없다.
  • 저장할 필드에 final을 사용해서는 안된다.

자바에서는 따로 정의하지 않으면 기본 생성자를 자동으로 생성한다. 하지만 파라미터가 있는 생성자를 정의할 경우 기본 생성자는 자동으로 생성되지 않는다.

@Entity
public class Test{
	...
	private String name;
	
	// 파라미터가 있는 생성자
	// public Test(){} 생략
	public Test(String name){
		this.name = name;
	}
}

위 코드에서 파라미터가 있는 생성자를 정의했기 때문에 기본 생성자는 생략된다. 이럴 때 기본 생성자를 사용하고 싶으면 직접 파라미터가 없는 기본 생성자를 정의 해야한다. 아래 코드를 보자.

@Entity
public class Test{
	...
	private String name;

	// 기본 생성자 정의
	public Test(){}

	// 파라미터가 있는 생성자
	public Test(String name){
		this.name = name;
	}
}

@Table

📖엔티티와 매핑할 테이블 정보를 지정하는 어노테이션이다. 생략 할 경우 엔티티명을 테이블 이름으로 사용한다.

속성

  • name : 매핑할 대상 테이블명으로 지정하지 않을 경우 엔티티 이름을 기본적으로 사용한다.
  • catalog : catalog 기능을 보유한 데이터베이스에서 catalog를 매핑한다. 여기서 catalog는 데이터베이스 개체들의 대한 정의를 담고 있는 메타데이터들로 구성된 인스턴스이다. 대표적인 관계형 데이터베이스로는 MySQL, PostgreSQL, Oracle, SQL Server 등이 있다.
  • schema : schema 기능이 있는 데이터베이스에서 schema를 매핑한다.

주의사항

  • 고유한 테이블명을 지정해야한다.
  • 테이블명 앞에 스키마명이 붙는 데이터베이스는 스키마 지정을 명시적으로 해준다.

@Table 어노테이션은 DDL 옵션을 제외하면 큰 주의 사항은 없다. DDL 옵션에 대한 이야기는 뒤에서 다룰 예정이니 지금은 넘어가도록하자.

다양한 매핑

다음은 엔티티의 필드에서 사용되는 다양한 매핑 어노테이션들에 대해서 알아보도록 하자.

엔티티 매핑

import javax.persistence.*;
import java.util.Date;

@Entity
@Table(name = "MEMBER")
public class Member {
	@Id // 1. 식별자 값 매핑
	@Column(name = "MEMBER_ID") // 2. 컬럼 매핑
	private String id;

	@Column(name = "MEMBER_NAME")
	private String name;
	private Integer age;

	@Eumerated(EnumType.STRING) // 3. Enum 타입 매핑
	private RoleType roleType;
	@Temporal(Temporal.TIMESTAMP) // 4. Date 관련 매핑
	private Date createdDate;
	
	@Lob // 5. CLOB 또는 BLOB 매핑
	private String description;

	// getter, setter
	...
}

// Enum 선언
public enum RoleType{
	ADMIN, USER
}

위 코드에서 부여된 번호를 기준으로 하나씩 알아보도록하자.

@Id

📖엔티티의 식별자 값과 테이블의 기본 키값을 매핑하는 경우 사용한다.

@Id 어노테이션의 할당 방법은 다음과 같이 두 가지가 있다.

  • 직접할당 : 기본 키를 애플리케이션에서 직접 할당
  • 자동생성 : 대리 키 사용 방식으로 IDENTITY, SEQUENCE, TABLE이 있다. @Id 어노테이션과 함께 @GeneratedValue 어노테이션을 사용하면 된다.

자동생성이 다양하게 구분되는 이유는 데이터베이스 벤더마다 지원하는 방식이 다르기 때문에 나뉘어져 있는 것이다. 시퀀스를 제공하는 데이터베이스를 사용하는 경우에는 SEQUENCE 방식이 동작하지만 MySQL처럼 AUTO_INCREMENT를 사용하는 경우에는 동작하지 않는다.

두 가지 방법에 대해 조금 더 알아보자.

직접 할당

@Id
@Column(name = "id")
private String id;

직접 할당은 위와 같이 매핑하면 된다. 여기서 @Id 어노테이션이 적용 가능한 타입은 아래와 같다.

  • 자바 기본형
  • 자바 래퍼(Wrapper)클래스
  • String
  • java.util.Date
  • java.sql.Date
  • java.math.BigDecimal
  • java.math.BigInteger

직접 할당은 말 그대로 영속성 컨텍스트에 엔티티를 저장하기 이전에 직접 엔티티의 기본 키(식별자 값)를 할당하는 것이다.

Member member = new Member();
member.setId("id1"); // 직접 할당

entityManager.persist(member); // 저장

위와 같이 직접 할당하지 않고 저장할 경우에는 다음과 같은 예외가 발생한다.

  • PersistenceException : JPA 최상위 예외
  • IdentifierGenerationException : 하이버네이터 예외

자동생성 - IDENTITY

IDENTITY 전략은 MySQL의 AUTO_INCREMENT와 같이 자동으로 증가하는 기본 키 값을 매핑하고 싶은 경우 사용 할 수 있다.

IDENTITY를 적용할 수 있는 데이터베이스 일부를 나열해보겠다.

  • MySQL : AUTO_INCREMENT 사용
  • SQL Server : IDENTITY 컬럼 사용
  • PostgreSQL : SERIAL 데이터 형식 사용

적용하는 방법은 다음과 같다.

@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;

IDENTITY 전략의 특징은 데이터베이스에 값이 저장되어 기본 키값이 생겨야 해당 기본 키값을 구할 수 있다. 아래 코드를 보면서 이해해보자.

...생략
Item item = new item(); // 엔티티 생성
entityManager.persist(item); // 엔티티 저장
System.out.println(item.getId()); // 기본 키 확인

// 출력 : 1

또한, IDENTITY 전략은 persist() 를 호출하는 즉시 SQL이 데이터베이스에 전달된다. 이 말은 트랜잭션을 지원하는 쓰기 지연이 동작하지 않는다는 말이다.

자동 생성 - SEQUENCE

시퀀스는 유일한 값을 순서대로 생성하는 특별한 데이터베이스 오브젝트다. SEQUENCE전략은 이 시퀀스를 사용한다. 지원 가능한 데이터베이스는 Oracle, PostgreSQL, DB2등이 가능하다.

적용하는 방법은 다음과 같다.

// 시퀀스 생성
CREATE SEQUENCE BOARD_SEQ START WITH 1 INCREMENTH BY 1;

// 시퀀스 매핑
@Entity
@SequenceGenerator(
	name = "BOARD_SEQ_GENERATOR", // 시퀀스 생성기 등록
	sequenceName = "BOARD_SEQ", // 생성한 시퀀스명
	initialValue = 1, // 초기 값
	allocationSize = 1 // 한번 호출 시 증가 수
)
public class Board{
	@Id
	@GeneratedValue(strategy = GenerationType.SEQUENCE, // 전략 선택
									generator = "BOARD_SEQ_GENERATOR") // 시퀀스 생성기 매핑
	private Long id;
}

시퀀스 생성기의 역할을 데이터베이스의 시퀀스와 매핑하는 역할을 한다. 이렇게 설정이 완료되면 id 식별자 값은 시퀀스 생성기에 의해서 자동으로 할당된다.

IDENTITY 전략과 다른 점은 엔티티를 저장하려고 persist()를 호출하면 먼저 데이터베이스의 시퀀스를 사용해서 식별자를 조회하고 해당 식별자 값을 엔티티에 할당한 후 영속성 컨텍스트에 저장한다. 이후에 트랜잭션 커밋이 가능하다. 간단히 설명하면 다음과 같다.

  • IDENTITY : 엔티티 저장 후 식별자 조회 가능
  • SEQUENCE : 엔티티 저장 전 식별자 할당 후 저장

@SequenceGenerator에는 속성이 여러가지가 있는데 한번 둘러보자.

속성명기능기본값
name식별자 생성기 이름필수
sequenceName데이터베이스 시퀀스명hibernate_sequence
initialValueDDL에만 사용되는 시퀀스 초기값1
allocationSize시퀀스 한 번 호출 시 증가하는 수(성능 최적화에 사용)50
catalog, schema카탈로그 및 스키마 명칭-

여기서 allocationSize가 50인 것에 대해 주의해야한다. 50은 시퀀스를 한번 호출 할 때마다 값이 50씩 증가한다는 의미이다. 이 말은 시퀀스를 한번 불러오면 1~50까지를 메모리에 미리 할당해두고 사용한다는 의미이며, 두번째 불러오는 경우에는 51~100까지 그 수를 증가시키는 것이다. 이러한 상황의 문제점은 데이터베이스에 직접 접근해서 데이터를 등록할 때 시퀀스 값이 한번에 많이 증가한다는 점이다.

따라서 시퀀스값이 한번에 50씩 증가하는 것이 부담스럽고, Insert성능이 크게 중요하지 않다면 1로 설정하여 사용하는 것이 좋다.

자동 생성 - TABLE

TABLE 전력은 키 생성 전용 테이블을 만들어 시퀀스를 흉내내는 전략이다. 장점은 테이블을 사용하기 때문에 모든 데이터베이스에 적용이 가능하다.

적용하는 방법은 다음과 같다.

// 시퀀스 역할 테이블 생성
CREATE TABLE MY_SEQUENCES (
	SEQUENCE_NAME VARCHAR(255) NOT NULL,
	NETX_VAL BIGINT,
	PRIMARY KEY (SEQUENCE_NAME)
)

// 적용
@Entity
@TableGenerator(
	name = "BOARD_SEQ_GENERATOR",
	table = "MY_SEQUENCES", // 테이블 지정
	pkColumnValue = "BOARD_SEQ", // 기본키 컬럼명
	allocationSize = 1
)
public class Board {
	@Id
	@GeneratedValue(
		strategy = GenerationType.TABLE,
		generator = "BOARD_SEQ_GENERATOR"
	)
	private Long id;
}

@TableGenerator를 사용해서 테이블 키 생성기를 등록하고, name에 지정한 이름으로 매핑해준다. 조금 헷갈리는 부분은 pkColumnValue를 BOARD_SEQ로 지정한 것이다. 이 부분은 매핑한 테이블에서 SEQUENCE_NAME 컬럼에 해당하는 컬럼명을 지정한 것이라고 생각하면 된다.

@TableGenerator에 있는 여러가지 속성을 알아보자.

속성명기능기본값
name식별자 생성기 이름필수
table키 생성 테이블명hibernate_sequence
pkColumnName시퀀스 컬럼명sequence_name
valueColumnName시퀀스 값 컬럼명next_val
pkColumnValue키로 사용할 값 이름엔티티 이름
initialValue초기 값0
allocationSize시퀀스 한번 호출에 증가하는 수50
catalog, schema카탈로그, 스키마 명-
uniqueConstraints유니크 제약조건(DDL)-

TABLE 전략은 SEQUENCE 전략과 비교하여 데이터베이스와 한 번 더 통신하게 되는 점이 차이인데 이러한 이유는 값을 조회하면서 SELECT 쿼리를 사용하고 다음 값 증가를 위해 UPDATE 쿼리를 사용하기 때문이다.

자동 생성 - AUTO

AUTO 전략은 설정된 데이터베이스 방언에 따라서 위 전략 중 하나를 선택하는 전략이다. AUTO 전략의 장점은 데이터베이스를 변경해도 코드를 수정할 필요가 없다는 것이다. 특히 개발 초기나 프로토 타입 개발 시 편하게 사용 가능하다.

기본 키 생성은 자연 키 보다는 대리 키 사용을 권장하는데 이유는 다음과 같다.

  • 비즈니스 환경은 언젠가 변한다.
  • 현재는 물론 미래까지 충족하는 자연 키를 찾기는 쉽지 않다.
  • 따라서 비즈니스와 관련없이 자동으로 생성되는 대리 키를 사용하면 비즈니스 환경이 변하더라도 수정되는 부분은 크지 않을 수 있다.

마무리

최근에 JPA를 계속 공부하면서 느낀 점은 내가 개발을 할 때 설계 단계를 가볍게 건너뛰고 개발에만 치우쳐서 코드를 작성하다 수정하는 일이 빈번하게 발생한다는 느낌을 받았다.

김영한 강사님의 책을 읽다보면 항상 ERD와 클래스 다이어그램을 먼저 그려놓고 코드를 작성하는 모습을 볼 수 있는데 코드를 작성하기 전 자신이 구현해야하는 모습에 대한 설계를 먼저 해보고 그것을 다이어그램으로 그려보면 어떻게 진행해야하는지 감이 올 것이고, 애초에 잘못 생각한 부분들을 조금이라도 더 식별할 수 있을 것 같다는 생각을 했다.

이제는 개발을 할 때 코드를 빨리 작성하는 것 보다는 설계를 통한 구조화 연습을 해보면서 어떤 부분들에 더 신경을 써야하고 데이터베이스와 객체 간의 매핑은 적절히 이루어졌는지 면밀하게 들여다봐야겠다는 생각을 한다.

그럼 이만.👊🏽

profile
서핑하는 개발자🏄🏽

0개의 댓글