Mối quan hệ nhiều-nhiều với extra columns trong JPA

Trong bài viết trước, mình đã giới thiệu với các bạn về annotation @ManyToMany để thể hiện mối quan hệ nhiều-nhiều giữa 2 bảng bất kỳ trong database, ví dụ như một developer có thể làm trong nhiều dự án khác nhau và một dự án thì lại có nhiều developer. Nhưng thực tế là chúng ta cần lưu thêm một số thông tin khác liên quan đến 2 bảng này, ví dụ như trong dự án thì developer có thể được giao để làm một task nào đó. Trong trường hợp này, chúng ta phải thể hiện bằng các entity trong JPA như thế nào? Trong bài viết này, chúng ta hãy cùng nhau tìm hiểu nhé các bạn!

Đầu tiên, mình sẽ tạo một Maven project để làm ví dụ:

Mối quan hệ nhiều-nhiều với extra columns trong JPA
Mình sẽ sử dụng Hibernate làm implementation của JPA nên sẽ thêm Hibernate dependency như sau:

MySQL Connector:

Project Lombok:

Giả sử bây giờ mình định nghĩa cấu trúc của 2 bảng developer và project có nội dung như sau:

Để thể hiện mối quan hệ nhiều-nhiều giữa 2 bảng trên, mình cũng định nghĩa một joinTable như sau:

Như mình ví dụ ở đầu bài viết, giờ mình muốn lưu trữ thêm thông tin developer sẽ làm gì trong dự án. Như các bạn thấy, ở đây chúng ta chỉ có thể lưu thông tin này trong joinTable mà thôi. Do đó, mình sẽ thêm một column trong bảng developer_project như sau:

Để thể hiện những table này qua entity, chúng ta không thể dùng annotation @ManyToMany được nữa, lí do là bởi vì nếu các bạn sử dụng annotation @ManyToMany, thông tin của joinTable sẽ bị ẩn, và do đó chúng ta không thể hiện được thông tin của column task trong table developer_project.

Giải pháp cho vấn đề này, chúng ta có thể dùng annotation @OneToMany hai chiều. Cụ thể như thế nào, các bạn đọc tiếp nhé!

Để sử dụng annotation @OneToMany hai chiều, chúng ta cần làm những bước sau:

  • Đầu tiên, các bạn cần xây dựng entity cho table developer_project.

Vì bảng developer_project có chứa một Composite Primary Key nên chúng ta sẽ định nghĩa một class với annotation @Embeddable như sau:

Entity cho bảng developer_project sẽ có nội dung như sau:

Trong entity này, mình đã sử dụng annotation @ManyToOne để thể hiện mối quan hệ nhiều-một giữa bảng developer_project với các bảng developer và project.

Ở đây, mình cũng đã sử dụng một annotation @MapsId. Annotation này được sử dụng trong entity có khai báo annotation @EmbeddedId để map với cột primary key được định nghĩa trong đối tượng Embeddable. Do đó, value của annotation @MapsId sẽ là tên biến trong đối tượng @Embeddable map với cột primary key.

  • Bước tiếp theo là chúng ta sẽ định nghĩa entity cho 2 bảng developer và project.

Entity của 2 bảng này sẽ sử dụng annotation @OneToMany:

OK, vậy là chúng ta đã định nghĩa xong các entity rồi đó các bạn. Giờ thử chạy ví dụ xem nào.

Mình sẽ thêm tập tin cấu hình cho JPA, persistence.xml, nằm trong thư mục /src/main/resources/META-INF với nội dung như sau:

Giả sử trong database mình đang có những dữ liệu sau:

Mối quan hệ nhiều-nhiều với extra columns trong JPA

thì khi chạy ví dụ sau:

Kết quả sẽ là:

5/5 - (1 bình chọn)

6 thoughts on “Mối quan hệ nhiều-nhiều với extra columns trong JPA

  1. Anh bị nhầm đoạn mã mappedBy, phải nên giống bên dưới:
    Class Developer:
    @OneToMany(mappedBy = “developer”)
    private List projects;
    Class Project:
    @OneToMany(mappedBy = “project”)
    private List projects;
    Kết quả: Developer: Khanh | Task: Code
    Developer: Quan | Task: Test

  2. anh ơi em bị lỗi này là sao ạ
    Exception in thread “main” org.springframework.orm.jpa.JpaSystemException: Could not set field value [2] value by reflection : [class model.ABKey.akey] setter of model.ABKey.akey; nested exception is org.hibernate.PropertyAccessException: Could not set field value [2] value by reflection : [class model.ABKey.akey] setter of model.ABKey.akey
    at org.springframework.orm.jpa.vendor.HibernateJpaDialect.convertHibernateAccessException(HibernateJpaDialect.java:351)
    at org.springframework.orm.jpa.vendor.HibernateJpaDialect.translateExceptionIfPossible(HibernateJpaDialect.java:253)
    at org.springframework.orm.jpa.AbstractEntityManagerFactoryBean.translateExceptionIfPossible(AbstractEntityManagerFactoryBean.java:527)
    at org.springframework.dao.support.ChainedPersistenceExceptionTranslator.translateExceptionIfPossible(ChainedPersistenceExceptionTranslator.java:61)
    at org.springframework.dao.support.DataAccessUtils.translateIfNecessary(DataAccessUtils.java:242)
    at org.springframework.dao.support.PersistenceExceptionTranslationInterceptor.invoke(PersistenceExceptionTranslationInterceptor.java:153)
    at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:186)
    at org.springframework.data.jpa.repository.support.CrudMethodMetadataPostProcessor$CrudMethodMetadataPopulatingMethodInterceptor.invoke(CrudMethodMetadataPostProcessor.java:135)
    at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:186)
    at org.springframework.aop.interceptor.ExposeInvocationInterceptor.invoke(ExposeInvocationInterceptor.java:93)
    at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:186)
    at org.springframework.data.repository.core.support.SurroundingTransactionDetectorMethodInterceptor.invoke(SurroundingTransactionDetectorMethodInterceptor.java:61)
    at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:186)
    at org.springframework.aop.framework.JdkDynamicAopProxy.invoke(JdkDynamicAopProxy.java:212)
    at com.sun.proxy.$Proxy49.save(Unknown Source)
    at Main.Main.main(Main.java:23)

Add Comment