1. 개요
Hibernate의 식별자는 엔티티의 기본 키를 나타냅니다. 이는 값이 고유하여 특정 엔터티를 식별할 수 있고 null이 아니며 수정되지 않음을 의미합니다.
Hibernate는 식별자를 정의하는 몇 가지 다른 방법을 제공합니다. 이 문서에서는 라이브러리를 사용하여 엔터티 ID를 매핑하는 각 방법을 검토합니다.
2. 간단한 식별자
식별자를 정의하는 가장 간단한 방법은 @Id 어노테이션을 사용하는 것입니다.
간단한 ID는 @Id 를 사용하여 Java 기본 및 기본 래퍼 유형, String , Date , BigDecimal 및 BigInteger 유형 중 하나의 단일 속성에 매핑됩니다 .
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;
// ...
}
이 예제에서는 pkColumnName 및 valueColumnName 과 같은 다른 속성도 사용자 정의할 수 있음을 알 수 있습니다 .
그러나 이 방법의 단점은 확장성이 좋지 않고 성능에 부정적인 영향을 미칠 수 있다는 것입니다.
요약하면 이 네 가지 생성 유형은 유사한 값이 생성되지만 다른 데이터베이스 메커니즘을 사용합니다.
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 개체에는 orderId 및 productId 라는 두 가지 속성으로 구성된 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 에서 찾을 수 있습니다 .