과거에는 엔티티 매핑에 XML 을 사용해왔지만 최근 트랜드는 어노테이션을 활용하는 것이 대세이다. JPA는 다양한 어노테이션들을 제공하고 있기 때문에 엔티티와 테이블을 정확히 매핑하기 위해선 매핑 어노테이션들을 제대로 숙지하고 있어야한다.
어노테이션 매핑의 경우 기본적으로 4가지로 분류를 할 수 있다.
이번 장에서는 객체와 테이블 매핑, 기본 키 매핑, 필드와 컬럼 매핑 에 대해 알아보겠다.
앞에서 JPA는 SQL 매퍼와 달리 나무가 아닌 숲을 바라본다고 했었다. 그러기 위해 단순히 필드만이 아닌 객체 전체를 JPA 가 인지하고 있어야하는데 이때 필수적으로 필요한 어노테이션이 바로 @Entity 이다.
1) name 속성을 통해 이름을 부여할 수 있다.
속성을 등록하지 않으면 그냥 클래스 이름을 사용한다.
(ex,@Entity(name ="member"))2) 기본 생성자가 필수 이다.
public 혹은 protected 생성자만 가능하다.3) final클래스, enum, interface, inner클래스 는 사용 불가능하다
4) 저장할 필드에 final을 사용하면 안 된다.
@Table 은 생략이 가능하지만 그러려면 엔티티 클래스 이름과 테이블 이름이 같아야한다.
이전까지의 실습에서는 우리가 직접 DB에 엔티티에 맞춰 테이블을 생성해주었지만 JPA는 이것들의 자동생성을 지원해준다.
persistence.xml 에 옵션 추가
![]()
〈property name="hibernate.hbm2ddl.auto" value="create" />위와 같이 옵션을 추가해주고 어플리케이션을 실행시키면 이제 JPA가 자동으로 DDL 을 전송해 테이블들을 생성해준다.
(물론 이미 사용할 엔티티들이 전부 매핑 되어있어야한다.)
![]()
DDL문이 자동 생성 되어 나가는것을 확인 할 수 있으며
테이블 생성 완료!
hibernate.hbm2dl.auto 에는 다음과 같은 속성들이 있다.

※※ 주의 사항
create, create-drop, update 의 경우 기존의 테이블을 삭제 혹은 수정하는 DLL 을 생성하는데, 만일 실제 운영 DB에 다음과 같은 옵션을 사용하게 되면 이미 돌아가고있는 서버의 DB에 즉각 반영되기 때문에 대형사고가 발생하게된다.그러므로 개발환경에 따른 추천전략은 다음과 같다.
![]()
필드에 제약 조건 걸기
Column(nullable = false, length = 10)
private String username;
: NOTNULL 조건 + 길이 10
엔티티 전체에 제약 조건 걸기
@Table(uniqueConstraints = {@UniqueConstraint(
name = "NAME_AGE_UNIQUE",
columnNames = {"NAME", "AGE"}
)})
public class Member{
@Id
@Column(name = "id")
private String id;
@Column (name = "name")
private String username;
private Integer age;
}
이렇게 제약조건을 SQL이 아닌 자바 코드로 직접 걸어줄 수도 있다.
하지만 주의해야할 점은 이 자바코드로 제약조건이 적용되려면 DDL 자동 생성 옵션을 사용해야지만 적용이된다.
즉, DDL 을 자동 생성하는것이 아닌 직접 SQL을 작성한다면 사용할 이유가 크게 없다. 하지만 다른 개발자들에게 제약 조건들을 명시 해줌으로써 명확성과 편리성을 높여준다는 의의가 있다. (근데 그러면 그냥 주석이 낫지 않나?)
우리는 각 테이블에 PK 를 할당할 때 크게 2가지 전략을 사용한다.
1) 직접 할당
2) 자동 생성
직접 할당 은 말그대로 개발자가 엔티티 객체를 생성할때 직접 세터나 생성자를 통해 식별자 필드에 값을 주입해주는 것이고,
자동 생성 은 DB에 데이터가 INSERT 될때마다 DB가 자동으로 id값을 생성하게끔 하는 전략이다.
※주의
키 생성 전략을 사용 할 때는persistence.xml에<property name="hibernate.id.new_generator_mappings" value="true"/>속성을 반드시 추가해야한다. 추후
SEQUENCE전략의 성능최적화시 필요하다.
Member member = new Member();
member.setId(1);
em.persist(member);
영속성 컨텍스트에 저장하기 전 미리 id를 주입을 해주고 있다.
@GeneratedValue(strategy = GenerationType.IDENTITY)
ID 생성을 DB에게 위임하는 전략으로 MySQL 의 AUTO_INCREMENT 가 대표적이다.
다른 DB들로는 PostgreSQL, SQL Server, DB2 가 있다.
@Entity
public class Member {
@Id @GeneratedValue(strategy = GenerationType.IDENTITY)
@Column(name = "MEMBER_ID")
private Long id;
private String name;
private String city;
private String street;
...
}
테스트 결과 2개의 Member 엔티티가 생성이되었고 DB에 저장되었다.
엇,,! 뭔가 이상하지 않은가? 분명 영속성 컨텍스트에 들어가기 위해서는 반드시 식별자가 존재해야한다고 앞시간에 설명을 했었다.
그런데 어떻게 식별자 없이 영속성 컨텍스트에 들어간거지?
여기에 비밀은 바로 INSERT 쿼리이다.
흐름 정리
1) 엔티티 매니저는 엔티티 객체의 기본키 생성 전략을 확인한다.2) 만일
IDENTITY일 경우, 매니저는 식별자값을 얻기위해 DB에INSERT쿼리를 전송하게 되고 DB는 id값을 생성해준다.3) 그럼 JPA는 생성된 id값을 다시 전달 받게 되는데 이때
SELECT문이 따로 출력되지 않는다.4) 왜냐하면 내부적으로 JDBC가 id 값을 받아오기 때문이다. (
Statement.getGeneratedKeys()를 통해 id값을 받아오게 된다.)
@GeneratedValue(strategy = GenerationType.SEQUENCE)
Oracle 에서는 AUTO_INCREMENT 대신 SEQUENCE 라 불리는 특별한 DB 오브젝트를 사용해 유일한 값을 순서대로 생성한다.
@Entity
@SequenceGenerator(
name = "MEMBER_SEQ_GENERATOR",
sequenceName = "MEMBER_SEQM, //매핑할 데이터베이스 시퀀스 이름
initialValue = 1, allocationSize = 1)
public class Member {
@Id
@GeneratedValue(strategy = GenerationType.SEQUENCE,
generator = "MEMBER_SEQ_GENERATOR")
@Column(name = "MEMBER_ID")
private Long id;
private String name;
private String city;
private String street;
...
}
사용 코드는 동일하지만 출력결과가 조금 다르다.

SEQUENCE 전략의 경우 IDENTITY 와 전략이 살짝 다르다.
흐름 정리
1) 엔티티 매니저는 엔티티 객체의 기본키 생성 전략을 확인한다.2) 만일
SEQUENCE일 경우, 매니저는 식별자 값을 얻기 위해SELECT BOARD_SEQ.NEXTVAL FROM DUAL쿼리를 날려서 sequence값을 조회한다.
3) JPA는 sequence 값을 전달 받은다음, 해당 값을 id 값으로 사용해 다시 DB에 저장한다.(
INSERT쿼리 전송)
JPA 사용의 장점 중 하나가 바로 캐싱 을 통한 성능 향상이였다. 쿼리들을 여러개 모아놨다가 커밋시에 한번에 flush 함으로써 I/O 단계를 줄일 수 있었다.
하지만, id를 자동생성하게 될 경우 JPA는 id 값을 알기위해 매번 INSERT 쿼리 혹은 SELECT 쿼리를 전송하게된다.
이는 성능이 중요한 프로그램일 경우 자칫 문제가 될 수 있다. 그래서 우린 성능 최적화를 위해 allocationSize 옵션을 사용해볼거다.
시퀀스생성기는 allocationSize 에 따라 한번 호출될 때마다 증가하는 수가 달라진다.
JPA의 성능 최적화
1) allocationSize = 50 을 할당했다고 가정하자.2) JPA는 제일 처음에는 시퀀스에 접근하여 최초 시퀀스값을 조회해온다. (DB의 시퀀스는 51까지 숫자가 증가한다)
3) JPA는 1~50 까지의 숫자를 메모리에 올려놓고 두번째 호출부터는 DB(시퀀스)에 접근하지않고 메모리의 값을 통해 id를 할당해준다.
4) 만일 51이 되면 다시 시퀀스에 접근해 시퀀스 값을 100으로 증가 시켜놓고 메모리에는 51~100 까지 할당받는다.
5) 만약 4) 이전에 다른세션(세션2)이 들어와 id값을 할당 받기 위해 시퀀스에 최초 접근한 경우 해당 세션은 nextVal인 51을 할당 받게 된다. (DB시퀀스는 101 이됨)
6) 그리고 다시 세션1이 50까지 다 할당 받고 새로 시퀀스값을 받기위해 시퀀스에 접근하게 되면 세션 1은 (101~150) 을 메모리에 할당 받고 시퀀스는 151 로 증가된다.
말이 어려우니 그림으로 설명하자면
이 성능최적화 방식의 주의 할점은 다음과 같다.
주의
1) persistence.xml 에 속성 추가 해주기<property name="hibernate.id.new_generator_mappings" value="true"/>2) DB로 직접 insert 할 경우 id값이 확 커져버림
JPA를 사용하면 JPA가 알아서 +1 씩 메모리에서 꺼내 할당해주지만, DB로 직접 데이터를 집어넣으면 seq 값이 사이즈만큼 증가함.
@GeneratedValue(strategy = GenerationType.AUTO)
@Entity
public class Member {
@Id
@GeneratedValue(strategy = GenerationType.AUTO)
@Column(name = "MEMBER_ID")
private Long id;
private String name;
private String city;
private String street;
...
}
사용 DB에 따라 자동으로 기본키 생성 전략을 취한다. 아직 기본키 생성 전략을 확정하지않은 프로젝트 초기단계에 사용하면 좋다.
결론부터 말하면 EnumType.STRING 을 사용해야한다.
ORDINAL 은 enum의 순서를 DB에 저장하기 때문에 만일 새로운 enum이 추가 될경우 DB에 저장된 모든 순서들을 바꿔줘야한다.
반면 STRING 을 사용할 경우 애초에 enum의 문자열 값이 저장되기 때문에 순서와 상관이 없으므로 새로운 enum이 추가된다고 해서 문제가 되지않는다.
DB에는 문자열을 저장할때 길이가 제한되는데 간혹 굉~장히 긴 문자열을 저장해야할 때가 있다. 또는 이미지 파일같이 경우 엄청나게 긴 바이트 코드 일 경우 일반적인 varchar 나 char 로 저장을 할 수가 없다.
이때 @Lob 을 사용하면 된다.
CLOB : 필드 타입이 문자일경우,
BLOB : 나머지
해당 어노테이션이 등록된 필드는 매핑되지 않는다. 즉, DB에 저장되지도, 조회 하지도 않는다.
본 포스트는
김영한의 자바 ORM 표준 JPA프로그래밍 기본 강의 및 도서를 참고하여 정리했습니다.