3.3.4 Tùy chỉnh các repository
Hãy tưởng tượng rằng ngoài các thao tác CRUD cơ bản được cung cấp bởi CrudRepository, bạn còn cần truy xuất tất cả các đơn hàng được giao đến một mã ZIP cụ thể. Thật may, điều này có thể được xử lý dễ dàng bằng cách thêm khai báo phương thức sau vào OrderRepository:
List<TacoOrder> findByDeliveryZip(String deliveryZip);Khi tạo phần triển khai repository, Spring Data sẽ kiểm tra từng phương thức trong giao diện repository, phân tích tên phương thức, và cố gắng hiểu mục đích của phương thức trong ngữ cảnh của đối tượng được lưu trữ (trong trường hợp này là TacoOrder). Về bản chất, Spring Data định nghĩa một loại ngôn ngữ miền chuyên biệt thu nhỏ (DSL), nơi mà các chi tiết lưu trữ được thể hiện qua chữ ký phương thức trong repository.
Spring Data biết rằng phương thức này nhằm mục đích tìm kiếm các Order, bởi vì bạn đã tham số hóa CrudRepository với TacoOrder. Tên phương thức, findByDeliveryZip(), cho thấy rõ ràng rằng phương thức này sẽ tìm tất cả các thực thể TacoOrder mà thuộc tính deliveryZip khớp với giá trị được truyền vào làm tham số cho phương thức.
Phương thức findByDeliveryZip() khá đơn giản, nhưng Spring Data còn có thể xử lý các tên phương thức phức tạp hơn. Các phương thức trong repository bao gồm một động từ, một chủ ngữ tùy chọn, từ khóa By, và một mệnh đề điều kiện (predicate). Trong trường hợp findByDeliveryZip(), động từ là find và mệnh đề điều kiện là DeliveryZip; chủ ngữ không được chỉ định rõ và được ngầm hiểu là TacoOrder.
Hãy xem xét một ví dụ khác phức tạp hơn. Giả sử bạn cần truy vấn tất cả các đơn hàng được giao đến một mã ZIP cụ thể trong một khoảng thời gian nhất định. Trong trường hợp đó, phương thức sau, khi được thêm vào OrderRepository, có thể rất hữu ích:
List<TacoOrder> readOrdersByDeliveryZipAndPlacedAtBetween(
String deliveryZip, Date startDate, Date endDate);Hình 3.2 minh họa cách Spring Data phân tích và hiểu phương thức readOrdersByDeliveryZipAndPlacedAtBetween() khi tạo phần triển khai repository. Như bạn thấy, động từ trong readOrdersByDeliveryZipAndPlacedAtBetween() là read. Spring Data cũng hiểu các từ khóa find, read, và get là đồng nghĩa với việc truy xuất một hoặc nhiều thực thể. Ngoài ra, bạn cũng có thể dùng count làm động từ nếu muốn phương thức trả về một số nguyên đại diện cho số lượng thực thể khớp.
Hình 3.2 Spring Data phân tích chữ ký phương thức repository để xác định truy vấn cần thực hiện
Mặc dù phần chủ ngữ của phương thức là tùy chọn, ở đây là Orders. Spring Data sẽ bỏ qua hầu hết các từ trong phần chủ ngữ, vì vậy bạn có thể đặt tên phương thức là readPuppiesBy... và nó vẫn sẽ tìm các thực thể TacoOrder, vì đó là kiểu dữ liệu mà CrudRepository được tham số hóa với.
Phần mệnh đề điều kiện theo sau từ khóa By trong tên phương thức và là phần thú vị nhất của chữ ký phương thức. Trong trường hợp này, mệnh đề điều kiện đề cập đến hai thuộc tính của TacoOrder: deliveryZip và placedAts. Thuộc tính deliveryZip phải bằng với giá trị được truyền vào tham số đầu tiên của phương thức. Từ khóa Between chỉ ra rằng giá trị của placedAt phải nằm trong khoảng giữa hai giá trị được truyền vào hai tham số cuối của phương thức.
Ngoài toán tử Equals ngầm định và toán tử Between, các chữ ký phương thức trong Spring Data còn có thể bao gồm bất kỳ toán tử nào sau đây:
- IsAfter, After, IsGreaterThan, GreaterThan
- IsGreaterThanEqual, GreaterThanEqual
- IsBefore, Before, IsLessThan, LessThan
- IsLessThanEqual, LessThanEqual
- IsBetween, Between
- IsNull, Null
- IsNotNull, NotNull
- IsIn, In
- IsNotIn, NotIn
- IsStartingWith, StartingWith, StartsWith
- IsEndingWith, EndingWith, EndsWith
- IsContaining, Containing, Contains
- IsLike, Like
- IsNotLike, NotLike
- IsTrue, True
- IsFalse, False
- Is, Equals
- IsNot, Not
- IgnoringCase, IgnoresCase
Là các lựa chọn thay thế cho IgnoringCase và IgnoresCase, bạn có thể sử dụng AllIgnoringCase hoặc AllIgnoresCase trên phương thức để bỏ qua phân biệt chữ hoa/thường cho tất cả các so sánh kiểu String. Ví dụ, hãy xem xét phương thức sau:
List<TacoOrder> findByDeliveryToAndDeliveryCityAllIgnoresCase(
String deliveryTo, String deliveryCity);Cuối cùng, bạn cũng có thể đặt OrderBy ở cuối tên phương thức để sắp xếp kết quả theo một cột nhất định. Ví dụ, để sắp xếp theo thuộc tính deliveryTo, hãy sử dụng cách sau:
List<TacoOrder> findByDeliveryCityOrderByDeliveryTo(String city);Mặc dù quy tắc đặt tên phương thức có thể hữu ích đối với các truy vấn tương đối đơn giản, nhưng bạn có thể dễ dàng tưởng tượng rằng tên phương thức sẽ trở nên quá dài và khó quản lý đối với các truy vấn phức tạp hơn. Trong trường hợp đó, hãy thoải mái đặt tên phương thức theo ý bạn và sử dụng annotation @Query để chỉ định rõ truy vấn cần thực hiện khi phương thức được gọi, như ví dụ sau đây:
@Query("select o from TacoOrder o where o.deliveryCity='Seattle'")
List<TacoOrder> readOrdersDeliveredInSeattle();Trong cách sử dụng đơn giản này của @Query, bạn yêu cầu lấy tất cả các đơn hàng được giao ở Seattle. Nhưng bạn cũng có thể sử dụng @Query để thực hiện gần như bất kỳ truy vấn JPA nào mà bạn nghĩ ra, ngay cả khi rất khó (hoặc không thể) đạt được điều đó bằng cách tuân theo quy tắc đặt tên.
Các phương thức truy vấn tùy chỉnh cũng hoạt động với Spring Data JDBC nhưng với những điểm khác biệt quan trọng sau:
- Tất cả các phương thức truy vấn tùy chỉnh đều yêu cầu
@Query. Điều này là do, không giống như JPA, JDBC không có metadata ánh xạ để giúp Spring Data JDBC tự động suy luận truy vấn từ tên phương thức. - Tất cả các truy vấn được chỉ định trong
@Queryphải là truy vấn SQL, không phải truy vấn JPA.
Trong chương tiếp theo, chúng ta sẽ mở rộng việc sử dụng Spring Data để làm việc với các cơ sở dữ liệu phi quan hệ. Khi đó, bạn sẽ thấy rằng các phương thức truy vấn tùy chỉnh hoạt động tương tự, mặc dù ngôn ngữ truy vấn được sử dụng trong @Query sẽ phụ thuộc vào cơ sở dữ liệu bên dưới.
