Tổng quan về số nhận dạng trong Hibernate / JPA

1. Giới thiệu

Định danh trong Hibernate đại diện cho khóa chính của một thực thể. Điều này ngụ ý rằng các giá trị là duy nhất để chúng có thể xác định một thực thể cụ thể, rằng chúng không rỗng và chúng sẽ không bị sửa đổi.

Hibernate cung cấp một số cách khác nhau để xác định số nhận dạng. Trong bài viết này, chúng tôi sẽ xem xét từng phương pháp ánh xạ id thực thể bằng thư viện.

2. Định danh đơn giản

Cách đơn giản nhất để xác định số nhận dạng là sử dụng chú thích @Id .

Các id đơn giản được ánh xạ bằng cách sử dụng @Id đến một thuộc tính duy nhất của một trong các kiểu sau: kiểu bao bọc nguyên thủy và nguyên thủy của Java, Chuỗi, Ngày, BigDecimal, BigInteger.

Hãy xem một ví dụ nhanh về việc xác định một thực thể có khóa chính là loại long:

@Entity public class Student { @Id private long studentId; // standard constructor, getters, setters }

3. Số nhận dạng đã tạo

Nếu chúng tôi muốn giá trị khóa chính được tạo tự động cho chúng tôi, chúng tôi có thể thêm chú thích @GeneratedValue .

Điều này có thể sử dụng 4 loại thế hệ: TỰ ĐỘNG, IDENTITY, SEQUENCE, TABLE.

Nếu chúng tôi không chỉ định giá trị một cách rõ ràng, kiểu tạo sẽ mặc định là TỰ ĐỘNG.

3.1. AUTO Generation

Nếu chúng ta đang sử dụng kiểu tạo mặc định, thì trình cung cấp độ bền sẽ xác định các giá trị dựa trên kiểu của thuộc tính khóa chính. Loại này có thể là số hoặc UUID.

Đối với giá trị số, việc tạo dựa trên trình tạo chuỗi hoặc bảng, trong khi giá trị UUID sẽ sử dụng UUIDGenerator.

Hãy xem một ví dụ về ánh xạ khóa chính của thực thể bằng cách sử dụng chiến lược tạo TỰ ĐỘNG:

@Entity public class Student { @Id @GeneratedValue private long studentId; // ... }

Trong trường hợp này, các giá trị khóa chính sẽ là duy nhất ở cấp cơ sở dữ liệu.

Một tính năng thú vị được giới thiệu trong Hibernate 5 là UUIDGenerator. Để sử dụng điều này, tất cả những gì chúng ta cần làm là khai báo một id kiểu UUID với chú thích @GeneratedValue :

@Entity public class Course { @Id @GeneratedValue private UUID courseId; // ... }

Hibernate sẽ tạo một id có dạng “8dd5f315-9788-4d00-87bb-10eed9eff566”.

3.2. IDENTITY thế hệ

Kiểu tạo này dựa vào IdentityGenerator mong đợi các giá trị được tạo bởi cột nhận dạng trong cơ sở dữ liệu, nghĩa là chúng được tăng tự động.

Để sử dụng kiểu tạo này, chúng ta chỉ cần đặt tham số chiến lược :

@Entity public class Student { @Id @GeneratedValue (strategy = GenerationType.IDENTITY) private long studentId; // ... }

Một điều cần lưu ý là tạo IDENTITY vô hiệu hóa cập nhật hàng loạt.

3.3. SEQUENCE Generation

Để sử dụng id dựa trên trình tự, Hibernate cung cấp lớp SequenceStyleGenerator .

Trình tạo này sử dụng trình tự nếu chúng được cơ sở dữ liệu của chúng tôi hỗ trợ và chuyển sang tạo bảng nếu chúng không được hỗ trợ.

Để tùy chỉnh tên trình tự, chúng ta có thể sử dụng chú thích @GenericGenerator với chiến lược SequenceStyleGenerator:

@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; // ... }

Trong ví dụ này, chúng tôi cũng đã đặt giá trị ban đầu cho chuỗi, có nghĩa là quá trình tạo khóa chính sẽ bắt đầu từ 4.

SEQUENCE là kiểu tạo được đề xuất bởi tài liệu Hibernate.

Các giá trị được tạo là duy nhất cho mỗi chuỗi. Nếu bạn không chỉ định tên trình tự, Hibernate sẽ sử dụng lại cùng một hibernate_sequence cho các kiểu khác nhau.

3.4. Tạo BẢNG

Các TableGenerator sử dụng một bảng cơ sở dữ liệu cơ bản chứa các phân đoạn của các giá trị hệ nhận dạng.

Hãy tùy chỉnh tên bảng bằng cách sử dụng chú thích @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; // ... }

In this example, we can see that other attributes such as the pkColumnName and valueColumnName can also be customized.

The disadvantage of this method is that it doesn't scale well and can negatively affect performance.

To sum up, these four generation types will result in similar values being generated but use different database mechanisms.

3.5. Custom Generator

If we don't want to use any of the out-of-the-box strategies, we can define our custom generator by implementing the IdentifierGenerator interface.

Let's create a generator that builds identifiers containing a String prefix and a number:

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"); } }

In this example, we override the generate() method from the IdentifierGenerator interface and first find the highest number from the existing primary keys of the form prefix-XX.

Then we add 1 to the maximum number found and append the prefix property to obtain the newly generated id value.

Our class also implements the Configurable interface, so that we can set the prefix property value in the configure() method.

Next, let's add this custom generator to an entity. For this, we can use the @GenericGenerator annotation with a strategy parameter that contains the full class name of our generator class:

@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; // ... }

Also, notice we've set the prefix parameter to “prod”.

Let's see a quick JUnit test for a clearer understanding of the id values generated:

@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"); }

Here, the first value generated using the “prod” prefix was “prod-1”, followed by “prod-2”.

4. Composite Identifiers

Besides the simple identifiers we've seen so far, Hibernate also allows us to define composite identifiers.

A composite id is represented by a primary key class with one or more persistent attributes.

The primary key class must fulfill several conditions:

  • it should be defined using @EmbeddedId or @IdClass annotations
  • it should be public, serializable and have a public no-arg constructor
  • it should implement equals() and hashCode() methods

The class's attributes can be basic, composite or ManyToOne while avoiding collections and OneToOne attributes.

4.1. @EmbeddedId

To define an id using @EmbeddedId, first we need a primary key class annotated with @Embeddable:

@Embeddable public class OrderEntryPK implements Serializable { private long orderId; private long productId; // standard constructor, getters, setters // equals() and hashCode() }

Next, we can add an id of type OrderEntryPK to an entity using @EmbeddedId:

@Entity public class OrderEntry { @EmbeddedId private OrderEntryPK entryId; // ... }

Let's see how we can use this type of composite id to set the primary key for an entity:

@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); }

Here the OrderEntry object has an OrderEntryPK primary id formed of two attributes: orderId and productId.

4.2. @IdClass

The @IdClass annotation is similar to the @EmbeddedId, except the attributes are defined in the main entity class using @Id for each one.

The primary-key class will look the same as before.

Let's rewrite the OrderEntry example with an @IdClass:

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

Then we can set the id values directly on the OrderEntry object:

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

Note that for both types of composite ids, the primary key class can also contain @ManyToOne attributes.

Hibernate also allows defining primary-keys made up of @ManyToOne associations combined with @Id annotation. In this case, the entity class should also fulfill the conditions of a primary-key class.

The disadvantage of this method is that there's no separation between the entity object and the identifier.

5. Derived Identifiers

Derived identifiers are obtained from an entity's association using the @MapsId annotation.

First, let's create a UserProfile entity which derives its id from a one-to-one association with the User entity:

@Entity public class UserProfile { @Id private long profileId; @OneToOne @MapsId private User user; // ... }

Next, let's verify that a UserProfile instance has the same id as its associated User instance:

@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. Conclusion

Trong bài viết này, chúng ta đã thấy nhiều cách có thể xác định số nhận dạng trong Hibernate.

Bạn có thể tìm thấy mã nguồn đầy đủ của các ví dụ trên GitHub.