1. 개요

Hibernate의 식별자는 엔티티의 기본 키를 나타냅니다. 이는 값이 고유하여 특정 엔터티를 식별할 수 있고 null이 아니며 수정되지 않음을 의미합니다.

Hibernate는 식별자를 정의하는 몇 가지 다른 방법을 제공합니다. 이 문서에서는 라이브러리를 사용하여 엔터티 ID를 매핑하는 각 방법을 검토합니다.

2. 간단한 식별자

식별자를 정의하는 가장 간단한 방법은 @Id 어노테이션을 사용하는 것입니다.

간단한 ID는 @Id 를 사용하여 Java 기본 및 기본 래퍼 유형, String , Date , BigDecimalBigInteger 유형 중 하나의 단일 속성에 매핑됩니다 .

long 유형의 기본 키로 엔터티를 정의하는 간단한 예를 살펴보겠습니다 .

@Entity
public class Student {

    @Id
    private long studentId;
    
    // standard constructor, getters, setters
}

3. 생성된 식별자

기본 키 값을 자동으로 생성 하려면 @GeneratedValue 어노테이션 을 추가할 수 있습니다 .

AUTO, IDENTITY, SEQUENCE 및 TABLE의 4가지 생성 유형을 사용할 수 있습니다.

값을 명시적으로 지정하지 않으면 생성 유형은 기본적으로 AUTO로 설정됩니다.

3.1. 자동 생성

기본 생성 유형을 사용하는 경우 지속성 Provider는 기본 키 속성의 유형에 따라 값을 결정합니다. 이 유형은 숫자 또는 UUID 일 수 있습니다 .

숫자 값의 경우 생성은 시퀀스 또는 테이블 생성기를 기반으로 하는 반면 UUID 값은 UUIDGenerator 를 사용합니다 .

먼저 AUTO 생성 전략을 사용하여 엔티티 기본 키를 매핑해 보겠습니다.

@Entity
public class Student {

    @Id
    @GeneratedValue
    private long studentId;

    // ...
}

이 경우 기본 키 값은 데이터베이스 수준에서 고유합니다.

이제 Hibernate 5에서 도입된 UUIDGenerator 를 살펴보겠습니다 .

이 기능을 사용하려면 @GeneratedValue 어노테이션 을 사용하여 UUID 유형의 id를 선언하기만 하면 됩니다 .

@Entity
public class Course {

    @Id
    @GeneratedValue
    private UUID courseId;

    // ...
}

최대 절전 모드는 "8dd5f315-9788-4d00-87bb-10eed9eff566" 형식의 ID를 생성합니다.

3.2. ID 생성

이 유형의 생성은 데이터베이스의 ID 열에서 생성된 값을 예상 하는 IdentityGenerator 에 의존 합니다. 이것은 그들이 자동 증가한다는 것을 의미합니다.

이 생성 유형을 사용하려면 전략 매개변수만 설정하면 됩니다.

@Entity
public class Student {

    @Id
    @GeneratedValue (strategy = GenerationType.IDENTITY)
    private long studentId;

    // ...
}

한 가지 유의할 점은 IDENTITY 생성이 일괄 업데이트를 비활성화한다는 것입니다.

3.3. 시퀀스 생성

시퀀스 기반 ID를 사용하기 위해 Hibernate는 SequenceStyleGenerator 클래스를 제공합니다.

이 생성기는 데이터베이스가 지원하는 경우 시퀀스를 사용합니다. 지원되지 않는 경우 테이블 생성으로 전환됩니다.

시퀀스 이름을 사용자 지정하기 위해 SequenceStyleGenerator 전략 과 함께 @GenericGenerator  어노테이션을 사용할 수 있습니다 .

@Entity
public class User {
    @Id
    @GeneratedValue(generator = "sequence-generator")
    @GenericGenerator(
      name = "sequence-generator",
      strategy = "org.hibernate.id.enhanced.SequenceStyleGenerator",
      parameters = {
        @Parameter(name = "sequence_name", value = "user_sequence"),
        @Parameter(name = "initial_value", value = "4"),
        @Parameter(name = "increment_size", value = "1")
        }
    )
    private long userId;
    
    // ...
}

이 예에서는 기본 키 생성이 4에서 시작됨을 의미하는 시퀀스의 초기 값도 설정했습니다.

SEQUENCE 는 Hibernate 문서에서 권장하는 생성 유형입니다.

생성된 값은 시퀀스마다 고유합니다. 시퀀스 이름을 지정하지 않으면 Hibernate는 다른 유형에 대해 동일한 hibernate_sequence 를 재사용합니다.

3.4. 테이블 생성

TableGenerator식별자 생성 값의 세그먼트를 보유하는 기본 데이터베이스 테이블을 사용합니다.

@TableGenerator 어노테이션 을 사용하여 테이블 이름을 사용자 지정해 보겠습니다 .

@Entity
public class Department {
    @Id
    @GeneratedValue(strategy = GenerationType.TABLE, 
      generator = "table-generator")
    @TableGenerator(name = "table-generator", 
      table = "dep_ids", 
      pkColumnName = "seq_id", 
      valueColumnName = "seq_value")
    private long depId;

    // ...
}

이 예제에서는 pkColumnNamevalueColumnName 과 같은 다른 속성도 사용자 정의할 수 있음을 알 수 있습니다 .

그러나 이 방법의 단점은 확장성이 좋지 않고 성능에 부정적인 영향을 미칠 수 있다는 것입니다.

요약하면 이 네 가지 생성 유형은 유사한 값이 생성되지만 다른 데이터베이스 메커니즘을 사용합니다.

3.5. Custom 생성기

기본 전략을 사용하고 싶지 않다고 가정해 보겠습니다. 이를 위해 IdentifierGenerator 인터페이스 를 구현하여 사용자 지정 생성기를 정의할 수 있습니다 .

문자열 접두사와 숫자 를 포함하는 식별자를 빌드하는 생성기를 생성합니다 .

public class MyGenerator 
  implements IdentifierGenerator, Configurable {

    private String prefix;

    @Override
    public Serializable generate(
      SharedSessionContractImplementor session, Object obj) 
      throws HibernateException {

        String query = String.format("select %s from %s", 
            session.getEntityPersister(obj.getClass().getName(), obj)
              .getIdentifierPropertyName(),
            obj.getClass().getSimpleName());

        Stream ids = session.createQuery(query).stream();

        Long max = ids.map(o -> o.replace(prefix + "-", ""))
          .mapToLong(Long::parseLong)
          .max()
          .orElse(0L);

        return prefix + "-" + (max + 1);
    }

    @Override
    public void configure(Type type, Properties properties, 
      ServiceRegistry serviceRegistry) throws MappingException {
        prefix = properties.getProperty("prefix");
    }
}

이 예제에서는 IdentifierGenerator 인터페이스 에서 generate() 메서드를 재정의합니다 .

먼저, prefix-XX 형식의 기존 기본 키에서 가장 높은 숫자를 찾으려고 합니다 . 그런 다음 찾은 최대 수에 1을 더하고 접두사 속성을 추가하여 새로 생성된 id 값을 가져옵니다.

우리 클래스는 또한 configure() 메서드 에서 접두사 속성 값을 설정할 수 있도록 Configurable 인터페이스를 구현합니다.

다음으로 이 사용자 지정 생성기를 엔터티에 추가해 보겠습니다.

이를 위해 생성기 클래스의 전체 클래스 이름을 포함 하는 전략 매개변수와 함께 @GenericGenerator 어노테이션을 사용할 수 있습니다 .

@Entity
public class Product {

    @Id
    @GeneratedValue(generator = "prod-generator")
    @GenericGenerator(name = "prod-generator", 
      parameters = @Parameter(name = "prefix", value = "prod"), 
      strategy = "com.baeldung.hibernate.pojo.generator.MyGenerator")
    private String prodId;

    // ...
}

또한 접두사 매개변수를 "prod"로 설정했습니다.

생성된 id 값을 더 명확하게 이해하기 위해 빠른 JUnit 테스트를 살펴보겠습니다.

@Test
public void whenSaveCustomGeneratedId_thenOk() {
    Product product = new Product();
    session.save(product);
    Product product2 = new Product();
    session.save(product2);

    assertThat(product2.getProdId()).isEqualTo("prod-2");
}

여기서 "prod" 접두사를 사용하여 생성된 첫 번째 값은 "prod-1"이고 그 뒤에 "prod-2"가 있습니다.

4. 복합 식별자

지금까지 본 간단한 식별자 외에도 Hibernate는 복합 식별자를 정의할 수도 있습니다.

복합 ID는 하나 이상의 영구 속성이 있는 기본 키 클래스로 표시됩니다.

기본 키 클래스는 다음과 같은 몇 가지 조건을 충족해야 합니다 .

  • @EmbeddedId 또는 @IdClass 어노테이션 을 사용하여 정의해야 합니다 .
  • 공개적이고 직렬화 가능해야 하며 인수가 없는 공개 생성자가 있어야 합니다.
  • 마지막으로 equals()hashCode() 메서드 를 구현해야 합니다.

클래스의 속성은 컬렉션 및 OneToOne 속성 을 피하면서 기본, 복합 또는 ManyToOne일 수 있습니다 .

4.1. @EmbeddedId

이제 @EmbeddedId 를 사용하여 ID를 정의하는 방법을 살펴보겠습니다 .

먼저 @Embeddable 어노테이션이 달린 기본 키 클래스가 필요합니다 .

@Embeddable
public class OrderEntryPK implements Serializable {

    private long orderId;
    private long productId;

    // standard constructor, getters, setters
    // equals() and hashCode() 
}

다음으로 @ EmbeddedId 를 사용하여 OrderEntryPK 유형의 ID를 엔터티에 추가할 수 있습니다 .

@Entity
public class OrderEntry {

    @EmbeddedId
    private OrderEntryPK entryId;

    // ...
}

이 유형의 복합 ID를 사용하여 엔티티의 기본 키를 설정하는 방법을 살펴보겠습니다.

@Test
public void whenSaveCompositeIdEntity_thenOk() {
    OrderEntryPK entryPK = new OrderEntryPK();
    entryPK.setOrderId(1L);
    entryPK.setProductId(30L);
        
    OrderEntry entry = new OrderEntry();
    entry.setEntryId(entryPK);
    session.save(entry);

    assertThat(entry.getEntryId().getOrderId()).isEqualTo(1L);
}

여기서 OrderEntry 개체에는 orderIdproductId 라는 두 가지 속성으로 구성된 OrderEntryPK 기본 ID가 있습니다.

4.2. @IdClass

@IdClass 어노테이션은 @EmbeddedId 와 유사 합니다 . @IdClass 와의 차이점 은 각 속성에 대해 @Id 를 사용하여 기본 엔티티 클래스에서 속성이 정의된다는 것입니다. 기본 키 클래스는 이전과 동일하게 보입니다.

@IdClass 를 사용하여 OrderEntry 예제를 다시 작성해 보겠습니다 .

@Entity
@IdClass(OrderEntryPK.class)
public class OrderEntry {
    @Id
    private long orderId;
    @Id
    private long productId;
    
    // ...
}

그런 다음 OrderEntry 개체 에서 직접 id 값을 설정할 수 있습니다 .

@Test
public void whenSaveIdClassEntity_thenOk() {        
    OrderEntry entry = new OrderEntry();
    entry.setOrderId(1L);
    entry.setProductId(30L);
    session.save(entry);

    assertThat(entry.getOrderId()).isEqualTo(1L);
}

두 유형의 복합 ID에 대해 기본 키 클래스에는 @ManyToOne 속성도 포함될 수 있습니다.

@Embeddable
public class OrderEntryPK implements Serializable {

    private long orderId;
    private long productId;
    @ManyToOne
    private User user;

    // ...
}
@Entity
@IdClass(OrderEntryPK.class)
public class OrderEntryIdClass {
    @Id
    private long orderId;
    @Id
    private long productId;
    @ManyToOne
    private User user;

    // ...
}

Hibernate는 또한 @Id 어노테이션과 결합된 @ManyToOne 연관 으로 구성된 기본 키 정의를 허용 합니다. 이 경우 엔터티 클래스는 기본 키 클래스의 조건도 충족해야 합니다.

그러나 이 방법의 단점은 엔터티 개체와 식별자가 구분되지 않는다는 것입니다.

5. 파생 식별자

파생 식별자는 @MapsId 어노테이션 을 사용하여 엔터티 연결에서 가져옵니다.

먼저 User 엔터티 와의 일대일 연결에서 ID를 파생 하는 UserProfile 엔터티를 생성해 보겠습니다.

@Entity
public class UserProfile {

    @Id
    private long profileId;
    
    @OneToOne
    @MapsId
    private User user;

    // ...
}

다음으로 UserProfile 인스턴스가 연결된 User 인스턴스 와 동일한 ID를 가지고 있는지 확인 합니다.

@Test
public void whenSaveDerivedIdEntity_thenOk() {        
    User user = new User();
    session.save(user);
       
    UserProfile profile = new UserProfile();
    profile.setUser(user);
    session.save(profile);

    assertThat(profile.getProfileId()).isEqualTo(user.getUserId());
}

6. 결론

이 기사에서 우리는 Hibernate에서 식별자를 정의할 수 있는 여러 가지 방법을 살펴보았다.

예제의 전체 소스 코드는 GitHub 에서 찾을 수 있습니다 .

Persistence footer banner