자바 ORM 표준 JPA 프로그래밍(책),
자바 ORM 표준 JPA 프로그래밍 - 기본편(인프런강의)
을 공부하면서 정리하는 글입니다.
JPA에서 객체와 테이블을 매핑하는 방법은 두가지가 있다. 첫번째는 XML을 사용하는 것 두번째는 어노테이션을 사용하는 것이다. 이번 글에서는 어노테이션 사용법에 대해서만 알아볼 것이다.
테이블과 매핑할 클래스는 반드시 엔티티 어노테이션을 붙여야 한다. 여기서 주의해야 할 점은, JPA는 엔티티 객체를 생성할 때 기본 생성자를 사용한다는 것이다. 만약에 생성자가 하나도 없을 경우에는 자동으로 만들어주기도 하지만, 기본적으로 사용되는 생성자가 한개 이상 있을 경우에는 기본 생성자를 직접, 수동으로 만들어주어야 한다.
생략해도 되는 어노테이션이다. 하지만 테이블 어노테이션을 생략하게 되면 엔티티명이 테이블명으로 그대로 사용된다.
persistence.xml 에서 hibernate.hbm2ddl.auto 속성을 활용하면 프로그램을 실행할 때 자동으로 데이터베이스의 스키마가 생성되도록 할 수 있다. 코드는 아래와 같다.
<property name="hibernate.hbm2ddl.auto" value="create" />
여기서 속성은 create, create-drop, update, validate, none이 있다. create 와 create-drop은 둘 다 생성시점에 drop을 하고 create를 하는 점은 동일하다. 하지만 create-drop의 경우에는 프로그램 종료 시 다시 한번 drop을 하는 차이점이 있다. validate와 none의 경우에는 실질적으로 데이터 베이스를 변경하지 않기 때문에 운영서버에서 사용해도 되지만, 나머지 특히 create가 포함된 속성은 개발 초기 단계가 아니라면 절대 사용해서는 안 된다.
JPA가 제공하는 데이터베이스 기본 키 생성전략은 총 2가지이다.
직접 할당의 경우 엔티티를 em.persist()로 영속시키기 전에 기본키 값을 직접 할당해주어야 한다. 그리고 이 Id값이 없을 경우에는 예외가 발생하기 때문에 실무에서 쓰기 불편하다.
기본 키 생성을 데이터베이스에 위임하는 전략이다. 주로 MySQL, PostgreSQL 등에 사용한다. 예를 들어, MySQL에서 IDENTITY 전략을 사용하게 되면 AUTO_INCREMENT로 기본 키 생성 전략이 취해진다. 사용방법은 아래와 같다.
@Id
@GeneratedValue(strategy=GenerationType.IDENTITY)
private Long Id;
여기서 가장 중요하게 기억해야 할 점은 트랜잭션을 지원하는 쓰기 지연이 불가능하다는 점이다. 이유는 이렇다. 객체를 생성하고 em.persist()를 호출하게 되면 엔티티가 영속상태가 되는데 이 때 반드시 식별자가 필요하다. 하지만 IDENTITY 전략을 쓰고 있을 경우, 식별자는 DB에 저장되는 시점에 생성이 된다. 이 문제를 해결하기 위해 JPA는 IDENTITY 전략을 사용할 때, em.persist()를 호출하는 즉시 INSERT 쿼리가 나가게 해 둔 것이다.
기본키는 기본적으로 1. NULL이 되지 않을 것, 2. 유일할 것, 3. 변하지 않을 것. 이 세 가지 기준을 만족해야 한다. 그리고 이 기준을 만족하는 것 중에 선택할 수 있는 것이 크게 2가지가 있다.
첫째는 자연키이고 둘째는 대리키이다. 하지만 실무에서는 대리키를 사용하는 것이 장기적인 관점에서 보았을 때 훨씬 이득이다. 자연키의 경우 비즈니스적으로 의미가 있기 때문에, 추후에 변동이 되는 로직이 생기거나 법, 정책 상의 이유로 기업에서 저장할 수 없게 되는 등의 조치가 취해질 수가 있다. 그때 가서 수십, 수백개의 테이블과 이미 매핑되어 있는 키 값을 변경하는 것은 대단히 골치아픈 일이 될 수 밖에 없다.
컬럼 어노테이션을 쓰면 객체 필드를 테이블 컬럼에 매핑해준다. 엔티티 어노테이션을 붙인 클래스의 각 필드마다 컬럼 어노테이션을 붙이다 보니 이런 생각이 들었다. 컬럼 어노테이션을 안 붙이면 어떻게 될까? 그러면 컬럼 속성의 기본값이 적용이 되고, 물론 테이블 컬럼에는 생성이 된다. 하지만 여기서 주의해야할 점이 있다. 예를 들어 데이터 타입이 int인 경우, 자바 기본 타입이기 때문에 null을 입력할 수가 없다. 하지만 데이터 타입이 Integer인 경우 null값이 허용이 된다. 그런데 컬럼 어노테이션의 경우 nullable=true가 기본값이다. 그렇기 때문에 데이터 타입이 int인 필드에 컬럼 어노테이션을 생략하면 기본값이 그대로 적용되어 문제가 발생할 수 있다.(객체와 테이블 속성이 다르다) 그래서 이러한 경우에는 꼭 어노테이션을 달아주고 nullable=false로 지정을 해주는 것이 안전하다.
자바의 enum 타입을 매핑하기 위해 붙이는 어노테이션이다. Enumerated 어노테이션에는 두가지 속성이 있다. 첫째는 EnumType.ORDINAL 둘째는 EnumType.STRING 이다. 하지만 앞의 ORDINAL 속성은 사용하지 않는 것이 좋다. 왜냐하면 이는 정의된 순서대로 0부터 숫자로 표시가 되는데 Enum이 더 추가되고 순서가 변경될 경우 기존의 값들과 뒤섞여서, 추후 문제가 생길 수 있다. 예를 들면 이렇다.
public enum UserType {
ADMIN, USER
}
이렇게 고객의 종류가 딱 2개 즉 관리자, 일반 고객으로만 나뉘어서 운영하는 초기 서비스가 있다고 치자. 그러면 고객 타입이 일반 유저일 때, DB에는 그 값이 1로 들어가 있을 것이다. 하지만 서비스의 규모가 점점 커져서 고객 등급이 세분화되고 그 등급에 맞는 혜택과 권한이 주어질 경우 ENUM 값이 추가되게 된다. ADMIN보다 권한은 부족하지만 일반 유저보다는 권한이 큰 LEADER라는 등급이 생겼다고 했을 때 ENUM 순서를 ADMIN, LEADER, USER로 할 경우 이제 USER 값은 2로 저장이 되고 LEADER가 1로 저장이 되는 사태가 발생한다. 이러한 사고를 미연에 방지하기 위하여, EnumType은 꼭 String으로 저장하자.
이 어노테이션을 붙인 필드는 매핑을 하지 않는다. 데이터베이스에 저장하지도, 조회하지도 않는다.