JPA/JPA Basic

[JPA] 엔티티 매핑 - 기본 키 생성전략 ( IDENTITY, SEQUENCE )

IT록흐 2023. 5. 31. 22:17
반응형

 

 

JPA는 어노테이션으로 엔티티와 테이블을 매핑한다.

 

[JPA] 엔티티 매핑 - @Entity, @Table, @Column

엔티티매니저에 의해 생성된 엔티티는 DB의 테이블과 매핑된다. 매핑 작업은 JPA프레임워크가 담당한다. 개발자가 해야할 일은 어떤 엔티티가 어떤 테이블과 매핑되어야 하는지, 어떤 필드가 기

lordofkangs.tistory.com

 

테이블에서 가장 중요한 개념 중 하나가 '기본 키' 이다. 개발자는 기본 키 정보를 엔티티에 어노테이션으로 '표시'하여 JPA에게 전달해야 한다. JPA는 표시된 어노테이션을 바탕으로 테이블과 엔티티를 매핑한다.

 

 

@Id

@Target({ElementType.METHOD, ElementType.FIELD})
@Retention(RetentionPolicy.RUNTIME)
public @interface Id {
}

 

@Id는 엔티티 필드와 테이블의 기본키를 매핑하기 위한 어노테이션이다. @Id로 표시된 필드나 반환 가능한 메소드(Getter)는 기본키를 의미한다. 기본키는 NULL 값이면 안 된다. 

 

@Entity
@Data
public class Member {

    @Id
    public Long id;

    @Column
    public String name;

}

 

id 필드가 @Id로 기본키와 매핑되었다. 이제 id 필드는 NULL 값이면 안된다. 

 

public class Main {

    public static void main(String[] args) {
        //EntityManagerFactory 생성
        EntityManagerFactory emf = Persistence.createEntityManagerFactory("h2");
        //EntityManager 생성
        EntityManager entityManager = emf.createEntityManager();
        //EntityTransaction 생성
        EntityTransaction tx = entityManager.getTransaction();


        tx.begin();

        try {
            Member member1 = new Member();
            member1.setName("사쿠라");

            Member member2 = new Member();
            member2.setName("김채원");

            entityManager.persist(member1);
            entityManager.persist(member2);

            tx.commit();
        }catch (Exception e){
            e.printStackTrace();
            tx.rollback();
        }finally {
            entityManager.close();
        }
    }
}

 

 

위 코드처럼 id를 세팅하지 않고 수행해보았다. 

 

javax.persistence.PersistenceException: org.hibernate.id.IdentifierGenerationException: ids for this class must be manually assigned before calling save(): org.example.member.Member

 

기본키를 세팅하라는 오류 메시지가 출력되었다. 기본키는 개발자가 직접 넣어줄 수 있지만 실무에서는 주로 자동생성 방식을 따른다. 기본키 자동생성은 DBMS의 종류에 따라 다양한 전략이 존재한다. 우선, 전략을 지정하는 어노테이션부터 알아보자.

 

@GeneratedValue

@Target({ElementType.METHOD, ElementType.FIELD})
@Retention(RetentionPolicy.RUNTIME)
public @interface GeneratedValue {
    GenerationType strategy() default GenerationType.AUTO;

    String generator() default "";
}

 

기본키 자동생성 전략은 @GeneratedValue로 표시된다.  @GeneratedValue는 기본키를 자동으로 생성하겠다는 표시이다. 어떤 전략을 사용할 것인지는 strategy 속성에 표시한다.  strategy 속성은 GenerationType을 반환한다.

 

GenerationType 열거형 클래스

public enum GenerationType {
    TABLE,
    SEQUENCE,
    IDENTITY,
    AUTO;

    private GenerationType() {
    }
}

 

GenerationType에는 네 가지 전략을 열거했다.

 

- IDENTITY 전략 ( MYSQL용 )

- SEQUENCE 전략 ( ORACLE용 )

- TABLE 전략 ( 모든 DB 사용 )

- AUTO 전략 ( 방언에 따라 자동으로 전략 지정 )

 

@Entity
@Data
public class Member {

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

    @Column
    public String name;

}

 

위와 같이, strategy 속성에 자신이 사용하는 DB에 맞는 기본키 자동생성 전략을 넣어준다. 그러면 id는 전략에 맞게 자동생성되어 진다. 

 

 

 

IDENTITY 전략

 

IDENTITY 전략은 MySQL, PostgreSQL에서 사용하는 기본키 자동생성 전략이다. auto-increment를 사용하여 삽입된 레코드를 1씩 증가하는 방식으로 자동생성한다.

 

나는 H2 DB를 사용하고 있는데, 테스트용 DB이다 보니 다양한 DB를 호환할 수 있는 기능을 가지고 있다. MySQL과 호환되는 H2 DB를 사용하기 위해 MODE 옵션에 MySQL을 넣어주었다.

 

persistence.xml

jdbc:h2:tcp://localhost/~/test;MODE=MySQL

 

Entity 클래스

@Entity
@Data
public class Member {

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

    @Column
    public String name;

}

 

위와 같이, IDENTITY 전략으로 설정해놓았다. 이제 클라언트를 실행해보자. Member 엔티티를 사용하는 클라이언트는 위에서 작성한 Main클래스와 동일하다.  코드를 보면 기본키가 세팅되어 있지 않다. 그럼에도 정말 자동생성되는지 한번 실행해보자. 

 

 

테이블을 생성할 때, IDENTITY 전략으로 생성되도록 DDL이 실행되었다.

 

 

그리고 JPA가 생성한 INSERT SQL을 보면, id에 NULL 같이 들어가 있는 것을 확인할 수 있다. 기본키가 NULL 값이면 안되지만 DB에서 자동생성해준다.

 

 

H2 콘솔을 확인하면 ID가 자동생성되어 있음이 확인된다. 

 

 

SEQUENCE 전략

 

SEQUENCE 전략은 ORACLE, PostgreSQL에서 사용하는 전략이다. ( PostgreSQL은 IDENTITY,SEQUENCE 전략 모두 사용한다. ) IDENTITY는 테이블을 생성할 때, 자동생성이 필요한 기본 키에 지정하여 사용되었다면, SEQUNCE는 SEQUENCE 객체를 따로 생성하여 기본키를 생성한다. 

 

persistence.xml

jdbc:h2:tcp://localhost/~/test;MODE=ORACLE

 

이번에는 MODE를 ORACLE 모드로 바꾸어준다. 

 

엔티티 클래스

@Entity
@Data
public class Member {

    @Id
    @GeneratedValue( strategy = GenerationType.SEQUENCE ) // 시퀀스 전략!
    public Long id;

    @Column
    public String name;

}

 

위와 같이, 전략을 SEQUENCE로 바꾸었다.클라이언트 코드는 역시 위와 동일하다. 그럼 실행해보자. 

 

 

프로그램이 실행되면 SEQUNCE 객체가 생성되었다는 로그가 출력된다. 이는 디폴트로 생성되는 시퀀스 객체이다. 엔티티에 SEQUENCE 전략을 사용하지 않아도 자동생성된다. H2 DB 콘솔을 확인해보자.

 

 

ID가 자동생성되었다. DB는 이처럼 기본키를 자동생성하여 개발자에게 편의를 제공할 수 있다.

 

 

@SequenceGenerator

 

하지만 문제가 하나 있다. DB에 하나의 트랜잭션만 접근한다면 문제가 없지만 여러개의 트랜잭션이 동시에 접근하여 '동시성 이슈'를 만든다. 동일한 기본키가 중복되어 생성되는 것이다. 

 

이런 문제를 해결하기 위해, DB에 접근하는 클라이언트(트랜잭션)는 넓은 범위의 기본키를 할당받는다. JPA는 이를 allocationSize라 부르고 디폴트값은 50이다. 한 개의 트랜잭션이 시퀀스객체에 접근하면 50개의 기본키를 미리 생성받는다. 50개는 해당 클라이언트 전용 기본키로 다른 트랜잭션이 접근할 수없다. 다른 트랜잭션은 51부터 100까지의 기본키를 할당 받는다. 이렇게 미리 기본키를 할당받으면 반복해서 DB에 접근하여 기본키를 생성할 필요가 사라지고 다른 트랜잭션과의 충돌도 방지할 수 있다. 

 

우리는 지금까지 디폴트 시퀀스 객체를 사용한 것이다. 만약 미리 기본키를 할당받고 싶다면 시퀀스를 직접 만들어야 한다. 시퀀스를 직접 만들려면 시퀀스 Generator가 필요하다. 개발자가 JPA에게 이를 어노테이션(@SequenceGenerator)으로 '표시'하면 된다. JPA는 어노테이션을 읽고 시퀀스 객체를 설정대로 생성한다. 

 

엔티티 클래스

@Entity
@Data
@SequenceGenerator(
        name="MEMBER_SEQ_GENERATOR",
        sequenceName = "MEMBER_SEQ",
        initialValue = 5, allocationSize = 100
)
public class Member {

    @Id
    @GeneratedValue( strategy = GenerationType.SEQUENCE, generator = "MEMBER_SEQ_GENERATOR")
    public Long id;

    @Column
    public String name;

}

 

 

@SequenceGenerator는 시퀀스객체를 생성하는 로직이 필요함을 표시하는 어노테이션이다. JPA는 @SequenceGenerator 표시를 읽어 설정대로 시퀀스 생성기 생성 로직을 수행한다. @GeneratedValue는 generator 속성에 사용할 시퀀스생성기 이름을 넣어주면 된다.  할당 사이즈를 100으로 하였고 시작 id는 5로 하였다. 그리고 두 개의 레코드를 삽입해보겠다. 

 

 

로그 첫줄은 디폴트시퀀스객체  생성 정보이고 두번째 줄은 우리가 만든 시퀀스객체생성 정보이다. 그럼 DB 콘솔을 확인해보자. 

 

 

2개가 삽입 되었고 ID는 5와6이다. 그럼 트랜잭션을 다르게 하기 위해 프로그램을 재실행해보자.

 

 

다른 트랜잭션이므로 다른 범위의 ID를 부여 받는다. 그럼 다시 프로그램을 재실행해보자. 

 

 

트랜잭션을 달리 할때마다 100개씩 늘어남을 알수 있다. 트랜잭션끼리의 충돌을 막기 위해, 한번 접근할때마다 100개를 미리 할당받아 놓고 사용하는 것이다. 다른 트랜잭션은 그 다음 100개를 할당받아 사용한다. 

 

이처럼 어노테이션을 사용하면 전략 뿐만이 아니라 시퀀스 객체 생성에 관한 구체적인 설정까지 개발자가 '표시'할 수 있다. 계속 '표시'라 말하는 이유는 개발자가 '구현'하는 것이 아니기 때문이다. '구현'은 JPA가 한다. 개발자는 구현이 필요한 부분에 어노테이션으로 '표시'만 하면 된다. 이것이 JPA가 ORM으로써 강력한 이유이다. 

 

 


 

참고자료

 

자바 ORM 표준 JPA 프로그래밍 - 기본편 - 인프런 | 강의

JPA를 처음 접하거나, 실무에서 JPA를 사용하지만 기본 이론이 부족하신 분들이 JPA의 기본 이론을 탄탄하게 학습해서 초보자도 실무에서 자신있게 JPA를 사용할 수 있습니다., - 강의 소개 | 인프런

www.inflearn.com

 

반응형