Tại sao nên sử dụng JPA Criteria
Bài đăng này đã không được cập nhật trong 6 năm
Giới thiệu về JPA Criteria API
JPA Criteria API là một tiền định nghĩa API được sử dụng để định nghĩa câu lệnh queries cho các entities. Nó là một sự ra đời nhằm thay thế cho JPQL query, Những queries được tạo ra là khá an toàn và linh hoạt dễ thay đổi.
Tại sao nên sử dụng JPA Criteria API
Bình thường khi chúng ta viết các câu lệnh queries chúng ta thường sử dụng JPQL query, với cách viết này nó khá tương đồng với các câu lệnh SQL nên khi đọc vào thì chúng ta sẽ cảm thấy dễ hiểu. Nhưng hãy tưởng tượng bạn có hàng nghìn chỗ sử dụng các câu query như vậy, và như chúng ta cũng biết chỉ khi nào ở thời điểm runtime khi chạy qua câu lệnh nào thì chúng ta mới kiểm chứng được độ chính xác của nó. Nếu bây giờ hàng ngàn câu lệnh Sql kia bị ảnh hưởng bởi vì chúng ta thay đổi các Entities, DB ... Như vậy chúng ta sẽ phải sửa đổi hết tất cả các chỗ bị ảnh hưởng, lúc đó cách làm của chúng ta là phải tìm hết những chỗ bị ảnh hưởng, còn chưa kể những công việc do con người làm thì rất dễ bị quên, nhầm lẫn ... JPA Criteria API sẽ tạo ra các queries dựa theo các entites, do đó khi chúng ta thay đổi các entities thì chúng ta sẽ dễ dãng nhận biết được những chỗ bị ảnh hưởng. Như vậy chúng ta sẽ tiết kiệm được rất nhiều chi phí cho việc sửa đổi, và kiểm tra lại những chỗ bị tác động.
Sử dụng JPA Criteria API và Metamodal để tạo Typesafe Queries
Metamodel API
Metamodal là gì và tại sao nên sử dụng
Tại sao chúng ta nên dùng Metamodel API, vậy mục đích của Metamodel API là gì? Metamodel API được sử dụng để tạo Metamodel cho mỗi entity nhằm mục đích quản lý các tên fields hay properties. Như vậy mỗi lần trong câu lệnh query muốn chỉ định đến một field hay property cụ thể nào đó thì chúng ta sẽ lấy thông qua metamodal này, làm như vậy giúp trúng ta có thể tránh được việc hard code trong câu query và quan trọng là khi mỗi Entity thay đổi chẳng hạn như tên field bị thay đổi thì chúng ta có thể biết được câu query nào bị ảnh hưởng do tên metamodal được sử dụng đã bị thay đổi. Tiêu chuẩn chung cho việc đặt tên Metamodal là có dấu gạch ngang ở phía sau tương tự như sau:
@Entity
public class Pet {
@Id
protected Long id;
protected String name;
protected String color;
@ManyToOne
protected Set<Owner> owners;
...
}
The corresponding Metamodel class is:
package com.example;
...
@Static Metamodel(Pet.class)
public class Pet_ {
public static volatile SingularAttribute<Pet, Long> id;
public static volatile SingularAttribute<Pet, String> name;
public static volatile SingularAttribute<Pet, String> color;
public static volatile SetAttribute<Pet, Owner> owners;
}
Cách sử dụng
Chúng ta có thể sử dụng metamodal thông qua việc gọi hàm getModal() của Root<Entitty>
EntityManager em = ...;
CriteriaBuilder cb = em.getCriteriaBuilder();
CriteriaQuery cq = cb.createQuery(Pet.class);
Root<Pet> pet = cq.from(Pet.class);
EntityType<Pet> Pet_ = pet.getModel();
Hoặc có thể gọi thông qua EntityManager như sau
EntityManager em = ...;
Metamodel m = em.getMetamodel();
EntityType<Pet> Pet_ = m.entity(Pet.class);
Criteria API Query
Nhìn chung thì để tạo nên câu SQL thì chúng ta thường có các mệnh đề SELECT, FROM, WHERE Chúng ta sẽ lấy một ví dụ đơn giản của việc tạo query thông qua Criteria API
EntityManager em = ...;
CriteriaBuilder cb = em.getCriteriaBuilder();
CriteriaQuery<Entity class> cq = cb.createQuery(Entity.class);
Root<Entity> from = cq.from(Entity.class);
cq.select(Entity);
TypedQuery<Entity> q = em.createQuery(cq);
List<Entity> allitems = q.getResultList();
Trước tiên để có được instance của CriteriaBuilder interface thì cung ta phải gọi phương thức getCriteriaBuilder của EntityManager hoặc EntityManagerFactory Criteria queries sẽ được xây dựng bằng cách tạo ra instance của interface avax.persistence.criteria.CriteriaQuery đối tượng CriteriaQuery này sẽ định nghĩa cho câu query sẽ trả về giá trị cho đối tượng nào. Để có được instance của nó chúng ta gọi phương thức createQuery của CriteriaBuilder Giả sử nếu chúng ta không muốn kết quả trả về là một entity mà là một kiểu String hay những kiểu giá trị đã được định nghĩa sẵn thì chúng ta sẽ thực hiện như sau
CriteriaQuery<String> cq = cb.createQuery(String.class);
Query Roots
Tiếp theo chúng ta sẽ thấy việc sử dụng Root<Entity> Đối với mỗi đối tượng CriteriaQuery thì Root entity của query là nơi tổ chức các điều hướng của câu query, chúng ta có thể hiểu nó tương tự như mệnh đề FROM trong JPQL query. Để tạo ra một query root chúng ta gọi phương thức from của CriteriaQuery
Root<Entity> pet = cq.from(Entity.class);
Như việc mô tả metalmodal ở trên thì việc gọi và sử dụng có thể được gọi thông qua Root entity
EntityManager em = ...;
Metamodel m = em.getMetamodel();
EntityType<Entity> Entity_ = m.entity(Entity.class);
Root<Entity> pet = cq.from(Entity_);
Criteria queries cũng có thể có nhiều Root entity, như trong trường hợp câu lệnh query muốn điều hướng tới nhiều entities. CriteriaQuery<Entity> cq = cb.createQuery(Entity.class); Root<Entity> pet1 = cq.from(Entity.class); Root<Entity> pet2 = cq.from(Entity.class);
Sử dụng Join cho Querying Relationships
Việc tương tác với database mà có các bảng liên kết với nhau là được sử dụng thường xuyên, vì vậy để tạo ra các liên kết này thì Criteria cung cấp Join với câu lệnh như sau Join<X, Y> với X là entity nguồn, và Y là mục tiêu join Ví dụ của việc Join
CriteriaQuery<Pet> cq = cb.createQuery(Pet.class);
Root<Pet> pet = cq.from(Pet.class);
Join<Pet, Owner> owner = pet.join(Pet_.owners);
Việc join chúng ta cũng có thể nỗi chuỗi lại với nhau cho việc join nhiều hơn 2 bảng với nhau chẳng hạn như
CriteriaQuery<Pet> cq = cb.createQuery(Pet.class);
Root<Pet> pet = cq.from(Pet.class);
Join<Owner, Address> address = cq.join(Pet_.owners).join(Owner_.addresses);
Path Navigation trong Criteria Queries
Đối tượng Path được sử dụng trong mệnh đề SELECT, WHERE của Criteria query Dựa vào đối tượng Path chúng ta có thể chỉ định cụ thể field muốn được lấy, hay chỉ định field nào sẽ được dùng trong câu lệnh kiểm tra điều kiện
CriteriaQuery<String> cq = cb.createQuery(String.class);
Root<Pet> pet = cq.from(Pet.class);
cq.select(pet.get(Pet_.name));
với câu lệnh trên thì chúng ta chỉ định là lấy ra tên của thú cưng
Restricting Criteria Query Results
Kết quả trả về của một query có thể được loại bỏ bớt những dữ liệu không cần thiết bằng cách chúng ta có thể set điều kiện ở trong câu lệnh WHERE. Criteria cung cấp các phương thức để cho việc thực thi các điều kiện theo mong muốn chẳng hạn như isNull, isNotNull, in ... gọi là Expression Một số ví dụ cụ thể : Lấy ra thông tin sinh viên mà không có địa chỉ
CriteriaQuery<Student> cq = cb.createQuery(Student.class);
Root<Student> student = cq.from(Student.class);
cq.where(student.get(Student_.address).isNull());
Hoặc lấy ra thông tin sinh viên có tên là Petter hoặc John
CriteriaQuery<Student> cq = cb.createQuery(Student.class);
Root<Student> student = cq.from(Student.class);
cq.where(student.get(Student_.address).in("Petter", "John"));
Ngoài ra chúng ta có thể tham khảo thêm các method khác của Expression như equal, notEqual, gt, ge, lt, le, between, like
Ordering Results
Việc kết quả trả về có thể sẽ được sắp xếp theo một thứ tự nào đó, theo một giá trị nào đó Criteria cung cấp 2 hàm cho việc này là desc và asc Ví dụ cụ thể Lấy ra danh sách sinh viên theo thứ tự sắp xếp giảm dần theo ngày sinh
CriteriaQuery<Student> cq = cb.createQuery(Student.class);
Root<Student> student = cq.from(Student.class);
cq.select(student);
cq.orderBy(cb.desc(student.get(Student_.birthday)));
Lấy ra danh sách sinh viên theo thứ tự sắp xếp tăng dần theo ngày sinh
CriteriaQuery<Student> cq = cb.createQuery(Student.class);
Root<Student> student = cq.from(Student.class);
cq.select(student);
cq.orderBy(cb.desc(student.get(Student_.birthday)));
Nếu muốn sắp xếp theo nhiều trường, thì tên những trường này được phân cách nhau bởi dấy phẩy
CriteriaQuery<Student> cq = cb.createQuery(Student.class);
Root<Student> student = cq.from(Student.class);
cq.select(student);
cq.orderBy(cb.desc(student.get(Student_.birthday), student.get(Student_.name)));
Grouping Results
Chúng ta cũng có thể nhóm các kết quả trả về theo những trường nhất định
CriteriaQuery<Student> cq = cb.createQuery(Student.class);
Root<Student> student = cq.from(Student.class);
cq.groupBy(student.get(Student_.birthday));
Thực thi Queries
Để chuẩn bị cho query được thực thi thì chúng ta tạo một đối tượng TypedQuery<T> bằng cách truyền CriteriaQuery như parameter vào phương thước EntityManager.createQuery Kết quả trả về sẽ chia làm 2 lọai
Single-Valued Query Results
Chúng ta gọi phương thức TypedQuery<T>.getSingleResult
CriteriaQuery<Student> cq = cb.createQuery(Student.class);
...
TypedQuery<Student> q = em.createQuery(cq);
Student result = q.getSingleResult();
Collection-Valued Query Results
Chúng ta gọi phương thức TypedQuery<T>.getResultList
CriteriaQuery<Student> cq = cb.createQuery(Student.class);
...
TypedQuery<Student> q = em.createQuery(cq);
List<Student> results = q.getResultList();
Tài liệu tham khảo https://www.tutorialspoint.com/jpa/jpa_criteria_api.htm https://www.tutorialspoint.com/jpa/jpa_criteria_api.htm https://docs.oracle.com/javaee/6/tutorial/doc/gjivm.html
All rights reserved