Association (Mối quan hệ giữa 2 bảng trong database) là một trong những tính năng chính của JPA và Hibernate. Giúp mô hình hóa các mối quan hệ giữa 2 bảng trong database thành các thuộc tính trong Entity class.
JPA và Hibernate hỗ trợ tất cả các mối quan hệ giữa 2 bảng có trong database bao gồm:
- one-to-one
- many-to-one
- many-to-many
Bạn có thể ánh xạ các mối quan hệ ở dạng 1 chiều hoặc 2 chiều đều được. Điều này có nghĩa là bạn có thể mô hình hóa mối quan hệ trên 1 hoặc cả 2 entity mà không làm ảnh hưởng đến các bảng bên dưới database. Chúng ta sẽ tìm hiểu sâu hơn về vấn đề này ở những phần sau.
Many-to-One Association
Một đơn đặt hàng (OrderItem) có nhiều sản phẩm (Item) và một sản phẩm chỉ thuộc về một đơn đặt hàng. Đây là một ví dụ điển hình cho mối many-to-one association. Nếu bạn muốn mô hình hóa mối quan hệ này dưới database thì bạn cần lưu một khóa ngoại trong bảng Item tham chiếu đến primary key của bảng Order.
CREATE TABLE OrderItem ( OrderItemId int NOT NULL, OrderNumber int NOT NULL, // .... ); CREATE TABLE Item ( ItemId int NOT NULL, OrderItemId int NOT NULL, PRIMARY KEY (ItemId ), FOREIGN KEY (OrderItemId ) REFERENCES OrderItem (OrderItemId ) // ... );
Với JPA – Hibernate bạn có thể ánh xạ mối quan hệ này theo 2 cách, hoặc là ánh xạ một chiều với một thuộc tính OrderItem thuộc về Item entity. Hoặc là ánh xạ 2 chiều trong với Item chứa một thuộc tính OrderItem và trong OrderItem chứa một tập Item Collection.
Trong many-to-one entity ánh xạ của bảng chiều N(nhiều) sẽ được ánh xạ với @ManyToOne annotation, entity ứng với chiều 0 hoặc 1 (ít) được ánh xạ với @OneToMany annotation.
Ánh xạ một chiều trong Many-to-One
Để ánh xạ một chiều trong many-to-one, trước hết chúng ta cần xác định Item sẽ chứa khoá ngoại tham chiếu đến khóa chính của bảng OrderItem với @ManyToOne annotation.
@Entity public class Item { @ManyToOne private OrderItem orderItem; // ... }
Mặc định Hibernate sẽ generate tên của khóa ngoại dựa trên tên của thuộc tính và primary key của bảng được tham chiếu. Trong ví dụ này thì khóa ngoại sẽ có tên là orderItem_id.
Nếu bạn muốn đặt một cái tên khác cho khóa ngoại thì có thể sử dụng @JoinColumn annotation chỉ định trong thuộc tính name.
@Entity public class Item { @ManyToOne @JoinColumn(name = "fk_order") private OrderItem orderItem; // .... }
Ánh xạ hai chiều trong Many-to-One
Ánh xạ 2 chiều trong many-to-one association là một cách sử dụng phổ biến trong JPA và Hibernate. Trên mỗi Item và OrderItem entity đều chứa các thuộc tính liên kết đến nhau. Item vẫn được ánh xạ như phần trên.
@Entity public class Item { @ManyToOne private OrderItem orderItem; // ... }
Ánh xạ trên Item là đã đủ các thông tin cần thiết để Hibernate triển khai mối quan hệ giữa 2 bảng. Để ánh xạ 2 chiều trong Hibernate bạn cần sử dụng @OneToMany annotation với mappedBy.
@Entity public class OrderItem { @OneToMany(mappedBy = "orderItem") private Set<Item> items = new HashSet<>(); // ... }
Ở đây chúng ta cần chú ý giá trị của mappedBy là tên của thuộc tính ánh xạ trong Item. Như vậy chúng ta đã có thể thiết lập được mối quan hệ hai chiều giữa OrderItem và Item.
Many-to-Many Association
Trong database mối quan hệ Many-to-Many thường được biểu diễn bằng cách tạo ra một bảng trung gian chứa cặp khóa chính của 2 bảng trong mối quan hệ many-to-many. Đối với Hibernate bạn không cần ánh xạ bảng trung gian này thành một Entity class bằng cách sử dụng @ManyToMany annotation.
Ví dụ về many-to-many association dễ dàng thấy là Product và Store, mỗi Store có thể bán nhiều Product và mỗi Product có thể bán ở nhiều Store. Tương tự chúng ta có thể ánh xạ một chiều hoặc hai chiều tùy vào mục đích sử dụng. Nhưng chú ý hãy sử dụng Set thay vì List khi ánh xạ các thuộc tính liên kết. Nếu không, khi xóa các entity trong many-to-many Hibernate sẽ xóa toàn bộ các record và thêm mới lại.
Ánh xạ một chiều trong Many-to-Many
Như đã thảo luận ở phần trên, bạn có thể ánh xạ 1 chiều trong many-to-many relationship với @ManyToMany annotation.
@Entity public class Store { @ManyToMany private Set<Product> products = new HashSet<Product>(); … }
Nếu bạn không cung cấp thông tin gì thêm, Hibernate sẽ kết hợp tên của 2 bảng Store và Product để đặt tên cho bảng trung gian Store_Product với 2 cột tương ứng với cặp khóa chính của bảng store_id và products_id.
Nếu bạn muốn tùy chỉnh thì có thể sử dụng @JoinTable annotation với các thuộc tính joinColumns và inverseColumns. Trong đó
- joinColumns – Chỉ định tên khóa ngoại tham chiếu đến bảng sở hữu thuộc tính ánh xạ..
- inverseColumns – Chỉ định tên khóa ngoại tham chiếu đến bảng được liên kết.
Trong ví dụ này, joinColumns được dùng để chỉ định tên khóa ngoại tham chiếu đến bảng Store và inverseColumns chỉ định tên khóa ngoại tham chiếu đến bảng Product.
@Entity public class Store { @ManyToMany @JoinTable(name = "store_product", joinColumns = { @JoinColumn(name = "fk_store") }, inverseJoinColumns = { @JoinColumn(name = "fk_product") }) private Set<Product> products = new HashSet<>(); // ... }
Ánh xạ 2 chiều trong Many-to-Many
Ánh xạ 2 chiều cho phép cho phép chúng ta truy cập các thuộc tính trong mối quan hệ từ 2 entity, Việc chúng ta ánh xạ trên một trong 2 entity many-to-many là đủ để Hibernate có thể ánh xạ mối quan hệ này, entity còn lại chỉ việc tham chiếu đến ánh xạ của entity kia tương tự many-to-one với thuộc tính mappyBy.
@Entity public class Store { @ManyToMany @JoinTable(name = "store_product", joinColumns = { @JoinColumn(name = "fk_store") }, inverseJoinColumns = { @JoinColumn(name = "fk_product") }) private Set<Product> products = new HashSet<>(); // ... } @Entity public class Product { @ManyToMany(mappedBy="products") private Set<Store> stores = new HashSet<>(); // .... }
Trong mối quan hệ 2 chiều khi muốn thêm hoặc xóa một entity chúng ta cần cập nhật ở cả 2 phía để đảm bảo tính nhất quán của dữ liệu. Vì thao tác này sử dụng khá thường xuyên nên mình khuyên nên viết ra một hàm riêng để reuse code
@Entity public class Store { public void addProduct(Product p) { this.products.add(p); p.getStores().add(this); } public void removeProduct(Product p) { this.products.remove(p); p.getStores().remove(this); } … }
Ok, chúng ta đã hoàn thành bài hướng dẫn many-to-many association mapping. Chúng ta sẽ đi đến phần cuối cùng trong bài viết này one-to-one relationship mapping.
OneToOne Association
One-to-One có lẽ là mối quan hệ mà chúng ta sẽ ít gặp nhất khi làm việc với cơ sở dữ liệu quan hệ, tuy nhiên nếu làm việc với nó một thời gian dài thì chắc chắn các bạn sẽ gặp nó. Đây là lí do mình để nó ở phần cuối cùng, nhưng hay hãy cố đọc hết vì nó cũng gần giống với 2 cách ánh xạ trên.
Mình sẽ lấy ví dụ cho mối quan hệ one-to-one về Customer và ShippingAddress, mỗi khách hàng chỉ có thể có một địa chỉ giao hàng và mỗi địa chỉ chỉ thuộc một khách hàng. Tuy nhiên trong thực tế thì có thể có nhiều khách hàng ở cùng một địa chỉ, nên các bạn cứ xem đây như là một giao ước trong ứng dụng cho mình làm ra.
Phần này cũng tương tự chúng ta sẽ có ánh xạ 1 chiều và 2 chiều, nhưng trước khi cần xác định bảng nào tham chiếu đến bảng nào. Nếu các bạn quên lý thuyết thì có thể xem lại nhé, phần này mình sẽ không giải thích, Custommer sẽ giữ khoá ngoại tham chiếu đến ShippingAddress.
Ánh xạ một chiều trong One-To-One
Trong ánh xạ một chiều buộc chúng ta phải ánh xạ cho entity chứa khoá ngoại tham chiếu đến bảng kia, trong ví dụ của mình chính là Custommer. Mặc định Hibernate sẽ generate tên của khóa ngoại dựa trên tên của thuộc tính và primary key của bảng được tham chiếu. Sử dụng @JoinColumn để thay đổi giá trị mặc định này.
@Entity public class Customer{ @OneToOne @JoinColumn(name = “fk_shippingaddress”) private ShippingAddress shippingAddress; … }
Ánh xạ 2 chiều trong One-To-One
Ánh xạ 2 chiều là một tính năng tiện lợi mà Hibernate cung cấp cho chúng ta có thể truy xuất từ cả 2 chiều. Trên thực tế thì ánh xạ 1 chiều là đã đủ các thông tin cần thiết cho Hibernate. Tương tự các phần trên, ánh xạ 1 chiều vẫn sẽ được giữ nguyên.
@Entity public class Customer{ @OneToOne @JoinColumn(name = “fk_shippingaddress”) private ShippingAddress shippingAddress; … }
Ở bảng được tham chiếu bạn chỉ cần sử dụng @OneToOne với giá trị của mappBy là tên của thuộc tính được ánh xạ trong Customer entity.
@Entity public class ShippingAddress{ @OneToOne(mappedBy = “shippingAddress”) private Customer customer; … }
Tóm lược
Các mối quan hệ trong cơ dữ liệu dữ liệu quan hệ như many-to-many, many-to-one và one-to-one hoàn toàn có thể ánh xạ trên JPA – Hibernate nhưng có tính năng vượt trội hơn là chúng ta có thể ánh xạ 1 chiều hay 2 chiều tuỳ ý. Tuy nhiên ánh xạ 1 chiều là bắt buộc vì đây là các thông tin cần thiết để Hibernate có thể biểu diễn đúng mối quan hệ của 2 bảng dưới database, trong khi ánh xạ 2 chiều chỉ là tính năng Hibernate cung cấp thêm nhầm tăng tính tiện dụng cho tầng xử lý dữ liệu.
Sau cùng, các bạn có thể tham khảo mã nguồn được mình công khai trên github.
Nếu vẫn chưa biết cách cấu hình một project JPA – Hibermate thì bạn có thể tham khảo bài viết sau để thực hành.
Nguồn tham khảo
https://thorben-janssen.com/ultimate-guide-association-mappings-jpa-hibernate