id생성 전략에 따른 Entity설계하기

Always·2024년 11월 2일
0

Backend&Devops

목록 보기
8/15

@Entity

Entity란 무엇이고, 어떻게 동작하는가?

Spring에서는 @Entity 어노테이션을 이용하여 실제 데이터베이스의 테이블과 매핑되는 클래스를 정의할 수 있습니다. 이를 통해 데이터베이스의 테이블과 CRUD 작업을 쉽게 수행하고 관리할 수 있습니다. Entity는 데이터베이스와 직접적으로 연결되기 때문에 테이블 구조와 일치해야 하며, Spring Boot와 JPA는 다음과 같은 과정으로 @Entity 클래스와 데이터베이스 테이블을 매핑합니다.

@Entity가 매핑되는 과정

  1. 기본 JPA 구성 요소 설정
    스프링 부트는 @SpringBootApplication 내부의 @EnableAutoConfigurationspring-data-jpa 종속성을 이용해
    EntityManagerFactory, DataSource, TransactionManager 등의 JPA 기본 구성 요소를 자동 설정합니다.

  2. Entity 클래스 스캔 및 매핑 정보 생성
    @Entity가 붙은 클래스를 스캔하여, JPA 프로바이더(Hibernate 등)는 데이터베이스 테이블과 Entity 클래스 간의
    매핑 정보(필드, 컬럼, 기본키, 제약 조건)를 생성합니다.

  3. 테이블 생성 및 업데이트
    spring.jpa.hibernate.ddl-auto 설정에 따라 데이터베이스 테이블을 생성하거나 업데이트합니다.

  4. JpaRepository 인터페이스 스캔 및 프록시 생성
    @EnableJpaRepositories를 통해 JpaRepository를 확장한 인터페이스들을 스캔하고, 리포지토리 프록시를 생성하여 JPA 기능을 제공합니다.

  5. EntityManagerFactory와 EntityManager 생성
    실제 CRUD 작업 시 EntityManagerFactory가 트랜잭션 내에서 EntityManager를 생성하고,
    리포지토리 프록시가 이를 이용해 작업을 수행합니다.

  6. 리포지토리 프록시를 통한 CRUD 수행
    리포지토리 프록시는 메서드 호출 시 이를 가로채서 EntityManager를 통해 CRUD 작업을 수행합니다.

  7. 트랜잭션 종료 및 커밋
    트랜잭션이 종료되면 변경사항을 커밋하고, EntityManager와 리포지토리의 연결이 끊어집니다.

참고: @Entity 객체는 영속성 컨텍스트에 등록되어야 실제 CRUD 작업이 가능합니다. 이때부터 EntityManager 관리 하에 놓여 데이터베이스와의 동기화가 이루어집니다.


예제 코드

@Entity
@Getter
@AllArgsConstructor
@NoArgsConstructor(access = AccessLevel.PROTECTED)
public class User {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    @NonNull
    private String phoneNumber;

    @Column(name = "nick_name")
    private String nickName;

    @Column(name = "is_curtaincall_on_and_off")
    private boolean isCurtainCallOnAndOff;

    @Column(name = "user_role")
    @ColumnDefault("'USER'")
    private String userRole;

    @Builder
    public User(@NotNull String phoneNumber, String nickName, boolean isCurtainCallOnAndOff) {
        this.phoneNumber = phoneNumber;
        this.nickName = nickName;
        this.isCurtainCallOnAndOff = isCurtainCallOnAndOff;
    }
}

설명

  • @Entity: 해당 클래스가 데이터베이스의 테이블과 매핑됨을 나타냅니다.
  • @AllArgsConstructor: 모든 필드의 값을 받는 생성자를 생성하여 초기화합니다.
  • @NoArgsConstructor(access = AccessLevel.PROTECTED): 기본 생성자를 protected로 정의하여 객체 생성 시 리플렉션을 통한 접근을 제한합니다.
  • @Id: 해당 필드를 데이터베이스의 기본키(PK)로 매핑합니다.
  • @GeneratedValue(strategy = GenerationType.IDENTITY): ID 값 생성 전략을 지정합니다.

ID 생성 전략

@GeneratedValue(strategy = GenerationType.IDENTITY)

  • 데이터베이스의 AUTO_INCREMENT 기능을 사용해 ID 값을 생성하는 전략입니다.
  • ID 생성이 데이터베이스에 위임되기 때문에, persist 호출 시 바로 INSERT가 실행됩니다.
  • 쓰기 지연(Write-behind) 전략을 사용할 수 없으며, 각 엔티티가 임시 저장소에 저장될 때마다 즉시 INSERT가 발생합니다.
  • 트랜잭션 내에서 여러 엔티티를 일괄적으로 저장하는 경우 성능이 떨어질 수 있습니다.
@Transactional
public void addUsers(List<User> users) {
    for (User user : users) {
        em.persist(user);
    }
}

@GeneratedValue(strategy = GenerationType.SEQUENCE)

  • 데이터베이스 시퀀스 객체를 사용하여 고유한 ID 값을 생성합니다.
  • JPA가 미리 ID 값을 할당받아 persist 호출 전부터 ID를 관리할 수 있습니다.
  • allocationSize를 통해 한 번에 여러 개의 ID 값을 가져와 성능을 최적화할 수 있습니다.
  • 시퀀스를 지원하지 않는 데이터베이스(MySQL 등)에서는 사용할 수 없습니다.

@GeneratedValue(strategy = GenerationType.TABLE)

  • 키 생성 전용 테이블을 사용하여 ID를 생성하는 방식입니다.
  • @TableGenerator로 설정이 필요하며, 모든 데이터베이스에서 사용 가능하지만 성능 이슈가 발생할 수 있습니다.
  • 동시성 문제 가능성이 있으므로, Pessimistic Lock이나 Optimistic Lock을 통해 동시성 문제를 제어해야 합니다.

@GeneratedValue(strategy = GenerationType.AUTO)

  • 기본 설정으로, 데이터베이스에 따라 적합한 전략을 자동으로 선택합니다.
  • 특정 상황에서 예기치 않은 전략이 선택될 수 있으므로 주의가 필요합니다.

기본키의 제약조건

  1. NULL 값이 허용되지 않습니다.
  2. 고유하게 식별 가능해야 합니다.
  3. 변경되지 않는 값이어야 합니다.

ID 값 부여에 따른 성능 차이

다량의 데이터 삽입 시 각 전략에 따라 성능이 다르게 나타납니다. 예를 들어, SEQUENCETABLE 전략은 쓰기 지연을 통해 트랜잭션 커밋 시 한꺼번에 INSERT를 처리할 수 있지만, IDENTITYAUTO_INCREMENT 특성상 persist 호출마다 즉시 INSERT를 실행합니다.

SEQUENCE,'Table' 전략의 장단점

  • 메모리 캐싱: 시퀀스 값을 메모리에 캐싱하여, 여러 개의 ID 값을 미리 할당받아 성능을 최적화합니다.
    • 위처럼 val이 101이고, allocationsize가 100인 경우 데이터베이스에서 해당값을 가져와서 메모리에 101~200까지의 수를 저장해둔 후, 201을 다시 데이터베이스에 업데이터 후, 메모리에 저장된 수를 id로 사용
    • 이를 통해서 어플리케이션 레벨의 부하를 줄일 수 있음
  • 쓰기 지연: identity가 한 실행마다 insert되는 것과 비교하여, SEQUENCETABLE 전략은 쓰기 지연을 통해 트랜잭션이 커밋될 때 한꺼번에 INSERT가 실행됩니다.
  • 동시성 문제: TABLE 전략은 시퀀스 값을 테이블로 관리하기 때문에, 여러 트랜잭션이 동시에 접근할 경우 동시성 문제가 발생할 수 있습니다. 이를 해결하기 위해 비관적 락(Pessimistic Lock)과 낙관적 락(Optimistic Lock)을 사용할 수 있습니다.

잠금 방식

잠금 방식원리장점단점적합한 상황
비관적 락다른 트랜잭션이 접근하지 못하도록 미리 잠금을 설정동시성 문제 예방, 데이터 일관성 보장대기 시간 증가, 성능 저하 가능충돌이 빈번한 경우
낙관적 락충돌 시 다시 시도, 충돌 발생 시 감지 후 처리경합 없이 접근 가능, 충돌이 드문 환경에서 성능 우수충돌 발생 시 재시도로 성능 저하 가능충돌이 드문 경우

TABLE 전략은 ACID를 보장하기 위해서는 잠금이 필요하며, 이러한 이유로 성능 저하가 발생할 수 있습니다. 또한, Hibernate에서 제공하는 TABLE 전략은 별도로 잠금을 설정할 수 없기 때문에, IDENTITYSEQUENCE 전략이 많이 사용됩니다.


예를 들어, 두 개의 트랜잭션이 각각 두 자원(A와 B)을 비관적 락으로 잠그려고 하는 상황을 가정해 보겠습니다.

1.트랜잭션 1이 자원 A에 비관적 락을 겁니다.
2.트랜잭션 2가 자원 B에 비관적 락을 겁니다.
3.트랜잭션 1이 자원 B를 잠그려고 시도하지만, 
4.이미 트랜잭션 2가 잠금 중이므로 대기 상태에 들어갑니다.
5.트랜잭션 2도 자원 A를 잠그려고 하지만, 
6.트랜잭션 1이 잠금 중이므로 대기 상태에 들어갑니다.

요약

  • IDENTITY 전략AUTO_INCREMENT 방식으로, 즉시 INSERT가 발생하여 다량의 데이터 삽입 시 성능이 떨어질 수 있습니다.
  • SEQUENCE 전략은 시퀀스를 지원하는 데이터베이스에서 빠르고 효율적인 방식으로 ID를 생성하며, 쓰기 지연을 활용할 수 있습니다. 또한 트랜잭션과 무관한 객체로 활용되므로, ACID의 보장이 안될수 있습니다.
  • TABLE 전략은 모든 데이터베이스에서 사용할 수 있지만 동시성 문제로 인해 성능 저하가 발생할 수 있습니다.
  • 일반적으로 IDENTITY 전략이 가장 많이 사용되며, 호환성안정성 측면에서 우수합니다.
profile
🐶개발 블로그

0개의 댓글