JPA khá hay, tuy nhiên không phải tất cả các tính năng nó cung cấp đều hữu ích, trong bài này mình sẽ liệt kê một số annotation mà bạn có thể biết đến nó nhưng chưa chắc đã dùng đến nó trong dự án thực tế, và mình cũng chưa bao giờ sử dụng thực tế chúng.

ManyToMany annotation

Hãy nói bạn có 2 bảng quan hệ nhiều nhiều với nhau thế này:

Một học sinh có thể thích nhiều khoá học, và một khoá học có thể cho phép nhiều học sinh thích, mã nguồn cài đặt bằng java sẽ như sau:

@Entity
class Student {
    @Id
    Long id;
    @ManyToMany
    Set<Course> likedCourses;
}

@Entity
class Course {

    @Id
    Long id;

    @ManyToMany
    Set<Student> likes;
}

Hình ảnh và mã nguồn bạn có thể tham khảo từ đây: https://www.baeldung.com/jpa-many-to-many
Thoạt nhìn có vẻ bạn thấy rất ổn khi chúng ta có thể sử dụng mã nguồn sau để truy vấn đơn giản là có thể lấy được danh sách khoá học đã thích và danh sách học sinh nếu cần:

@ManyToMany
@JoinTable(
  name = "course_like", 
  joinColumns = @JoinColumn(name = "student_id"), 
  inverseJoinColumns = @JoinColumn(name = "course_id"))
Set<Course> likedCourses;
@ManyToMany(mappedBy = "likedCourses")
Set<Student> likes;

Tuy nhiên trong thực tế sẽ thế nào nếu một học viên có thể thích hàng nghìn khoá học, trong khi đó lại có hàng trăm nghìn khoá học? Giả sử mỗi một bản ghi khoá học hoặc học viên là 1KB thì 100,000 bản ghi sẽ là 100MB con số này quá lớn để có thể tải lên bộ nhớ khi có quá nhiều yêu cầu gửi đến. Vậy nên sẽ không thể nào áp dụng cách này được. Tốt nhất chúng ta vấn sẽ sử dụng cách thông thường là khai báo 3 lớp entity như sau:

@Entity
class Student {
    @Id
    Long id;
}

@Entity
class Course {

    @Id
    Long id;
}


class CourseLikeId implements Serializable {
    Long studentId;
    Long courseId;
}

@IdClass(CourseLikeId.class)
@Entity
class CourseLike {
    @Id
    Long studentId;
    @Id
    Long courseId;
}

Khi cần truy vấn danh sách học viên thích một cuốn sách chẳng hạn bạn có thể áp dụng câu lệnh join và phân trang như sau để đảm bảo không bị tràn bộ nhớ như sau:

inteface CourseLikeRepository extends JpaRepository<CourseLike, CourseLikeId> {
    @Query(
        "SELECT s FROM Student s 
        INNER JOIN CourseLike cl ON s.id = cl.studentId 
        WHERE cl.courseId = ?1"
    )
    List<Student> findStudentsByCourseId(long courseId, Pageable pageable);
}

Các annotation quan hệ khác

Cũng cùng một vấn đề với ManyToMany annotation các annotatoin sau cũng tương đối phế nên ít được sử dụng trong thực thế:

  1. OneToMany
  2. ManyToOne

Các annotation vòng đời của một entity

Một entity cũng có vòng đời như được tải từ cơ sở dữ liệu, được lưu, được update và bị loại bỏ tương ứng với các annotation:

  1. PostLoad
  2. PostPersist
  3. PostRemove
  4. PostUpdate
  5. PrePersist
  6. PreRemove
  7. PreUpdate
    Ví dụ sử dụng với lớp User thế này:
public class User {
    private static Log log = LogFactory.getLog(User.class);

    @Id
    @GeneratedValue
    private int id;
    
    private String userName;
    private String firstName;
    private String lastName;
    @Transient
    private String fullName;
    
    @PrePersist
    public void logNewUserAttempt() {
        log.info("Attempting to add new user with username: " + userName);
    }
    
    @PostPersist
    public void logNewUserAdded() {
        log.info("Added user '" + userName + "' with ID: " + id);
    }
    
    @PreRemove
    public void logUserRemovalAttempt() {
        log.info("Attempting to delete user: " + userName);
    }
    
    @PostRemove
    public void logUserRemoval() {
        log.info("Deleted user: " + userName);
    }
    
    @PreUpdate
    public void logUserUpdateAttempt() {
        log.info("Attempting to update user: " + userName);
    }
    
    @PostUpdate
    public void logUserUpdate() {
        log.info("Updated user: " + userName);
    }
    
    @PostLoad
    public void logUserLoad() {
        fullName = firstName + " " + lastName;
    }
}

Bạn có thể tham khảo mã nguồn tại đây.
Trên thực tế chúng ta sẽ thường phân tầng xử lý, nghĩa là các lớp dữ liệu chỉ lo nghiệp vụ lưu dữ liệu, còn vần xử lý logic tầng khác lo, ví dụ như thay vì đưa một đối tượng log khá ngây ngô vào đối tượng User chúng ta sẽ log ở tầng service thế này:

class UserService {
    private final UserRepository userRepository;
    
    private static Log log = LogFactory.getLog(UserService.class);
    
    public void saveUser(User user) {
        log.info("Attempting to add new user with username: " + user.getUserName());
        user = userRepository.save(user);
        log.info("Added user '" + user.getUserName() + "' with ID: " + user.getId());
    }
}

Làm như vậy sẽ linh hoạt và rõ ràng hơn rất nhiều.

Tổng kết

Như vậy chúng ta đã cùng nhau tìm hiểu một số annotation tương đối phế trong JPA.


Cám ơn bạn đã quan tâm đến bài viết|video này. Để nhận được thêm các kiến thức bổ ích bạn có thể:

  1. Đọc các bài viết của TechMaster trên facebook: https://www.facebook.com/techmastervn
  2. Xem các video của TechMaster qua Youtube: https://www.youtube.com/@TechMasterVietnam nếu bạn thấy video/bài viết hay bạn có thể theo dõi kênh của TechMaster để nhận được thông báo về các video mới nhất nhé.
  3. Chat với techmaster qua Discord: https://discord.gg/yQjRTFXb7a