Học viên: Nguyễn Chí Hiếu
Lớp: Java Fullstack 15
Email: hieunc82@gmail.com
Số điện thoại: 0989555658
Nguồn tham khảo: https://www.baeldung.com/hibernate-one-to-many


1. Giới thiệu

Hướng dẫn ngăn Hibernate này sẽ đưa chúng ta thông qua một ví dụ về một one-to-many ánh xạ sử dụng các Annotation JPA, một cách thay thế XML.

Chúng ta sẽ tìm hiểu những mối quan hệ hai chiều (bidirectional relationships), cách chúng có thể tạo ra sự không nhất quán (Inconsistencies), và ý tưởng của quyền sở hữu (Ownership) có thể giải quyết nó.

2. Mô tả

Đơn giản là ánh xạ one-to-many có nghĩa là một hàng trong bảng được ánh xạ thành nhiều hàng trong bảng khác.

Hãy xem sơ đồ thực entity-relationship sau đây để thấy mối quan hệ one-to-many:

relation

Đối với ví dụ này, chúng ta sẽ triển khai một hệ thống cart trong đó chúng ta có một bảng cho mỗi “cart” và một bảng cho mỗi “item”.Một cart có thể có nhiều item, vì vậy ở đây chúng ta có một ánh xạ một-nhiều.

Chúng ta sẽ triển khai một hệ thống Cart trong đó chúng ta có một bảng cho mỗi Cart và một bảng khác cho mỗi Item. Một Cart có thể có nhiều Item, vì vậy ở đây chúng ta có một ánh xạ one-to-many.

Cách thức hoạt động này trong cơ sở dữ liệu là chúng ta có một cart_id làm khóa chính trong bảng cart và cũng có một cart_id làm khóa ngoại trong item.

Cách chúng ta thực hiện trong mã là dùng @OneToMany.

Hãy ánh xạ lớp Cart với tập hợp các đối tượng Item theo cách phản ánh mối quan hệ trong cơ sở dữ liệu:

public class Cart {
    //...     
    @OneToMany(mappedBy="cart")
    private Set<Item> items;
    //...
}

Chúng ta cũng có thể thêm một tham chiếu đến Cart trong mỗi Item bằng cách sử dụng @ManyToOne, khiến cho mối quan hệ này trở thành hai chiều (bidirectional relationship).Hai chiều nghĩa là chúng ta có thể truy cập các items từ carts, và cũng có thể truy cập các carts từ items.

Để chỉ cho Hibernate biết chúng ta đang sử dụng biến nào để đại diện cho lớp cha trong lớp con của chúng ta, chúng ta sử dụng thuộc tính mappedBy.

Các công cụ và thư viện sau được sử dụng để phát triển một ứng dụng Hibernate mẫu thực hiện mối quan hệ one-to-many:
• JDK 1.8 or later
• Hibernate 5
• Maven 3 or later
• H2 database

3. Cài đặt

3.1. cài đặt cơ sở dữ liệu

Chúng ta sẽ sử dụng Hibernate để quản lý lược đồ từ domain model. Nói cách khác, chúng ta không cần cung cấp các lệnh SQL để tạo ra các bảng(table) và mối quan hệ giữa các thực thể(entities). Vì vậy, hãy tiếp tục với việc một ứng dụng Hibernate mẫu.

3.2. Maven Dependencies

Hãy bắt đầu bằng cách thêm trình điều khiển Hibernate và H2 driver dependency vào tệp pom.xml của chúng ta. Hibernate dependency sử dụng ghi chú JBoss, và nó tự động được thêm vào như các dependency chuyển tiếp:
• Hibernate version 5.6.7.Final
• H2 driver version 2.1.212

Vui lòng truy cập kho lưu trữ Maven trung tâm để tải phiên bản mới nhất của các dependency Hibernate và [H2]
(https://search.maven.org/classic/#search|ga|1|g%3A"com.h2database").

3.3. Hibernate SessionFactory

Tiếp theo, chúng ta sẽ tạo SessionFactory của Hibernate để tương tác với cơ sở dữ liệu:

public static SessionFactory getSessionFactory() {

    ServiceRegistry serviceRegistry = new StandardServiceRegistryBuilder()
      .applySettings(dbSettings())
      .build();

    Metadata metadata = new MetadataSources(serviceRegistry)
      .addAnnotatedClass(Cart.class)
      // other domain classes
      .buildMetadata();

    return metadata.buildSessionFactory();
}

private static Map<String, String> dbSettings() {
    // return Hibernate settings
}

4. Các Model

Các cấu hình ánh xạ liên quan sẽ được thực hiện bằng cách sử dụng các chú thích JPA trong các model class:

@Entity
@Table(name="CART")
public class Cart {
    //...
    @OneToMany(mappedBy="cart")
    private Set<Item> items;
	
    // getters and setters
}

Lưu ý rằng @OneToMany Annotation được sử dụng để định nghĩa thuộc tính trong class Item dùng để sử dụng để ánh xạ giá trị mappedBy. Đó là lý do tại sao chúng ta có một thuộc tính có tên “cart” trong class Item:

@Entity
@Table(name="ITEMS")
public class Item {
    //...
    @ManyToOne
    @JoinColumn(name="cart_id", nullable=false)
    private Cart cart;

    public Item() {}
    
    // getters and setters
}

Đồng thời lưu ý rằng chú thích @ManyToOne được liên kết với biến class Cart. @JoinColumn Annotation tham chiếu đến cột được ánh xạ.

5. Thực hiện

Trong chương trình chạy thử, chúng ta tạo một class với phương thức main() để lấy Hibernate Session, và lưu các đối tượng model vào cơ sở dữ liệu thực hiện sự kết hợp one-to-many:

sessionFactory = HibernateAnnotationUtil.getSessionFactory();
session = sessionFactory.getCurrentSession();
System.out.println("Session created");
	    
tx = session.beginTransaction();

session.save(cart);
session.save(item1);
session.save(item2);
	    
tx.commit();
System.out.println("Cart ID=" + cart.getId());
System.out.println("item1 ID=" + item1.getId()
  + ", Foreign Key Cart ID=" + item.getCart().getId());
System.out.println("item2 ID=" + item2.getId()
+ ", Foreign Key Cart ID=" + item.getCart().getId());

Đây là kết quả của chương trình chạy thử:

Session created
Hibernate: insert into CART values ()
Hibernate: insert into ITEMS (cart_id)
  values (?)
Hibernate: insert into ITEMS (cart_id)
  values (?)
Cart ID=7
item1 ID=11, Foreign Key Cart ID=7
item2 ID=12, Foreign Key Cart ID=7
Closing SessionFactory

6. @ManyToOne Annotation

Như chúng ta đã thấy ở phần 2, chúng ta có thể chỉ định một mối quan hệ many-to-one bằng cách sử dụng chú thích @ManyToOne. Ánh xạ many-to-one có nghĩa là nhiều đối tượng của Entity này được ánh xạ với một đối tượng của Entity khác - nhiều Item trong một Cart.

@ManyToOne Annotation cho phép chúng ta tạo các mối quan hệ song hướng. Chúng ta sẽ bàn về điều này chi tiết trong các phần tiếp theo.

6.1. Sự không nhất quán và quyền Sở hữu

Bây giờ, nếu Cart tham chiếu đến Item, nhưng Item không tham chiếu lại đến Cart, mối quan hệ của chúng ta sẽ là đơn hướng. Các đối tượng cũng sẽ có tính nhất quán.

Tuy nhiên, trong trường hợp của chúng ta, mối quan hệ là song hướng, mang đến khả năng không nhất quán.

Hãy tưởng tượng một tình huống trong đó người dùng muốn thêm một item1 vào đối tượng cart1 và một item2 vào đối tượng cart2, nhưng phát sinh lỗi khiến cho các tham chiếu giữa cart2 và item2 trở nên không nhất quán:

Cart cart1 = new Cart();
Cart cart2 = new Cart();

Item item1 = new Item(cart1);
Item item2 = new Item(cart2); 
Set<Item> itemsSet = new HashSet<Item>();
itemsSet.add(item1);
itemsSet.add(item2); 
cart1.setItems(itemsSet); // wrong!

Như hình trên, item2 tham chiếu đến cart2, trong khi cart2 không tham chiếu đến item2, và điều đó không tốt.

Hibernate lưu item2 vào cơ sở dữ liệu như thế nào? Liệu item2 có tham chiếu khóa ngoại đến cart1 hay cart2?

Chúng ta giải quyết sự mơ hồ này bằng cách sử dụng ý tưởng về bên sở hữu của mối quan hệ; các tham chiếu thuộc bên sở hữu có ưu tiên và được lưu vào cơ sở dữ liệu.

6.2. Item thuộc bên Sở Hữu

Như đã nêu trong JPA specification trong phần 2.9, đó là một ví dụ tốt để đánh dấu bên many-to-one là bên sở hữu.

Nói cách khác, Item sẽ là bên sở hữu và Cart là bên còn lại, đó là những gì chúng ta đã thực hiện trước đó.

Vậy làm thế nào chúng ta đạt được điều này?

Bằng cách bao gồm thuộc tính mappedBy trong lớp Cart, chúng ta đánh dấu nó là bên còn lại. Đồng thời, chúng ta cũng thêm @ManyToOne annotate trên trường Item.cart, biến Item thành bên sở hữu.

Trở lại ví dụ “không nhất quán” của chúng ta, bây giờ Hibernate biết rằng tham chiếu của item2 quan trọng hơn và sẽ lưu tham chiếu của item2 vào cơ sở dữ liệu.

Hãy kiểm tra kết quả:

item1 ID=1, Foreign Key Cart ID=1
item2 ID=2, Foreign Key Cart ID=2

Mặc dù cart tham chiếu đến item2 trong đoạn mã của chúng ta, tham chiếu của item2 đến cart2 được lưu trong cơ sở dữ liệu.

6.3. Cart là bên sở hữu

Có thể đánh dấu one-to-many bên sở hữu, và many-to-one bên còn lại.

Mặc dù điều này không được khuyến khích, hãy tiếp tục thử nghiệm.

Đoạn mã dưới đây cho thấy cách thực hiện one-to-many là bên sở hữu:

public class ItemOIO {
    //  ...
    @ManyToOne
    @JoinColumn(name = "cart_id", insertable = false, updatable = false)
    private CartOIO cart;
    //..
}

public class CartOIO {
    //..  
    @OneToMany
    @JoinColumn(name = "cart_id") // we need to duplicate the physical information
    private Set<ItemOIO> items;
    //..
}

Chú ý là chúng ta đã loại bỏ phần mappedBy và đặt many-to-one @JoinColumn là insertable và updatable thành false.

Nếu chúng ta chạy mã tương tự, kết quả sẽ ngược lại:

item1 ID=1, Foreign Key Cart ID=1
item2 ID=2, Foreign Key Cart ID=1

Như đã thể hiện ở trên, bây giờ item2 thuộc về cart1.

7. Tổng kết

Chúng ta đã thấy làm thế nào để triển khai mối quan hệ one-to-many với Hibernate ORM và cơ sở dữ liệu H2 bằng cách sử dụng các JPA annotations.

Ngoài ra, chúng ta đã tìm hiểu về mối quan hệ song hướng và cách triển khai khái niệm bên sở hữu.

Mã nguồn trong bài viết trên GitHub.