0

[Java Backend Zero to Hello] [Phase 4] BÀI 4.8: QUAN HỆ TRONG JPA

📚 Series: Java Backend Zero to Hello 📂 Phân đoạn: Phase 4: Database & SQL 📖 Nội dung: BÀI 4.8: QUAN HỆ TRONG JPA 💡 Khóa học lập trình Backend Java & Spring Boot chuẩn doanh nghiệp từ con số 0.


BÀI 4.8: QUAN HỆ TRONG JPA

Mục tiêu


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 bài học

⭐ Hãy bookmark (clip) lại series để tiện theo dõi các bài học tiếp theo nhé!


All rights reserved

Viblo
Hãy đăng ký một tài khoản Viblo để nhận được nhiều bài viết thú vị hơn.
Đăng kí