Mục lục
Trong bài viết này chúng ta sẽ cùng nhau tìm hiểu làm thế nào để rút trích dữ liệu từ các cột của entity trong JPA và Hibernate.
Entity example
@Entity public class Product { @Id private long id; private String name; private String description; private String category; private BigDecimal unitPrice; // setters and getters }
Để thuận tiện cho việc kiểm thử, chúng ta sẽ thêm 3 Product vào database với loạt câu Insert sau:
INSERT INTO `projections`.`Product` (`id`, `category`, `description`, `name`, `unitPrice`) VALUES ('1', 'cate1', 'description 1', 'product 1', '10'); INSERT INTO `projections`.`Product` (`id`, `category`, `description`, `name`, `unitPrice`) VALUES ('2', 'cate 2', 'description 2', 'product 2', '50'); INSERT INTO `projections`.`Product` (`id`, `category`, `description`, `name`, `unitPrice`) VALUES ('3', 'cate 3', 'description2', 'product 3', '20');
JPA Projections
Thông thường, JPQL query thường sẽ lấy tất cả các cột trong entity class, khi câu truy vấn được thực hiện kết quả trả về sẽ được ánh xạ vào entity object.
Tuy nhiên, đôi lúc chúng ta chỉ cần lấy một số thuộc tính của entity class thay vì lấy hết, cũng có thể chúng ta muốn lấy kết quả từ các aggregate function như group, max, min. Điều này chúng ta hoàn toàn có thể làm được với JPA Projections.
Single-Column Projections
Giả sử chúng ta muốn lấy danh sách tên của tất cả các product trong database. Trong JPQL chúng ta có thể làm như sau:
Query query = entityManager.createQuery("select name from Product"); List<Object> resultList = query.getResultList(); System.out.println(resultList);
Hoặc cách tương tự với CriteriaBuilder
CriteriaBuilder builder = entityManager.getCriteriaBuilder(); CriteriaQuery<String> query = builder.createQuery(String.class); Root<Product> product = query.from(Product.class); query.select(product.<String>get("name")); List<String> resultList = entityManager.createQuery(query).getResultList(); System.out.println(resultList);
Output
[product 1, product 2, product 3]
Multi-Column Projections
Để lấy 2 hay nhiều thuộc tính trong JPQL, chúng ta chỉ cần chỉ định rõ chúng trong mệnh đề select.
Query query = entityManager.createQuery("select id, name, unitPrice from Product"); List<Object[]> resultList = query.getResultList(); for(Object[] item : resultList) { System.out.println(item[0]); System.out.println(item[1]); System.out.println(item[2]); }
Đối với CriteriaBuilder thì có một chút khác biệt khi chúng ta phải sử dụng multiselect() thay vì select() như khi lấy một cột.
CriteriaBuilder builder = entityManager.getCriteriaBuilder(); CriteriaQuery<Object[]> query = builder.createQuery(Object[].class); Root<Product> product = query.from(Product.class); query.multiselect(product.get("id"), product.get("name"), product.get("unitPrice")); List<Object[]> resultList = entityManager.createQuery(query).getResultList(); for (Object[] item : resultList) { System.out.println(item[0]); System.out.println(item[1]); System.out.println(item[2]); }
Output
1 product 1 10.00 2 product 2 50.00 3 product 3 20.00
Projecting Aggregate Functions
Cũng là một trong những nhu cầu thường xuyên sử dụng, đôi lúc chúng ta muốn nhóm dữ liệu và sử dụng các aggregate function như count, average.
Giả sử mình muốn nhóm dữ liệu theo category và đến số lượng với JPQL như sau
Query query = entityManager.createQuery("select p.category, count(p) from Product p group by p.category"); List<Object[]> resultList = query.getResultList(); for(Object[] item : resultList) { System.out.println(item[0] + " " + item[1]); }
Hoặc với CriteriaBuilder
CriteriaBuilder builder = entityManager.getCriteriaBuilder(); CriteriaQuery<Object[]> query = builder.createQuery(Object[].class); Root<Product> product = query.from(Product.class); query.multiselect(product.get("category"), builder.count(product)); query.groupBy(product.get("category")); List<Object[]> resultList = entityManager.createQuery(query).getResultList(); for (Object[] item : resultList) { System.out.println(item[0] + " " + item[1]); }
cate1 1 cate 2 1 cate 3 1
Ngoài count() method CriteriaBuilder còn hỗ trợ cácaggregate functions khác:
- avg – Tính giá trị trung bình của các cột trong nhóm.
- max – Lấy giá trị lớn nhất trong nhóm
- min – Lấy giá trị nhỏ nhất.
- least – Tìm phần tử cuối cùng được sắp xếp theo tứ thự.
- sum – Tính tổng các cột trong nhóm.
Hibernate Projections
Không giống JPA, Hibernate cung cơ chế rút trích dữ liệu các cột từ entity với org.hibernate.criterion.Projection.
Single-Column Projections
Tương tự, chúng ta sẽ bắt đầu với trường hợp đơn giản nhất, single column.
Criteria criteria = session.createCriteria(Product.class); criteria = criteria.setProjection(Projections.property("name"));
Chúng ta đã sử dụng Criteria.setProjection() method để chỉ định các thuộc tính muốn lấy khi thực hiện truy vấn. Projections.property() hoạt động tương tự với Root.get() mà chúng ta đã được tìm hiểu ở phần trên.
Multi-Column Projections
Để lấy dữ liệu từ cột, chúng ta phải tạo ProjectionList chỉ định tất cả các thuộc tính chúng ta muốn.
Criteria criteria = session.createCriteria(Product.class); criteria = criteria.setProjection( Projections.projectionList() .add(Projections.id()) .add(Projections.property("name")));
Projecting Aggregate Functions
Giống với cách sử dụng CriteriaBuilder, Projections cũng cung cấp các method hỗ trợ các aggregate functions như count, sum, min, max etc.
Criteria criteria = session.createCriteria(Product.class); criteria = criteria.setProjection( Projections.projectionList() .add(Projections.groupProperty("category")) .add(Projections.rowCount()));
Tóm lược
Như vậy là chúng ta đã tìm hiểu được cách rút trích dữ liệu từ các cột của entity thay vì phải lấy hết chúng trong khi không có nhu cầu sử dụng đến. Mặc dù mình hướng dẫn cả 2 cách với JPA và Hibernate thế nhưng chúng ta nên sử dụng JPA để định nghĩa, sau này sẽ giúp chúng ta luân chuyển dễ dàng giữa các JPA Provider.
Sau cùng, các bạn có thể tham khảo mã nguồn được mình công khai trên github.
Nếu vẫn chưa biết cách cấu hình một project JPA – Hibermate thì bạn có thể tham khảo bài viết sau để thực hành.
Nguồn tham khảo