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ư pkColumnName và valueColumnName đượ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() và 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 và 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.
Bình luận