해당 포스팅의 내용은 김영한 강사님의 자바 ORM 표준 JPA 프로그래밍 책의 내용을 정리하여 작성하였습니다.
📖데이터베이스의 테이블과 엔티티 클래스를 매핑하는 것이다. JPA에서 가장 중요한 부분에 해당하며 엔티티 매핑이 제대로 이루어지지 않을 경우 수많은 오류가 발생 할 수 있다.
엔티티 매핑은 XML과 어노테이션을 선택하여 사용 가능하다. 나는 어노테이션을 이용해서 매핑하는 방법에 대해 공부했기 때문에 어노테이션을 이용해서 하겠다.
매핑에 필요한 어노테이션을 큰 개념에서 4가지로 분류하면 다음과 같다.
@Entity
, @Table
@Id
@Column
@ManyToOne
, @JoinColumn
위에서 분류한 4가지 이외에도 많은 어노테이션이 있지만 엔테테 매핑에 기본이자 핵심으로 사용되는 어노테이션들이니 먼저 알아보도록 하자.
JPA를 사용하여 테이블로 매핑할 클래스는 해당 어노테이션을 필수로 붙여야한다. 이렇게 @Entity 어노테이션이 붙은 클래스를 엔티티 클래스라고 한다.
@Entity을 사용 할 경우 다음과 같은 사항을 조심해야한다.
자바에서는 따로 정의하지 않으면 기본 생성자를 자동으로 생성한다. 하지만 파라미터가 있는 생성자를 정의할 경우 기본 생성자는 자동으로 생성되지 않는다.
@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 어노테이션은 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 어노테이션의 할당 방법은 다음과 같이 두 가지가 있다.
IDENTITY
, SEQUENCE
, TABLE
이 있다. @Id 어노테이션과 함께 @GeneratedValue 어노테이션을 사용하면 된다.자동생성이 다양하게 구분되는 이유는 데이터베이스 벤더마다 지원하는 방식이 다르기 때문에 나뉘어져 있는 것이다. 시퀀스를 제공하는 데이터베이스를 사용하는 경우에는 SEQUENCE
방식이 동작하지만 MySQL
처럼 AUTO_INCREMENT
를 사용하는 경우에는 동작하지 않는다.
두 가지 방법에 대해 조금 더 알아보자.
@Id
@Column(name = "id")
private String id;
직접 할당은 위와 같이 매핑하면 된다. 여기서 @Id 어노테이션이 적용 가능한 타입은 아래와 같다.
직접 할당은 말 그대로 영속성 컨텍스트에 엔티티를 저장하기 이전에 직접 엔티티의 기본 키(식별자 값)를 할당하는 것이다.
Member member = new Member();
member.setId("id1"); // 직접 할당
entityManager.persist(member); // 저장
위와 같이 직접 할당하지 않고 저장할 경우에는 다음과 같은 예외가 발생한다.
IDENTITY
전략은 MySQL의 AUTO_INCREMENT
와 같이 자동으로 증가하는 기본 키 값을 매핑하고 싶은 경우 사용 할 수 있다.
IDENTITY
를 적용할 수 있는 데이터베이스 일부를 나열해보겠다.
AUTO_INCREMENT
사용IDENTITY
컬럼 사용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
전략은 이 시퀀스를 사용한다. 지원 가능한 데이터베이스는 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()
를 호출하면 먼저 데이터베이스의 시퀀스를 사용해서 식별자를 조회하고 해당 식별자 값을 엔티티에 할당한 후 영속성 컨텍스트에 저장한다. 이후에 트랜잭션 커밋이 가능하다. 간단히 설명하면 다음과 같다.
@SequenceGenerator에는 속성이 여러가지가 있는데 한번 둘러보자.
속성명 | 기능 | 기본값 |
---|---|---|
name | 식별자 생성기 이름 | 필수 |
sequenceName | 데이터베이스 시퀀스명 | hibernate_sequence |
initialValue | DDL에만 사용되는 시퀀스 초기값 | 1 |
allocationSize | 시퀀스 한 번 호출 시 증가하는 수(성능 최적화에 사용) | 50 |
catalog, schema | 카탈로그 및 스키마 명칭 | - |
여기서 allocationSize
가 50인 것에 대해 주의해야한다. 50은 시퀀스를 한번 호출 할 때마다 값이 50씩 증가한다는 의미이다. 이 말은 시퀀스를 한번 불러오면 1~50까지를 메모리에 미리 할당해두고 사용한다는 의미이며, 두번째 불러오는 경우에는 51~100까지 그 수를 증가시키는 것이다. 이러한 상황의 문제점은 데이터베이스에 직접 접근해서 데이터를 등록할 때 시퀀스 값이 한번에 많이 증가한다는 점이다.
따라서 시퀀스값이 한번에 50씩 증가하는 것이 부담스럽고, Insert
성능이 크게 중요하지 않다면 1로 설정하여 사용하는 것이 좋다.
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
전략의 장점은 데이터베이스를 변경해도 코드를 수정할 필요가 없다는 것이다. 특히 개발 초기나 프로토 타입 개발 시 편하게 사용 가능하다.
기본 키 생성은 자연 키 보다는 대리 키 사용을 권장하는데 이유는 다음과 같다.
최근에 JPA를 계속 공부하면서 느낀 점은 내가 개발을 할 때 설계 단계를 가볍게 건너뛰고 개발에만 치우쳐서 코드를 작성하다 수정하는 일이 빈번하게 발생한다는 느낌을 받았다.
김영한 강사님의 책을 읽다보면 항상 ERD와 클래스 다이어그램을 먼저 그려놓고 코드를 작성하는 모습을 볼 수 있는데 코드를 작성하기 전 자신이 구현해야하는 모습에 대한 설계를 먼저 해보고 그것을 다이어그램으로 그려보면 어떻게 진행해야하는지 감이 올 것이고, 애초에 잘못 생각한 부분들을 조금이라도 더 식별할 수 있을 것 같다는 생각을 했다.
이제는 개발을 할 때 코드를 빨리 작성하는 것 보다는 설계를 통한 구조화 연습을 해보면서 어떤 부분들에 더 신경을 써야하고 데이터베이스와 객체 간의 매핑은 적절히 이루어졌는지 면밀하게 들여다봐야겠다는 생각을 한다.
그럼 이만.👊🏽