본문 바로가기
스터디/JPA

[JPA] 객체와 테이블 매핑 (엔티티 매핑)

by 삠빰삠뽕 2023. 10. 28.

JPA 사용하는데 가장 중요한 것은 엔티티와 테이블을 정확하게 맵핑하는 것, 이는 매핑 어노테이션을 어느 정도 숙지가 필요하다는 거다.

  • 객체와 테이블 맵핑 : @Entity, @Table
  • 기본키 맵핑 : @Id
  • 필드와 컨럼 맵핑 : @Column
  • 연관관계 맵핑 : @ManyToOne, @OneToMany, @OneToOne, @JoinColumn

 

객체와 테이블 맵핑

@Entity

JPA 를 이용해서 테이블을 맵핑할 클래스에는 필수로 넣어야하는 애노테이션.
속성값 name을 지정하지 않은 클래스명을 그대로 사용한다. JPA가 엔티티 객체를 생성할 때 기본 생성자를 사용하기 때문에 기본 생성자는 반드시 있어야 한다.

* 자바는 생성자가 아예 없는 경우 자동으로 기본 생성자를 만드느데 만일 다른 생성자가 있다면 기본 생성자를 직접 만들어 주어야 한다.

 

@Table

엔티티와 맵핑 될 테이블을 지정하기 위한 애노테이션.
JPA 는 DB스키마 자동 생성 기능을 지원한다. 앞서서 @Entity @Table 과 같은 맵핑 정보를 읽고 스키마를 생성하는데 운영환경이 아닌 테스트 또는 개발 환경에서 사용을 권장한다 (DDL 을 완벽하게 제공하지 않을 수도 있기 때문에)

 

기본키 맵핑

@Id : 테이블의 기본키 pk 를 엔티티의 식별자 값으로 맵핑하는 애노테이션

 

@GeneratedValue기본키 값의 자동 생성 전략을 선택 할 수 있다. 만일 직접 기본키를 생성하는 경우엔 해당 애노테이션은 붙여주지 않아도 된다. 그렇다면 왜 굳이 자동 생성 전략을 선택 할 수 있게하느냐하면 DB 벤더 사 마다 지원하는 방식이 다르기 때문에 DB 벤더 사에 맞는 전략을 선택할 수 있도록 다양한 전략이 생기게 된 것이다.

* @Id 로 지정된 필드에만 사용가능한 것은 아니나, 기본키 이외의 컬럼에 다가 추가하여도 자동으로 값 증가를 해주지는 않는 듯함(실제 테스트 한 경우 그러했음)

 

- IDENTITY 전략
기본 키 생성을 DB에 위임하는 전략. Mysql 에서 auto_increment 와 같이 실제 DB 에 데이터가 insert 될 때 기본 키 값을 얻을 수 있다.
위의 이유로 실제 JPA 가 이 전략을 사용하면 기본 키 값을 얻기 위해 DB를 추가로 조회하게 되는데, JPA 내부에서 DB에 한 번 접근하여 insert 와 생생성된 기본 키값을 받을 수 있도록 JDBC3 에서 만든 api를 사용하여 최적화를 하였다고 한다. 엔티티는 식별자가 필수이기 때문에 이 전략을 선택하는 경우 em.persist() 호출 즉시, insert 쿼리가 DB 에 날라가게 된다. (트랜잭션을 지원하는 쓰기 지연이 동작하지 않음)

 

[내부 동작]

엔티티 생성 -> em.persist() -> insert 쿼리 DB에 전달 -> 식별자 조회 후 엔티티에 할당 -> 영속성 컨텍스트에 저장 -> 영속 상태의 엔티티 반환

 

사용 가능한 DB : Mysql

 

- SEQUENCE 전략
DB 시퀀스는 유일한 값을 순서대로 생성하는 특별한 오브젝트이다. 이 시퀀스를 통해 기본키를 생성하는 전략이 바로 SEQUENCE 전략이다. 추가적으로 시퀀스 정보가 필요하기 때문에 @SequenceGenerator 를 사용하여 시퀀스 맵핑이 필요.
SEQUENCE 전략을 사용하게되면 IDENTITY 전략을 사용하는 것과 내부 동작 방식이 다르다. em.persist() 호출 시 DB 시퀀스를 사용해 식별자 조회 후 엔티티에 할당하고 그 다음에 영속성 컨텍스트에 저장한다.

IDENTITY 전략의 경우에는 persist() 시에 DB에 바로 저장이 되었으나, SEQUENCE 전략의 경우 트랜잭션 커밋 후 플러시가 일어나면 엔티티가 DB에 저장된다.

 

[내부 동작]

엔티티 생성 -> em.persist() -> 시퀀스에게서 식별자 조회 후 엔티티에 할당 -> 영속성 컨텍스트에 저장 -> 영속 상태의 엔티티 반환

 

* @SequenceGenerator 는 시퀀스 호출 시 증가하는 값이 50이 기본값으로 설정되어있기 때문에 1씩 증가하도록 만들었다면 이 속성도 반드시 1로 설정해 주어야 한다. 이게 50 이 기본값인 이유는 최적화 이슈로 그러한 것인데,,,, 대충 설명하면 미리 50 까지의 식별자값은 땡겨 받고 내부 엔티티에서 땡겨받은 식별자 다쓰고 또 시퀀스에 한번에 땡겨받고 하는 식으로 DB에 접근하는 횟수를 줄이고자 이러한 방식이 사용될 수 있다고 한다.

 

사용 가능한 DB : Oracle

 

- TABLE 전략

키 생성 전용 테이블을 하나 생성하고,  여기에 이름과 값으로 사용할 컬럼을 만들어 사용하는 전략이다. 생성된 테이블을 마치 DB 시퀀스 처럼 동작하도록 하는 전략이라고 할 수 있다. 이 전략은 Table을 사용하기 때문에 모든 DB에 적용이 가능한 전략이다.

* 참고로 키생성 용도로 사용할 테이블은 직접 만들어야 한다

동작 방식 자체가 비슷하다 보니 아무래도 내부적인 동작 방식도 SEQUERNCE 전략과 거의 동일하나, TABLE 전략의 경우 기본 키값으 update 해줘야해서 1번 더 DB 와 통신해야하는 단점이 있다. 이것 역시 SEQUENCE 전략과 동일한 방식으로 최적화가 가능하다.

 

- AUTO 전략

선택한 DB의 dialect 에 따라 위의 3가지 전략 중 하나를 자동으로 선택 해주는 전략이다.

 

필드와 컬럼 맵핑

@Column

필드-컬럼을 맵핑하는 애노테이션으로 생략이 가능하며 생략 시에는 자바 기본 타입(int, float, boolean 등) 은 null 이 들어갈 수 없기 때문에 자동으로 not null 로 컬럼이 생성되고, Integer, String 과 같은 객체 타입의 경우 null 이 들어 갈 수 있기 때문에 nullable 한 컬럼으로 생성된다. 또한 @Column의 nullable 속성의 기본값은 true 이기 때문에 기본 타입에 추가하는 경우 nullable 하게 컬럼이 만들어 진다.

 

@Enumerated : 자바의 enum 타입을 맵핑할 때 사용

enum UserType {
  ADMIN, GUEST
}

- @Enumerated(EnumType.STRING) 을 사용하는 경우

enum 의 name 값이 그래도 DB에 저장된다. enum 순서가 변경이 되어도 상관이 없다. 상대적으로 저장된 데이터의 크기가 큰편.

 

- @Enumerated(EnumType.ORDINAL) 을 사용하는 경우

enum 의 순서가 DB 에 저장된다 (ADMIN : 0, GUEST : 1) 이렇게 되면 enum 의 순서 변경이 불가능하다. 저장되는 데이터의 크기가 작다.

 

@Temporal : 날짜 타입을 맵핑할 때 사용

 

@Lob : BLOB, CLOB 타입 매핑

 

@Transient : 이 필드는 따로 컬럼에 맵핑하지 않고 객체에 임시로 어떤 값을 보관하고 싶은 경우에 사용한다. 당연한 소리지만 DB 에 저장도 조회도 안된다.

 

@Access 

JPA 가 엔티티에 접근하는 방식을 지정하는 애노테이션. 필드에 직접 접근할 지, 접근자(getter)를 사용하여 접근할 지 선택 할 수 있다. 이 애노테이션이 생략된 경우에는 @Id의 위치를 보고 자동으로 판단하여 설정된다

 

- @Access(AccessType.FEILD) : 필드에 직접 접근하며 private 여도 접근이 가능하다.

- @Access(AccessType.PROPERTY) : 접근자 사용

//FEILD 방식
public class Member {
  @Id
  private String id;
  ...
}

//PROPERTY 방식
public class Member {
	private String id;
    ...
    
    @Id
    public String getId() { return this.id; }
}

물론 @Access 는 식별자가 아닌 다른 필드에도 설정이 가능하기 때문에 아래의 코드처럼 하나의 엔티티에 Feild 방식와 property 방식을 혼용하여 사용도 가능하다.

class Member {
	@Id
	private String id;
    
    @Column
    private int age;
    
    @Transient
    private String firstName;
    
    @Transient
    private String lastName;
    
    ....
    
    @Access(AccessType.PROPERTY)
    public String getName() { return firstName + lastName; }
}

'스터디 > JPA' 카테고리의 다른 글

[JPA] 객체지향쿼리 언어  (0) 2023.11.06
[JPA] 데이터 타입  (1) 2023.11.03
[JPA] 연관 관계 매핑 - 심화편  (0) 2023.10.29
[JPA] 연관 관계 매핑  (1) 2023.10.29
[JPA] 기본 개념 및 영속성 컨텍스트  (0) 2023.10.27