Lựa chọn Criteria queries khi dùng Hibernate

Đối với java developer thì khi khi cần 1 thành phần để trao đổi dữ liệu giữa ứng dụng và database thì hẳn mọi người đều nghĩ đến ngay hibernate, jpa. Cả 2 đều rất phổ biển mà trong phạm vi bài viết mình xin nói về hibernate và criteria queries

  1. Một vài suy nghĩ về Hibernate và criteria queries
  • Khi sử dụng 2 hibernate chúng ta có một vài sự lựa chọn như native sql, hql, và criteria queries.
  • Sau khi sử dụng cả 3 loại thì nhìn từ criteria queries mình thấy thế này:
    • So với navtive sql thì nối bảng và các trường phụ thuộc vào mô tả lớp entity
    • Thuận tiện trong việc đổ dữ liệu kết hợp với lớp entity.
    • Cú pháp trong sáng rõ ràng, dễ viết, dễ bảo trì và hạn chế các sai sót về cú pháp hơn so với HQL và native SQL.
    • Dễ dàng hơn khi viết các truy vấn có các tham số đầu vào không cố định.
  1. Một vài cách tính năng của criteria queries: Một vài tính năng mà mình cho là mang lại ưu thế của criteria queries

    a. Thêm một điều kiện query

Với cú pháp rất đơn giản và được hỗ trợ bởi IDE thì có thể thêm các điều kiện truy vấn tương ứng với sql như sau: Với 3 câu query riêng biệt:

	// HQL: tr.storeId=:storeId
    criteria.add(Restrictions.eq("tr.storeId", storeId));
    // HQL: tr.r.trafficTime>=:minDate
    criteria.add(Restrictions.ge("tr.trafficTime", minDate));
    // HQL: tr.r.trafficTime<:maxDate
    criteria.add(Restrictions.lt("tr.trafficTime", maxDate));

Kết hợp cả 3 câu query với điều kiện AND:

	// HQL: tr.storeId=:storeId AND tr.r.trafficTime>=:minDate AND tr.r.trafficTime<:maxDate
    criteria.add(Restrictions.eq("tr.storeId", storeId));
    criteria.add(Restrictions.ge("tr.trafficTime", minDate));
    criteria.add(Restrictions.lt("tr.trafficTime", maxDate));

Thì lợi thế của criteria so với cả navtive sql và hql là đơn giản trong cú pháp.

Với criteria thì việc định nghĩa mô tả quan hệ AND giữa các điều kiện là không cần thiết khi quan hệ giữa tất cả đều là AND. Và tất nhiên là nó hoàn toàn có thể mô tả mối quan hệ AND, OR giữa các điều kiện query

    criteria.add(Restrictions.and(...))
    criteria.add(Restrictions.or(...))

Và một số thứ rất hữu ích nữa như

    // hql: tr.storeId=:value1 OR tr.storeId is null
    criteria.add(Restrictions.eqOrIsNull("tr.storeId", value1))
    // hql: tr.storeId!=:value2 OR tr.storeId is not null
    criteria.add(Restrictions.neOrIsNotNull("tr.storeId", value2))

Trong trường hợp nếu như số lượng các điều kiện dùng để query là không cố định thì sử dụng criteria queries là luôn luôn là một là chọn tốt. b. Projections, Group by và các function để tính toán thống kê Chúng ta dùng Projections để group và thực hiện các thông kê cơ bản:

ProjectionList projectionList = Projections.projectionList()
// hql: group by tr.itemType, tr.itemId
.add(Projections.groupProperty("tr.itemType").as("itemType"))
.add(Projections.groupProperty("tr.itemId").as("itemId"))
// hql: sum(tr.viewTraffic) as totalViewTraffic
.add(Projections.sum("tr.viewTraffic").as("viewTraffic"))
criteria.setProjection(projectionList);

Khi dùng native sql hoặc hql thì vẫn phải viết câu query theo trình tự khác với sử dụng criteria Câu thống kê trên vẫn sẽ được viết theo cú pháp của hql như sau:

select tr.itemType, tr.itemId, sum(tr.viewTraffic) as totalViewTraffic
from traffic as tr
group by tr.itemType, tr.itemId

Không phải lúc nào chúng ta cũng muốn trả về là một tập giá trị đổ vào các trường của entity mà có thể là vào một class bean khác. Đổ dữ liệu vào một class bean khác có số lượng và tên trường khác đơn giản hẳn khi dùng Projections.

    ProjectionList projectionList = Projections.projectionList()
				.add(Projections.groupProperty("tr.storeId").as("storeId"))
				.add(Projections.groupProperty("tr.itemId").as("bookId"))
    criteria.setProjection(projectionList);

    // bean để dữ liệu đổ vào là Book
    // Book có 2 trường là storeId, bookId
	criteria.setResultTransformer(Transformers.aliasToBean(Book.class));

Với sự kết hợp này criteria đã có khả năng chọn lựa dữ liệu trả về tương đương hql và native sql ngoài ra còn linh hoạt hơn trong trong việc đổ dữ liểu vào bean.

Liên kết giữa các bảng khi dùng criteria vần phải phụ thuộc nhiều vào mô tả trường và liên kết bảng ở entity khi dùng hql và criteria queries. Với criteria queries thì ta vẫn có 1 sự lựa chọn là kết hợp với native sql như ví dụ sau:

        Projections.projectionList().add(Projections.sqlProjection("(select book.title as item_name from book as b where cast(b.book_id as varchar(10))= {alias}.item_id) as itemName", new String[] { "itemName" }, new Type[] { TextType.INSTANCE }));

Ở ví dụ sử dụng sqlProjection để dùng native lấy ra thông tin ở bảng khác được liên kết bằng đoạn mô tả

where cast(b.book_id as varchar(10))= {alias}.item_id

Với cách dùng này ta khắc phục được hạn chế của criteria và hql là thực hiện liên kết với một bảng dữ liệu khác nhưng quan hệ không được mô tả trong entity Trong thực tế chúng ta thường không cần thiết phải lấy tất cả dữ liệu trong bảng hoặc muốn đổ vào một bean khác như DTO và tính toán trước một số thông tin trước khi lấy đổ vào dữ liệu rồi mới đổ dữ liệu vào đối tượng. Projections hỗ trợ tốt cho những mục đích trên.

  1. Kết

Mặc dù criteria queries có những hạn chế như mình đã nhắc đến ở đầu. Nhưng như các chia sẽ của mình thì gần như đều có thể khắc giải quyết. Ngoài ra criteria còn các ưu thế lớn khi tạo ra các truy vấn linh hoạt và rõ ràng thì với thời gian phát triển dài và quy mô nhóm phát triển lớn. Hy vọng những chia sẽ hữu ích