Bài viết gốc bạn có thể xem ở đây.

1. Giới thiệu

Định danh trong Hibernate được đại diện bởi khoá chính của một entity. Giá trị của nó là duy nhất để có thể định danh một entity, nó sẽ không thể null và sẽ không thể sửa đổi.

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

Cách đơn giản nhất để định nghĩa một định danh là sử dụng annotation @Id.

Những id đơn giản được ánh xạ đến một thuộc tính duy nhất của một trong các loại sau bằng @Id: Java nguyên thuỷ và lớp bao bọc của các kiểu nguyên thuỷ (Java primitive and primitive wrapper types), String, Date, BigDecimal, BigInteger.

Ví dụ bên dưới trình bày cách định nghĩa một entity với khoá chính có kiểu long:

 

@Entity

public class Student {



    @Id

    private long studentId;

    

    // standard constructor, getters, setters

}

 

3. Cách tạo định danh

Nếu chúng ta muốn giá trị của khoá chính có thể tự sinh ra cho chúng ta, có thể thêm annotation @GeneratedValue.

Đây là 4 kiểu tự sinh: AUTO, IDENTITY, SEQUENCE, TABLE.

Nếu chúng ta không định nghĩa kiểu tự sinh rõ ràng thì mặc định sẽ là AUTO.

3.1. Kiểu tự sinh AUTO

Nếu chúng ta sử dụng kiểu sinh mặc định, persistence provider sẽ xác định giá trị dựa vào thuộc tính của khoá chính. Kiểu có thể là số hoặc UUID.

Với giá trị là số, sẽ tạo giá trị lần lượt hoặc theo bảng, còn giá trị UUID sẽ sử dụng UUIDGenerator.

Ví dụ dưới sử dụng tự sinh AUTO:

 

@Entity

public class Student {



    @Id

    @GeneratedValue

    private long studentId;



    // ...

}

Trong ví dụ này, giá trị khoá 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 nó, tất cả những gì chúng ta cần làm đơn giản là khai báo id với kiểu là UUID với annotation là @GeneratedValue:

 

@Entity

public class Course {



    @Id

    @GeneratedValue

    private UUID courseId;



    // ...

}

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

3.2. Kiểu tự sinh IDENTITY

Đây là kiểu tự sinh dựa vào IdentityGenerator trong đó giá trị mong đợi sẽ được sinh ra bởi một cột định danh trong database, có nghĩa là chúng tự tăng.

Để sử dụng kiểu tự sinh này, chúng ta chỉ cần thiết lập đối số strategy:

@Entity

public class Student {



    @Id

    @GeneratedValue (strategy = GenerationType.IDENTITY)

    private long studentId;



    // ...

}

Một điều cần lưu ý kiểu tự sinh IDENTITY không thể cập nhật hàng loạt được.

3.3. Kiểu tự sinh SEQUENCE

Để sử dụng kiểu tự sinh này, Hibernate cung cấp lớp SequenceStyleGenerator.

Kiểu tự sinh này sẽ sử dụng các trình tự nếu nó được hỗ trợ trong cơ sở dữ liệu của chúng ta và đổi sang kiểu tự sinh table nếu nó không được hỗ trợ.

Để tuỳ chỉnh tên trình tự, chúng ta có thể sử dụng annotation @GenericGenerator với phương pháp 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 ta thiết lập giá trị khởi tạo của trình tự, có nghĩa là khoá chính sẽ được sinh ra bắt đầu từ 4.

SEQUENCE là kiểu tự sinh được gợi ý bởi tài liệu của Hibernate.

Giá trị được sinh ra là duy nhất cho mỗi trình tự. Nếu bạn không chỉ định một 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. Kiểu tự sinh TABLE

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

Ví dụ dưới là tuỳ biến tên bảng sử dụng annotation @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;



    // ...

}

Trong ví dụ này, chúng ta có thể thấy thuộc tính khác như pkColumnNamevalueColumnName được tuỳ biến.

Nhược điểm của phương pháp là không mở rộng quy mô tốt và ảnh hưởng tiêu cực đến hiệu năng.

Tóm lại, bốn kiểu tự sinh này sẽ tạo ra kết quả giống nhau nhưng sẽ khác ở cơ chế của cơ sở dữ liệu.

3.5. Kiểu tự sinh tuỳ biến

Nếu chúng ta không muốn sử dụng phương pháp nào trong 4 phương pháp trên, chúng ta có thể định nghĩa kiểu tự sinh riêng bằng cách triển khai interface IdentifierGenerator.

Hãy cùng tạo một kiểu tự sinh sẽ xây dựng định danh chứa một tiền tố là String và một số:

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

    }

}

Trong ví dụ này, chúng ta sẽ override phương thức generate() từ interface IdentifierGenerator và đầu tiên là tìm số lớn nhất từ khoá chính đã tồn tại của kiểu prefix-XX.

Sau đó chúng ta cộng 1 vào số lớn nhất được tìm thấy và nối thêm thuộc tính tiền tố để lấy giá trị id mới được tạo.

Trong class chúng ta triển khai interface Configurable, chúng ta sẽ thiết lập giá trị tiền tố trong phương thức configure().

Tiếp theo, hãy thêm kiểu tự sinh tuỳ biến này vào một entity. Để làm điều này, chúng ta có thể sử dụng annotation @GenericGenerator với một đối số có chứa tên đầy đủ của class tự sinh của chúng ta.

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



    // ...

}

 

Ngoài ra, chúng ta cần chú ý đã thiết lập đối số tiền tố thành “prod”.

 

 Hãy xem phần kiểm tra nhanh bằng JUnit để hiểu rõ hơn về giá trị id tự sinh:

 

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

}

Ở đây, giá trị đầu tiên được tạo bằng tiền tố “prod” là “prod-1”, tiếp theo là “prod-2”.

4. Định dạng tổng hợp

Bên cạnh các kiểu định dạng cơ bản chúng ta đã xem, Hibernate cũng cho phép chúng ta định nghĩa kiểu định dạng tổng hợp.

Một id tổng hợp được đại diện bởi một lớp khoá chính với một hoặc nhiều hơn các thuộc tính persistent.

Lớp khoá chính bắt buộc phải đáp ứng một số điều kiện:

  • nó nên được định nghĩa bằng cách sử dụng annotation @EmbeddedId hoặc @IdClass
  • nó nên được công khai, serializable và có một constructor công không không cần tham số.
  • nó nên triển khai phương thức equals()hashCode()

Các thuộc tính của class có thể đơn giản, phức hợp hoặc ManyToOne và nên tránh các các collection và thuộc tính OneToOne.

4.1. @EmbeddedId

Để định nghĩa một id sử dụng @EmbeddedId, đầu tiên chúng ta cần một annotation class khoá chính với @Embeddable:

@Embeddable

public class OrderEntryPK implements Serializable {



    private long orderId;

    private long productId;



    // standard constructor, getters, setters

    // equals() and hashCode() 

}

 

Tiếp theo, chúng ta có thể thêm một id kiểu OrderEntryPK vào một entity sử dụng @EmbeddedId:

 

@Entity

public class OrderEntry {



    @EmbeddedId

    private OrderEntryPK entryId;



    // ...

}

 

Cùng xem cách chúng ta có thể sử dụng kiểu của id hỗn hợp để thiết lập khoá chính cho một 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);

}

Ở đây đối tượng OrderEntry có một id chính OrderEntryPK được tạo nên bởi hai thuộc tính: orderId productId.

4.2. @IdClass

Annotation @IdClass tương tự với @EmbeddedId, ngoại trừ những thuộc tính được định nghĩa trong class entity chính sử dụng @Id cho từng thuộc tính.

Lớp khoá chính sẽ trông giống như sau đây.

Hãy viết lại ví dụ OrderEntry với @IdClass:

 

@Entity

@IdClass(OrderEntryPK.class)

public class OrderEntry {

    @Id

    private long orderId;

    @Id

    private long productId;

    

    // ...

}

 

Sau đó chúng ta có thể thiết lập giá trị id trực tiếp trong đối tượng OrderEntry:

 

@Test

public void whenSaveIdClassEntity_thenOk() {        

    OrderEntry entry = new OrderEntry();

    entry.setOrderId(1L);

    entry.setProductId(30L);

    session.save(entry);



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

}

Chú ý rằng đối với cả hai loại id tổng hợp, lớp khoá chính có thể chứa trường @ManyToOne.

Hibernate cho phép định nghĩa khoá chính được tạo thành từ các liên kết @ManyToOne kết hợp với annotation @Id. Trong trường hợp này, lớp entity nên đáp ứng các điều kiện của một lớp khoá chính.

Nhược điểm của phương thức này là không có sự phân tách giữa đối tượng thực thể và định danh.

5. Định danh có nguồn gốc

Các định danh có nguồn gốc được lấy từ một liên kết entity sử dụng chú thích @MapsId.

Đầu tiên, hãy tạo một entity UserProfile lấy id của nó từ một liên kết one-to-one với entity User:

 

@Entity

public class UserProfile {



    @Id

    private long profileId;

    

    @OneToOne

    @MapsId

    private User user;



    // ...

}

 

Tiếp theo, hãy xác thực UserProfile có cùng id với User được liên kết của nó:

@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. Tổng kết

Trong bài viết này, chúng ta đã tiếp cận rất nhiều cách để định danh trong Hibernate. 

Tất cả code trong bài viết này có thể xem ở GitHub này.