[Java Backend Zero to Hello] BÀI 4.8: QUAN HỆ TRONG JPA
📚 Bài viết thuộc series Java Backend Zero to Hello 📌 Phần: Phase 4: Database & SQL | Bài 43/86
BÀI 4.8: QUAN HỆ TRONG JPA
Mục tiêu
- Ánh xạ các quan hệ giữa entity
- Sử dụng @OneToOne, @OneToMany, @ManyToOne, @ManyToMany
- Hiểu FetchType và Cascade
- Xử lý Lazy Loading
1. CÁC LOẠI QUAN HỆ
One-to-One: One-to-Many: Many-to-Many:
┌─────┐ ┌─────┐ ┌─────┐ ┌─────┐
│User │←──────────→│Order│ │Stud.│←───→│Course│
└─────┘ 1 1 └─────┘ └─────┘ M M └─────┘
2. @OneToOne
@Entity
public class User {
@Id
@GeneratedValue
private Long id;
private String name;
@OneToOne(cascade = CascadeType.ALL)
@JoinColumn(name = "profile_id")
private Profile profile;
}
@Entity
public class Profile {
@Id
@GeneratedValue
private Long id;
private String bio;
private String avatar;
}
Sử dụng
User user = new User();
user.setName("An");
Profile profile = new Profile();
profile.setBio("Backend Developer");
user.setProfile(profile);
em.persist(user); // Cascade lưu cả profile
3. @OneToMany VÀ @ManyToOne
Quan hệ phổ biến nhất - User có nhiều Orders.
3.1 One-to-Many (phía "One")
@Entity
public class User {
@Id
@GeneratedValue
private Long id;
private String name;
@OneToMany(mappedBy = "user", cascade = CascadeType.ALL, fetch = FetchType.LAZY)
private List<Order> orders = new ArrayList<>();
// Helper method
public void addOrder(Order order) {
orders.add(order);
order.setUser(this);
}
}
3.2 Many-to-One (phía "Many")
@Entity
@Table(name = "orders")
public class Order {
@Id
@GeneratedValue
private Long id;
private Double total;
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "user_id")
private User user;
}
3.3 Sử dụng
User user = em.find(User.class, 1L);
Order order = new Order();
order.setTotal(100.0);
user.addOrder(order); // Set cả 2 phía
em.persist(order);
4. @ManyToMany
Sinh viên - Khóa học (qua bảng trung gian).
4.1 Entity
@Entity
public class Student {
@Id
@GeneratedValue
private Long id;
private String name;
@ManyToMany
@JoinTable(
name = "student_course",
joinColumns = @JoinColumn(name = "student_id"),
inverseJoinColumns = @JoinColumn(name = "course_id")
)
private Set<Course> courses = new HashSet<>();
}
@Entity
public class Course {
@Id
@GeneratedValue
private Long id;
private String name;
@ManyToMany(mappedBy = "courses")
private Set<Student> students = new HashSet<>();
}
4.2 Sử dụng
Student student = new Student();
student.setName("An");
Course course = new Course();
course.setName("Java");
student.getCourses().add(course);
course.getStudents().add(student);
em.persist(student);
em.persist(course);
5. FETCH TYPE
5.1 EAGER - Tải ngay
@ManyToOne(fetch = FetchType.EAGER)
private User user;
5.2 LAZY - Tải khi cần (mặc định cho collection)
@OneToMany(fetch = FetchType.LAZY)
private List<Order> orders;
5.3 LazyInitializationException
// Trong transaction
User user = em.find(User.class, 1L);
em.close(); // Đóng EntityManager
// Ngoài transaction
user.getOrders().size(); // ❌ LazyInitializationException
5.4 Giải pháp
// 1. Fetch trong transaction
@Transactional
public List<Order> getUserOrders(Long userId) {
User user = em.find(User.class, userId);
return user.getOrders(); // OK trong transaction
}
// 2. JOIN FETCH
"SELECT u FROM User u JOIN FETCH u.orders WHERE u.id = :id"
// 3. EntityGraph
@EntityGraph(attributePaths = {"orders"})
User findWithOrders(Long id);
6. CASCADE
Tự động áp dụng operation cho entity liên quan.
@OneToMany(cascade = CascadeType.ALL)
private List<Order> orders;
| Cascade | Mô tả |
|---|---|
PERSIST |
Lưu entity con khi lưu cha |
MERGE |
Update entity con khi update cha |
REMOVE |
Xóa entity con khi xóa cha |
REFRESH |
Refresh entity con khi refresh cha |
DETACH |
Detach entity con khi detach cha |
ALL |
Tất cả |
Ví dụ
@OneToMany(mappedBy = "user", cascade = CascadeType.ALL, orphanRemoval = true)
private List<Order> orders;
// Xóa order khỏi list → tự động xóa khỏi DB
user.getOrders().remove(order);
7. @JoinColumn
@ManyToOne
@JoinColumn(
name = "user_id", // Tên cột FK
referencedColumnName = "id", // Cột tham chiếu
nullable = false,
unique = true
)
private User user;
8. BẢNG TRUNG GIAN VỚI @JoinTable
@ManyToMany
@JoinTable(
name = "user_role", // Tên bảng trung gian
joinColumns = @JoinColumn(name = "user_id"),
inverseJoinColumns = @JoinColumn(name = "role_id"),
uniqueConstraints = @UniqueConstraint(
columnNames = {"user_id", "role_id"}
)
)
private Set<Role> roles;
9. VÍ DỤ HOÀN CHỈNH
9.1 Blog System
@Entity
public class User {
@Id @GeneratedValue
private Long id;
private String username;
@OneToMany(mappedBy = "author", cascade = CascadeType.ALL)
private List<Post> posts = new ArrayList<>();
}
@Entity
public class Post {
@Id @GeneratedValue
private Long id;
private String title;
private String content;
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "author_id")
private User author;
@OneToMany(mappedBy = "post", cascade = CascadeType.ALL)
private List<Comment> comments = new ArrayList<>();
@ManyToMany
@JoinTable(name = "post_tag",
joinColumns = @JoinColumn(name = "post_id"),
inverseJoinColumns = @JoinColumn(name = "tag_id"))
private Set<Tag> tags = new HashSet<>();
}
@Entity
public class Comment {
@Id @GeneratedValue
private Long id;
private String content;
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "post_id")
private Post post;
}
@Entity
public class Tag {
@Id @GeneratedValue
private Long id;
private String name;
}
10. BÀI TẬP THỰC HÀNH
Bài 1: E-commerce
Tạo các entity:
- User (1) - (N) Order
- Order (1) - (N) OrderItem
- Product (N) - (N) Category
Bài 2: Cascade
Test các loại cascade.
Bài 3: Lazy Loading
Xử lý LazyInitializationException.
11. TÓM TẮT
| Quan hệ | Annotation | Mô tả |
|---|---|---|
| 1-1 | @OneToOne |
Một-một |
| 1-N | @OneToMany / @ManyToOne |
Một-nhiều |
| N-N | @ManyToMany |
Nhiều-nhiều |
| Fetch | EAGER / LAZY |
Khi nào tải |
| Cascade | ALL, PERSIST, ... |
Lan truyền operation |
| mappedBy | - | Phía sở hữu quan hệ |
Bài tiếp theo: 4.9 Query nâng cao
🧭 Điều Hướng Series
⬅️ Bài trước: BÀI 4.7: JPA/HIBERNATE CƠ BẢN
📋 Lộ trình tổng quan: Xem Toàn Bộ Series
➡️ Bài tiếp theo: BÀI 4.9: QUERY NÂNG CAO TRONG JPA
All rights reserved