Các kiểu JPA Join và cú pháp trong JPQL
Như bạn biết, SQL hỗ trợ 7 kiểu JOIN khác nhau. Trong bài viết này chúng ta sẽ tìm hiểu xem những kiểu JOIN nào được hỗ trợ bởi JPA và xem xét các ví dụ trong JPQL (Java Persistence Query Language)
- Bắt đầu với JPA JPQL
1. Entities
Hãy xem mô hình dữ liệu mẫu mà chúng ta sẽ sử dụng trong các ví dụ.
Thực thể Employee - mô phỏng một nhân viên.
Employee.java
package org.o7planning.sample_bank_db.entity;
// Imports
@Entity
@Table(name = "Employee")
public class Employee implements Serializable {
@Id
@GeneratedValue
@Column(name = "Emp_Id", nullable = false)
private Long empId;
@Column(name = "Start_Date", nullable = false)
private LocalDate startDate;
@Column(name = "End_Date", nullable = true)
private LocalDate endDate;
@Column(name = "First_Name", length = 32, nullable = false)
private String firstName;
@Column(name = "Last_Name", length = 32, nullable = false)
private String lastName;
@Column(name = "Title", length = 32, nullable = false)
private String title;
@ManyToOne
@JoinColumn(name = "Dept_Id", nullable = true, //
foreignKey = @ForeignKey(name = "Employee_Department_Fk"))
private Department department;
@ManyToOne
@JoinColumn(name = "Superior_Emp_Id", nullable = true, //
foreignKey = @ForeignKey(name = "Employee_Employee_Fk"))
private Employee superiorEmployee;
// Other properties and Getters, Setters.
}
Thực thể Department - mô phỏng một phòng ban.
Department.java
package org.o7planning.sample_bank_db.entity;
// Imports
@Entity
@Table(name = "Department")
public class Department implements Serializable {
@Id
@GeneratedValue
@Column(name = "Dept_Id", nullable = false)
private Long deptId;
@Column(name = "Name", length = 256, nullable = false)
private String name;
@OneToMany(fetch = FetchType.LAZY, mappedBy = "department")
private Set<Employee> employees = new HashSet<>(0);
// Other properties and getters, setters
}
Mô hình dữ liệu được sử dụng trong bài viết này là một cơ sở dữ liệu mẫu, mô phỏng dữ liệu của một ngân hàng. Nếu quan tâm, bạn có thể download dự án dưới đây, nó được viết trên Spring Boot + JPA và hỗ trợ mọi loại cơ sở dữ liệu. Khi chạy dự án, các bảng và dữ liệu sẽ tự động được tạo ra.
- JPA Sample Bank Database
2. Inner JOIN
Đầu tiên, chúng ta xem xét một truy vấn INNER JOIN trong SQL:
SELECT d.* FROM Employee e
INNER JOIN Department d on e.dept_id = d.dept_id
Where e.first_name = 'Susan';
-- Same As:
SELECT d.* FROM Employee e
JOIN Department d on e.dept_id = d.dept_id
Where e.first_name = 'Susan';
- SQL Inner JOIN
INNER JOIN tường minh:
Trong cú pháp INNER JOIN tường minh, từ khoá "INNER JOIN" hoặc "JOIN" sẽ được sử dụng. Cả hai từ khoá này đều có thể sử dụng với ý nghĩa giống nhau.
Mối quan hệ giữa 2 thực thể Employee và Department đã được xác định thông qua thuộc tính "department" của Employee, nên từ khoá "ON" là không cần thiết.
InnerJoinExample1.java
package org.o7planning.jpa_jpql.java_14321;
import java.util.List;
import org.o7planning.sample_bank_db.entity.Department;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import jakarta.persistence.EntityManager;
import jakarta.persistence.TypedQuery;
@Component
public class InnerJoinExample1 {
@Autowired
private EntityManager entityManager;
public void test() {
String jpql = "Select d from Employee e " //
+ " join e.department d " //
+ " where e.firstName = 'Susan'";
//
TypedQuery<Department> query = entityManager.createQuery(jpql, Department.class);
List<Department> departments = query.getResultList();
this.showResults(departments);
}
private void showResults(List<Department> departments) {
for (Department dept : departments) {
System.out.println("Dept: " + dept.getName());
}
}
}
INNER JOIN ngầm định:
Cú pháp INNER JOIN có thể được sử dụng một cách ngầm định (Không cần sử dụng từ khoá "JOIN" và "INNER JOIN").
InnerJoinExample2.java
public void test() {
String jpql = "Select e.department from Employee e " //
+ " where e.firstName = 'Susan'";
//
TypedQuery<Department> query = entityManager.createQuery(jpql, Department.class);
List<Department> departments = query.getResultList();
this.showResults(departments);
}
INNER JOIN tường minh với một tập hợp
InnerJoinExample3.java
package org.o7planning.jpa_jpql.java_14321;
import java.util.List;
import org.o7planning.sample_bank_db.entity.Employee;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import jakarta.persistence.EntityManager;
import jakarta.persistence.TypedQuery;
@Component
public class InnerJoinExample3 {
@Autowired
private EntityManager entityManager;
public void test() {
String jpql = "Select e from Department d " //
+ " JOIN d.employees e " //
+ " where d.name = 'Operations' " //
+ " and e.firstName like 'S%' ";
//
TypedQuery<Employee> query = entityManager.createQuery(jpql, Employee.class);
List<Employee> employees = query.getResultList();
this.showResults(employees);
}
private void showResults(List<Employee> employees) {
for (Employee emp : employees) {
System.out.println("Emp: " + emp.getFirstName() + " " + emp.getLastName());
}
}
}
Output:
Emp: Susan Hawthorne
Emp: Sarah Parker
Emp: Samantha Jameson
INNER JOIN ngầm định với một tập hợp
public void test() {
String jpql = "Select d.employees from Department d " //
+ " where d.name = 'Operations' ";
//
TypedQuery<Employee> query = entityManager.createQuery(jpql, Employee.class);
List<Employee> employees = query.getResultList();
this.showResults(employees);
}
private void showResults(List<Employee> employees) {
for (Employee emp : employees) {
System.out.println("Emp: " + emp.getFirstName() + " " + emp.getLastName());
}
}
3. Left Outer JOIN
Cú pháp LEFT OUTER JOIN sử dụng từ khoá "LEFT JOIN" hoặc "LEFT OUTER JOIN", cả hai đều có ý nghĩa giống nhau.
SELECT d.*, e.emp_id, e.dept_id
FROM department d
LEFT JOIN Employee e on d.dept_Id = e.dept_Id;
-- Same As ---
SELECT d.*, e.emp_id, e.dept_id
FROM department d
LEFT OUTER JOIN Employee e on d.dept_Id = e.dept_Id;
- SQL Outer JOIN
Bạn có thể nhìn thấy sự khác biệt về kết quả giữa hai truy vấn "LEFT OUTER JOIN" và "INNER JOIN" trong ví dụ dưới đây. "LEFT OUTER JOIN" trả về kết quả bao gồm tất cả các bản ghi của bảng bên trái, kể cả khi bảng bên phải không có bản ghi nào thoả mãn điều kiện của mệnh đề "ON".
LEFT OUTER JOIN | INNER JOIN |
|
|
Tìm các phòng ban không có nhân viên hoặc có ít nhất một nhân viên nào đó tên là "Susan".
OuterJoinExample1.java
public void test() {
// Find departments with no employees or employees named "Susan".
String jpql = "Select d from Department d " //
+ " LEFT JOIN d.employees e " //
+ " where e is null or e.firstName = 'Susan'";
//
TypedQuery<Department> query = entityManager.createQuery(jpql, Department.class);
List<Department> departments = query.getResultList();
this.showResults(departments);
}
private void showResults(List<Department> departments) {
for (Department dept : departments) {
System.out.println("Dept: " + dept.getName());
}
}
Output:
Dept: Operations
Dept: Administration
Dept: IT
4. Right Outer JOIN
"RIGHT OUTER JOIN" trả về kết quả bao gồm tất cả các bản ghi của bảng bên phải, kể cả khi bảng bên trái không có bản ghi nào thoả mãn điều kiện của mệnh đề "ON".
RIGHT OUTER JOIN | INNER JOIN |
|
|
Tìm các phòng ban không có nhân viên hoặc có ít nhất một nhân viên nào đó tên là "Susan".
RightOuterJoinExample1.java
public void test() {
// Find departments with no employees or employees named "Susan".
String jpql = "Select DISTINCT d from Employee e " //
+ " RIGHT JOIN e.department d " //
+ " where e is null or e.firstName = 'Susan'";
//
TypedQuery<Department> query = entityManager.createQuery(jpql, Department.class);
List<Department> departments = query.getResultList();
this.showResults(departments);
}
private void showResults(List<Department> departments) {
for (Department dept : departments) {
System.out.println("Dept: " + dept.getName());
}
}
Output:
Dept: Operations
Dept: Administration
Dept: IT
5. JOIN trong mệnh đề WHERE
SQL:
SELECT DISTINCT d.*
FROM Department d, Employee e
Where d.dept_id = e.dept_id
and e.first_name = 'Susan'
Đối với JPQL, chúng ta cũng có thể đặt hai hoặc nhiều Entities trong mệnh đề FROM và chỉ định các điều kiện nối (join) trong mệnh đề WHERE.
JoinInWhereExample1.java
public void test() {
String jpql = "Select DISTINCT d from Department d, Employee e " //
+ " where e.department = d"
+ " and e.firstName = 'Susan'";
//
TypedQuery<Department> query = entityManager.createQuery(jpql, Department.class);
List<Department> departments = query.getResultList();
this.showResults(departments);
}
private void showResults(List<Department> departments) {
for (Department dept : departments) {
System.out.println("Dept: " + dept.getName());
}
}
6. Nhiều mệnh đề JOIN
Kết hợp nhiều Entities trong một truy vấn JPQL:
Ví dụ: Tìm các phòng ban của các nhân viên cấp cao nhất (Những nhân viên không thuộc quyền quản lý của bất kỳ nhân viên nào khác).
MultiJoinExample1.java
public void test() {
String jpql = "Select DISTINCT d from Employee e " //
+ " left join e.superiorEmployee se " //
+ " join e.department d " //
+ " where se is null ";
//
TypedQuery<Department> query = entityManager.createQuery(jpql, Department.class);
List<Department> departments = query.getResultList();
this.showResults(departments);
}
private void showResults(List<Department> departments) {
for (Department dept : departments) {
System.out.println("Dept: " + dept.getName());
}
}
7. Fetch JOIN
"FETCH JOIN" là một khái niệm đặc biệt trong JPQL mà SQL không có. Có khá nhiều điều để nói về chủ đề này vì vậy tôi giải thích nó trong một bài viết riêng biệt. Nếu quan tâm bạn có thể xem trong bài viết dưới đây:
- Tìm hiểu về Fetch JOIN trong JPA JPQL
Type | Keyword |
INNER JOIN FETCH |
|
LEFT OUTER JOIN FETCH |
|
RIGHT OUTER JOIN FETCH |
|