JPA에서 가장 중요한 일은 엔티티
와 테이블
을 정확히 매핑하는 것입니다.
- 객체와 테이블 매핑:
@Entity
,@Table
- 필드와 컬럼 매핑:
@Column
- 기본 키 매핑:
@Id
- 연관관계 매핑:
@ManyToOne
,JoinColumn
@Entity가 붙은 클래스는 JPA가 관리하는 클래스입니다. JPA를 사용해서 테이블과 매핑할 클래스는 @Entity
는 필수입니다. 그리고 주의사항이 있는데 아래와 같습니다.
- 기본 생성자는 필수입니다. (파라미터가 없는 public 또는 protected 생성자)
- final 클래스, enum, interface, inner 클래스에는 사용할 수 없습니다.
- 데이터베이스에 저장할 필드에 final을 사용하면 안됩니다.
간단한 코드를 실행해보면 위와 같이 테이블 이름이 MBR
인 것을 볼 수 있습니다.
간단하게 요구사항을 보면서 JPA에서는 필드와 컬럼을 어떻게 매핑
하는지 간단하게 알아보겠습니다.
- 회원은 일반 회원과 관리자로 구분해야 합니다.
- 회원 가입일과 수정일이 있어야 합니다.
- 회원을 설명할 수 있는 필드가 있어야 합니다. 이 필드는 길이 제한이 없습니다.
@Entity
public class Member {
@Id
private Long id;
@Column(name = "name") // DB에는 name 컬럼으로 사용하고 싶을 때
private String username;
private Integer age;
@Enumerated(EnumType.STRING) // Enum을 사용하고 싶을 때
private RoleType roleType;
@Temporal(TemporalType.TIMESTAMP)
private Date createdDate;
@Temporal(TemporalType.TIMESTAMP)
private Date lastModifiedDate;
@Lob // VARCHAR를 넘어서는 큰 용량을 필요로 할 때
private String description;
// Getter, Setter 존재
}
위와 같이 엔티티와 테이블을 매핑한 후에 실행을 해보겠습니다.
그러면 위와 같이 JPA에서 테이블을 만들어주는 것을 볼 수 있습니다. @Enumerated
는 VARCHAR
로 생성된 것을 볼 수 있고, @Lob
은 clob이라는 타입으로 생성된 것을 볼 수 있습니다.
첫 번째로 컬럼에 위와 같이 nullable = true or false
값을 줄 수 있는데 이름에서 유추할 수 있듯이 false를 주면 NULL이 불가능하다는 뜻입니다. 이 상태로 실행하면 아래와 같은 DDL 문을 JPA에서 만들어줍니다.
위와 같이 not null
인 상태의 필드로 JPA가 생성해줍니다.
위와 같이 컬럼의 length도 줄 수 있습니다. length=30 으로 하고 실행하면 아래와 같이 VARCHAR(30)인 필드를 만들어줍니다.
Enumerated 어노테이션을 보면 위와 같이 default가 ORDINAL
인 것을 볼 수 있습니다. (하지만 ORDINAL 사용하지 말기!!)
- EnumType.ORDINAL: enum 순서를 데이터베이스에 저장합니다.
- EnumType.STRING: enum 이름을 데이터베이스에 저장합니다.
만약에 위와 같이 Enumerated
를 default ORDINAL로 놓고 저장했을 때 어떻게 저장되는지 확인해보겠습니다.
그러면 JPA에서는 순서를 기반으로 저장하기 때문에 RoleType을 INTERGER로 생성한 것을 볼 수 있습니다.
그리고 데이터베이스에도 확인을 해보면 순서가 디비에 저장이 된 것도 확인할 수 있습니다.
위와 같이 예시는 2개의 데이터만 저장이 되있지만 만약에 한 100개의 데이터가 저장되어 있다고 생각해보겠습니다.
이런 상태에서 요구사항이 바뀌어 RolyType에 GEUST가 추가되었다면 어떻게 될까요?
그리고 위와 같이 GUEST
를 저장하게 되면 아래와 같이 RoleType이 섞여버려 0이 무엇인지 구분하기 힘들어지는 상황이 되어버리는데요.
즉, 데이터 양이 많다면 돌이키기 상당히 힘든 문제가 발생하게 될 것입니다.
그래서 Enumerated
를 사용하게 된다면 반드시 EnumType.STRING
을 사용하여야 합니다.
그러면 ROLETYPE
도 GUEST
인 STRING으로 저장되는 것을 볼 수 있습니다. 이러면 나중에 요구사항이 바뀌더라도 문제가 될일이 없기 때문에 이렇게 사용하는 것을 권장합니다.
날짜 타입(java.util.Date, java.util.Calendar)를 매핑할 때 사용합니다. 참고로 자바 8 이후부터 LoadDate
, LocalDateTime
을 사용할 때는 생략 가능합니다.
데이터베이스 BLOB, CLOB 타입과 매핑됩니다. 매핑하는 필드 타입이 문자면 CLOB 매핑, 나머지(byte) 같은 경우면 BLOB으로 매핑이 됩니다.
자동 생성은 @GeneratedValue
어노테이션을 사용합니다.
IDENTITY
: 데이터베이스에 위임(MYSQL)SEQUENCE
: 데이터베이스 시퀀스 오브젝트 사용(ORACLE)AUTO
: 데이터베이스 방언에 따라 자동 지정됩니다.
위와 같이 IDENTITY
속성을 준 후에 JPA가 만들어주는 DDL 문을 보겠습니다.
현재는 H2 Database로 방언이 되어 있기 때문에 위와 같이 Identity
라고 생성되는 것을 볼 수 있습니다. 그러면 MySQL로 한번 바꿔서 다시 실행해보겠습니다.
MySQL 경우 AUTO_INCREMENT
로 생성이 되는 것을 확인할 수 있습니다.
보통 트랜잭션이 커밋하는 시점에 INSERT 쿼리를 실행하지만, IDENTITY 전략은 예외적으로 em.persist()
를 할 때 바로 INSERT 쿼리가 실행됩니다. 그 이유가 무엇일까요?
1차 캐시에 키 값은 해당 엔티티의 PK 값이어야 하는데 IDENTITY는 INSERT 쿼리가 실행이 된 이후에야 PK 값을 알 수 있기 때문에 예외적으로 em.persist()
를 했을 때 바로 INSERT 쿼리가 실행되는 것입니다.