Quan hệ One-to-Many và Many-to-One trong Spring Boot JPA

Trong cơ sở dữ liệu quan hệ, mối quan hệ One-to-Many xảy ra khi một bản ghi ở bảng cha tương ứng với nhiều bản ghi ở bảng con. Ngược lại, Many-to-One là khi nhiều bản ghi ở bảng con tham chiếu tới một bản ghi duy nhất ở bảng cha. Hai bảng được liên kết với nhau thông qua khóa ngoại.

1. Cấu hình phía One-to-Many

Đoạn mã dưới đây minh họa thực thể Department – đại diện cho bảng cha, sở hữu danh sách nhiều nhân viên:

package com.example.jpa.relations.model;

import javax.persistence.*;
import java.util.List;

@Entity
@Table(name = "departments")
public class Department {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    @Column(name = "dept_id")
    private Long deptId;

    @Column(name = "dept_name")
    private String deptName;

    @OneToMany(
        targetEntity = Employee.class,
        mappedBy = "departmentRef",
        fetch = FetchType.EAGER,
        cascade = CascadeType.ALL
    )
    private List<Employee> employees;

    public Long getDeptId() {
        return deptId;
    }

    public void setDeptId(Long deptId) {
        this.deptId = deptId;
    }

    public String getDeptName() {
        return deptName;
    }

    public void setDeptName(String deptName) {
        this.deptName = deptName;
    }

    public List<Employee> getEmployees() {
        return employees;
    }

    public void setEmployees(List<Employee> employees) {
        this.employees = employees;
    }
}

2. Cấu hình phía Many-to-One

Thực thể Employee nằm ở phía nhiều, trỏ về một Department duy nhất:

package com.example.jpa.relations.model;

import com.fasterxml.jackson.annotation.JsonIgnore;
import javax.persistence.*;

@Entity
@Table(name = "employees")
public class Employee {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    @Column(name = "emp_id")
    private Long empId;

    @Column(name = "emp_name")
    private String empName;

    @Column(name = "dept_id")
    private Long deptId;

    @ManyToOne
    @JoinColumn(
        name = "dept_id",
        insertable = false,
        updatable = false,
        referencedColumnName = "dept_id"
    )
    @JsonIgnore
    private Department departmentRef;

    public Long getEmpId() {
        return empId;
    }

    public void setEmpId(Long empId) {
        this.empId = empId;
    }

    public String getEmpName() {
        return empName;
    }

    public void setEmpName(String empName) {
        this.empName = empName;
    }

    public Long getDeptId() {
        return deptId;
    }

    public void setDeptId(Long deptId) {
        this.deptId = deptId;
    }

    public Department getDepartmentRef() {
        return departmentRef;
    }

    public void setDepartmentRef(Department departmentRef) {
        this.departmentRef = departmentRef;
    }
}

3. Phân tích thuộc tính @OneToMany

Annotation @OneToMany được đặt trên một trường kiểu List (hoặc Set) để lưu trữ tập hợp các thực thể con. Các tham số chính:

  • targetEntity: chỉ định lớp thực thể sẽ được liên kết (tương ứng với bảng con).
  • mappedBy: trỏ tới tên thuộc tính ở phía thực thể con giữ mối quan hệ. Tham số này không được dùng cùng với @JoinTable, và cũng không được áp dụng trên @ManyToOne.
  • fetch: chiến lược tải dữ liệu, gồm LAZY (tải khi cần) và EAGER (tải ngay). Theo đặc tả JPA, mặc định @OneToMany là LAZY.
  • cascade: xác định các thao tác lan truyền, bao gồm ALL, PERSIST, MERGE, REMOVE, REFRESH, DETACH.

Theo đặc tả JPA, phía "nhiều" trong quan hệ hai chiều Many-to-One luôn là chủ sở hữu (owning side), do đó mappedBy không thể khai báo trên @ManyToOne.

4. Phân tích thuộc tính @ManyToOne và @JoinColumn

  • @ManyToOne: đánh dấu mối quan hệ nhiều-một, trỏ thực thể con về thực thể cha.
  • @JoinColumn: xác định cột khóa ngoại dùng để nối hai bảng. Các tham số insertable = false và updatable = false ngăn không cho JPA tự động sinh lệnh INSERT/UPDATE trên cột này, tránh xung đột với cột deptId vốn đã được khai báo riêng. Tham số referencedColumnName chỉ định cột được tham chiếu ở bảng cha.
  • @JsonIgnore: loại trừ thuộc tính departmentRef khỏi kết quả JSON khi serialize, nhằm tránh vòng lặp tham chiếu giữa hai thực thể.

Thẻ: Spring Boot jpa hibernate OneToMany ManyToOne

Đăng vào ngày 6 tháng 10 lúc 19:10