[JPA] 매핑 어노테이션이 뭔데??

윤들윤들·2021년 1월 6일
0
post-thumbnail

Spring 을 사용하기만해도 어노테이션이 많은데 JPA를 쓰기 위해서는 어떤 어노테이션이 필요할까??

Entity Class를 생성하면서 가장 많이 사용하고 또한 필수로 알아야 하는 어노테이션 부터 알아보도록 하겠습니다.


😀 JPA 대표 어노테이션

직접 개발하면서도 느꼈던 가장 많이 사용했던 어노테이션이자 대표 어노테이션은 Entity, Table, Id, Column 입니다. 이 외에도 다양한것이 있지만 천천히 알아가보도록 하죠



🎁 @Entity

@Entity 는 클래스를 테이블과 매핑시키기 위해 필수로 사용하는 어노테이션이다.
해당 어노테이션이 붙은 클래스는 JPA가 관리하는 것이며, 개발을 하면서 엔티티라고 흔히 부르며 사용합니다.

각각의 어노테이션은 다양한 속성을 가지고 있을겁니다. @Entity은 어떤 속성을 가지고 있는지 봐보겠습니다.

@Entity 에는 name 이라는 속성이 존재합니다.

name : JPA에서 사용할 Entity의 이름을 지정합니다. 보통 속성을 지정하고 싶지 않을 경우가 있는데, 지정하지 않는다면 클래스의 이름을 기본값으로 사용합니다. 주의할 사항은 다른 패키지에 동일한 이름을 같는 Entity 클래스가 있다면 name 속성을 사용하여 Entity의 이름이 중복되지 않게 하도록 해야한다.


💡 @Entity를 사용하기전에 주의해야할 점!

  1. @Entity를 사용하기 위해서는 해당 클래스의 기본 생성자는 필수 입니다
  2. final 클래스, enum, interface, inner클래스에서는 사용할 수 없다.
  3. 저장할 필드에 final을 사용하면 안된다.

JPA는 default Constructor을 활용하여 객체를 생성하고 Reflection을 사용하여 값을 매핑하도록 내부 구현되어있기 때문이다.
(처음 사용할때 해당 내용을 확인하지 않고 삽질한 경험이 있습니다, 파라미터가 있는 생성자를 사용해야 한다면 반드시 기본생성자를 직전 생성하도록 하자.)

final 클래스, enum, interface, inner클래스에 @Entity 어노테이션을 사용할 수 없는 이유는 @Entity 에서는 추후에 proxy등을 사용하는데 proxy를 사용하기 위해서는 해당 class를 상속받아서 사용해야 하는데 final, enum, interface, inner를 사용항면 상속이 불가능하기 때문이다.

필드에 final을 사용할 수 없는 이유는 reflection을 사용하면 대게 생성자 or setter를 사용하는데 final을 사용하면 setter를 사용할 수 없기 떄문이라고 한다.
참고 joont92


🎁 @Table

@Table 어노테이션은 엔티티와 매핑할 테이블을 지정한다. 해당 어노테이션을 생략하면 엔티티의 이름을 테이블의 이름으로 사용한다.

🎈 @Table의 속성

name : 매핑할 테이블의 이름, default로는 엔티티의 이름을 사용한다.
uniqueConstraints : DDL 생성 시에 유니크 제약조건을 만든다. 2개 이상의 복합 유니크 제약조건도 만들 수 있다. 해당 기능은 스키마 자동생성 기능을 사용해 DDL을 만들떄만 사용된다.
catalog : catalog 기능이 있는 데이터베이스에서 catalog를 매핑한다.
scheme : scheme기능이있는 데이터베이스에서 scheme를 매핑한다.

개인적으로 catalogscheme는 사용해 본적이 없어서 자세히 설명을 못했습니다.ㅠㅠ


🎁 @Id

Entity에서의 식별자값은 어떻게 보면 해당 Entity를 구분하기 위한 필수 값이라고 볼수있다 이러한 필수 값을 주기위해 JPA 에서는 @Id 을 사용하여 구분하며, 해당 필드는 Database의 Primary Key와 매핑됩니다.

식별자 값은 primitive 타입보다는 wrapper 타입을 추천한다

🎈 @Id의 기본 키 생성 전략!

자동 생성은 다음과 같습니다. 자동 생성옵션이 한가지가 아닌 이유는 데이터베이스는 벤더마다 지원하는 방식이 다르기 때문에 JPA에서는 다양항 기본 키생성방식을 지원한다.

직접 할당
직접 할당같은 경우는 Application에서 기본 키를 직접 할당한다.
자동 생성
IDENTITY : 기본 키의 생성을 데이터베이스에게 맡긴다.
SEQUENCE : 데이터베이스 시퀀스를 사용해 기본 키를 할당한다.
TABLE : 기본 키의 생성을 키 생성 테이블을 사용한다.

위와 같이 기본 키를 직접 할당 하고싶을 경우에는 @Id 만사용하면 되고 아니다, 자동 생성에 맡기겠다라고 하시는분들은 @GeneratedValue 를 추가하고 원하는 키 생성 전략을 선택하여 넣어주면 됩니다.

기본 키 직접 할당 전략에서 식별자 값 없이 저장하면 예외가 발생하는데 . 하이버네이트를 구현체로 사용하면 JPA 최상위 예외인 PersistenceException 예외가 발생하는데 , 내부에 하이버네이트 예외인 IdentifierGenerationException예외를 포함하고 있다.

☝ Identity 전략

Identity 전략은 기본 키 생성을 데이터베이스에 위임하는 전략입니다. 주로 MySQL, PostgreSQL, SQL Server 등에서 사용한다.

@Entity
class MemberEntity {
	@Id
    	@GeneratedValue(strategy = GenerationType.IDENTITY)
        private Long id
}

Identity 전략은 데이터를 데이터베이스에 Insert한 후 기본 키 값을 조회할 수 있는데, 그렇게 되면 데이터베이스를 추가로 조회하여 Insert된 이후의 식별자 값을 가져와야 한다. 하지만 JDBC3에 추가된 Statement.getGeneratedKeys()를 사용하면 데이터를 저장함과 동시에 생성된 기본 키 값을 얻어 올 수 있다.

☝☝ Sequence 전략

Sequence 전략은 데이터베이스 시퀀스라는 유일한 값을 순서대로 생성하는 데이터베이스 오브젝트를 기본 키로 생성한다. 해당 전략은 Oracle, PostreSQL, H2에서 사용한다. Class 또는 GeneratedValue쪽에 어노테이션을 사용한다.

@Entity
@SequenceGenerator(
	name = "MEMBER_SEQ_GENERATOR",
    	sequenceName = "MEMBER_SEQ" // 매핑할 DB 시퀀스 이름
        initialValue = 1, allocationSize = 1
)
class MemberEntity {
	@Id
    	@GeneratedValue(strategy = GenerationType.SEQUENCE,
        		generator = "MEMBER_SEQ_GENERATOR)
        private Long id
}

위의 코드에서 Sequence를 사용하기 위해 @SequenceGenerator를 활용하여 MEMBER_SEQ_GENERATOR 라는 이름을 갖는 시퀀스를 생성했다. 또한 sequenceName속성을 사용하여 해당 이름으로 지정하였는데 해당 속성을 사용하면 실제 DB에서 사용하고 있는 시퀀스와 매핑한다.

@SequenceGenerator
name : 식별자 Generator의 이름을 지정, 해당 속성은 필수 값입니다.
sequenceName : 실제 데이터베이스에 등록되어 있는 시퀀스의 이름을 지정한다. default 값은 hibernate_sequence

allocationSize : 시퀀스를 한번 호출할때 증가하는 수 , default 값은 50입니다. allocationSize는 성능 최적화에 사용됩니다.

💡allocationSize 알아두면 좋은 내용!
allocationSize에 따라서 설정한 값만큼 시퀀스를 증가시킨다음 해당 시퀀스만큼 메모리에서 식별자를 할당한다. 즉 50 이라고 한다면 1 ~ 50까지는 메모리에서 식별자 값을 할당한다는 말이다. 50이 넘어가면 51을 조회하면서 시퀀스값을 100으로 증가시키고 51 ~ 100을 메모리에서 관리한다.

catalog, scheme : 데이터베이스 catalog, scheme이름 (저는 아직까지 사용해본 적이 없습니다.)

Sequence 전략은 Identity와 똑같은 기능을 하는거 아니야?? 라고 생각할 수 있습니다. 하지만 Sequence 전략은 persist()를 통해 저장하기 전에 데이터베이스에서 먼저 시퀀스를 조회한다음 해당 식별자를 엔티티에 저장한다음에 해당 엔티티를 영속성 컨텍스트에 저장합니다. 이후 트랜잭션이 종료가 되면 해당 엔티티를 데이터베이스에 저장한다.

☝☝☝ Table 전략

Table 전략은 키 생성 전용 테이블을 만들고 이름과 값으로 사용할 컬럼을 만들어 데이터 베이스 시퀀스를 흉내내는 전략이다, 이 전략은 테이블을 사용하기 때문에 모든 데이터베이스에서 사용이 가능하다.

Table 전략또한 Sequence전략처럼 TableGenerator를활용하여 연결해줄 수 있다.


☝☝☝☝ Auto 전략

데이터베이스의 종류는 다양하고 기본키를 생성하는 방식에도 다양한 방식이 있다. GenerationType.AUTO 를 사용하면 설정에서 선언한 데이터베이스 방언에 따라 Identity, Sequence, Table 전략중 하나를 자동으로 선택해 준다.

기본적으로 GeneratedValue.strategy의 default 값은 AUTO 입니다.

Auto 전략을 사용하면 데이터 베이스를 변경해도 해당 코드를 수정할 필요가 없다는 특징이 있다. 개발 초기에 사용하기에는 좋은듯하다.


🎁 @Column

@Column은 클래스의 필드와 데이터베이스의 컬럼을 매핑할때 사용하는 어노테이션이다.
실제로 개발할때 가장 많이 사용하기도 하고 다양한 기능이 있다. name , nullable 옵션을 많이 사용한다.

name : 필드와 매핑할 테이블의 컬럼 이름, default는 필드의 이름

insertable : 엔티티 저장시 해당 필드도 같이 저장한다, default true이며 false로 설정시 데이터베이스에 저장하지 않는다.

updatable : 엔티티 수정시 해당 필드도 같이 수정한다. default true 이며 false로 설정시 업데이트 하지 않는다.

table : 하나의 엔티티를 두 개 이상의 테이블에 매핑할 때 사용. (거의 사용 X)

nullable : null 값의 허용 여부를 설정한다, default는 true 이며 false로 작성시 not null제약조건이 붙는다.

unique : 컬럼에 간단히 유니크 제약조건을 걸 때 사용한다.

columnDefinition : 컬럼 정보를 직접 설정 가능

length : 문자 길이 제약조건 속성, String 타입에만 사용한다, default 255

💡 @Column 을 생략하였을때 발생할 수 있는 문제!!
// @Column 생략, Primitive Type
int field;
field integer not null // @Column 생략시 not null 조건이 붙음.
// @Column 생략, Wrapper Type
Integer field;
field integer // @Column을 생략해도 Wrapper Type이면 null을 허용함.
// @Column 추가 , Primitive Type
@Column
int field
field integer // @Column 붙이면 primitive라도 null 을 허용하게 된다.
/** Primitive Type에 @Column을 붙였다면 nullable=true가 
default 이기 때문에 nullable = false를 지정해주는것이 안전하다.*/

🎁 @Enumerated

@Enumerated는 자바의 enum Type을 매핑하기 위해 사용한다.
속성으로는 value 속성을 가지고 있는데 순서를 저장할 것인지, String으로 이름을 저장할 것인지 설정할때 사용한다.
value : EnumType.ORDINAL, EnumType.STRING, 2가지를 지원하며 default는 ORDINAL이다.

enum Permission {
	BRONZE, SILVER, GOLD
}

@Enumerated(value=EnumType.String)
private Permission permission;

위와 같이 사용할 수 있다.

💡 EnumType.ORDINAL

ORDINAL은 enum에 정의된 순서대로 숫자를 적용합니다. (0부터 시작)
ORDINAL을 사용하였을때는 숫자로 저장되기 때문에 저장되는 데이터의 크기가 작지만 Enum class의 중간에 타입이 추가되게 된다면 순서가 변경되어 데이터가 꼬일수 있기 때문에 권장하지 않습니다.

💡 EnumType.STRING

STRING 은 말 그대로 enum class의 Type을 문자열로 데이터베이스에 저장합니다. ORDINAL에 비해 중간에 순서가 변경되거나 추가되도 안전합니다. 하지만 ORDINAL과 반대로 데이터베이스에 저장할때 데이터의크기가 큽니다.
그래도 EnumType.STRING을 사용하는것을 권장합니다!


profile
Front&BaackEnd를 재미있게 공부하고싶은 개발자 YundleYundle

0개의 댓글