Ngôn ngữ truy vấn REST với Thông số kỹ thuật JPA Dữ liệu mùa xuân

Bài viết này là một phần của loạt bài: • Ngôn ngữ truy vấn REST với Spring và JPA Criteria

• Ngôn ngữ truy vấn REST với Thông số kỹ thuật JPA dữ liệu mùa xuân (bài viết hiện tại) • Ngôn ngữ truy vấn REST với dữ liệu mùa xuân JPA và Querydsl

• Ngôn ngữ truy vấn REST - Hoạt động tìm kiếm nâng cao

• Ngôn ngữ truy vấn REST - Triển khai HOẶC Hoạt động

• Ngôn ngữ truy vấn REST với RSQL

• Ngôn ngữ truy vấn REST với Hỗ trợ Web Querydsl

1. Khái quát chung

Trong hướng dẫn này - chúng tôi sẽ xây dựng API REST Tìm kiếm / Lọc bằng cách sử dụng Spring Data JPA và Thông số kỹ thuật.

Chúng tôi đã bắt đầu xem xét ngôn ngữ truy vấn trong bài viết đầu tiên của loạt bài này - với giải pháp dựa trên Tiêu chí JPA.

Vậy - tại sao lại là một ngôn ngữ truy vấn? Bởi vì - đối với bất kỳ API nào đủ phức tạp - việc tìm kiếm / lọc tài nguyên của bạn theo các trường rất đơn giản là không đủ. Ngôn ngữ truy vấn linh hoạt hơn và cho phép bạn lọc xuống chính xác các tài nguyên bạn cần.

2. Thực thể người dùng

Đầu tiên - hãy bắt đầu với một thực thể Người dùng đơn giản cho API Tìm kiếm của chúng tôi:

@Entity public class User { @Id @GeneratedValue(strategy = GenerationType.AUTO) private Long id; private String firstName; private String lastName; private String email; private int age; // standard getters and setters }

3. Bộ lọc sử dụng đặc điểm kỹ thuật

Bây giờ - hãy đi thẳng vào phần thú vị nhất của vấn đề - truy vấn với Thông số JPA của Spring Data tùy chỉnh .

Chúng tôi sẽ tạo một UserSpecification triển khai giao diện Đặc tả và chúng tôi sẽ vượt qua ràng buộc của riêng mình để xây dựng truy vấn thực tế :

public class UserSpecification implements Specification { private SearchCriteria criteria; @Override public Predicate toPredicate (Root root, CriteriaQuery query, CriteriaBuilder builder) { if (criteria.getOperation().equalsIgnoreCase(">")) { return builder.greaterThanOrEqualTo( root. get(criteria.getKey()), criteria.getValue().toString()); } else if (criteria.getOperation().equalsIgnoreCase("<")) { return builder.lessThanOrEqualTo( root. get(criteria.getKey()), criteria.getValue().toString()); } else if (criteria.getOperation().equalsIgnoreCase(":")) { if (root.get(criteria.getKey()).getJavaType() == String.class) { return builder.like( root.get(criteria.getKey()), "%" + criteria.getValue() + "%"); } else { return builder.equal(root.get(criteria.getKey()), criteria.getValue()); } } return null; } }

Như chúng ta có thể thấy - chúng tôi tạo Thông số kỹ thuật dựa trên một số ràng buộc đơn giản mà chúng tôi đại diện trong lớp “ Tiêu chí tìm kiếm ” sau:

public class SearchCriteria { private String key; private String operation; private Object value; }

Việc triển khai SearchCriteria giữ một đại diện cơ bản của một ràng buộc - và dựa trên ràng buộc này mà chúng ta sẽ xây dựng truy vấn:

  • key : tên trường - ví dụ: firstName , age ,… v.v.
  • phép toán : phép toán - ví dụ: bằng nhau, nhỏ hơn,… v.v.
  • value : giá trị trường - ví dụ: john, 25,… v.v.

Tất nhiên, việc thực hiện rất đơn giản và có thể được cải thiện; Tuy nhiên, nó là một cơ sở vững chắc cho các hoạt động mạnh mẽ và linh hoạt mà chúng ta cần.

4. Kho lưu trữ người dùng

Tiếp theo - hãy xem qua UserRepository ; chúng tôi chỉ đơn giản là mở rộng JpaSpecificationExecutor để nhận các API đặc tả mới:

public interface UserRepository extends JpaRepository, JpaSpecificationExecutor {}

5. Kiểm tra các Truy vấn Tìm kiếm

Bây giờ - hãy thử nghiệm API tìm kiếm mới.

Đầu tiên, hãy tạo một vài người dùng để họ sẵn sàng khi chạy thử nghiệm:

@RunWith(SpringJUnit4ClassRunner.class) @ContextConfiguration(classes = { PersistenceJPAConfig.class }) @Transactional @TransactionConfiguration public class JPASpecificationsTest { @Autowired private UserRepository repository; private User userJohn; private User userTom; @Before public void init() { userJohn = new User(); userJohn.setFirstName("John"); userJohn.setLastName("Doe"); userJohn.setEmail("[email protected]"); userJohn.setAge(22); repository.save(userJohn); userTom = new User(); userTom.setFirstName("Tom"); userTom.setLastName("Doe"); userTom.setEmail("[email protected]"); userTom.setAge(26); repository.save(userTom); } }

Tiếp theo, hãy xem cách tìm người dùng có họ đã cho :

@Test public void givenLast_whenGettingListOfUsers_thenCorrect() { UserSpecification spec = new UserSpecification(new SearchCriteria("lastName", ":", "doe")); List results = repository.findAll(spec); assertThat(userJohn, isIn(results)); assertThat(userTom, isIn(results)); }

Bây giờ, hãy xem cách tìm một người dùng có cả họ và tên :

@Test public void givenFirstAndLastName_whenGettingListOfUsers_thenCorrect() { UserSpecification spec1 = new UserSpecification(new SearchCriteria("firstName", ":", "john")); UserSpecification spec2 = new UserSpecification(new SearchCriteria("lastName", ":", "doe")); List results = repository.findAll(Specification.where(spec1).and(spec2)); assertThat(userJohn, isIn(results)); assertThat(userTom, not(isIn(results))); }

Lưu ý: Chúng tôi đã sử dụng “ where ” và “ and ” để kết hợp các Thông số kỹ thuật .

Tiếp theo, hãy xem cách tìm người dùng có cả họ và tuổi tối thiểu :

@Test public void givenLastAndAge_whenGettingListOfUsers_thenCorrect() { UserSpecification spec1 = new UserSpecification(new SearchCriteria("age", ">", "25")); UserSpecification spec2 = new UserSpecification(new SearchCriteria("lastName", ":", "doe")); List results = repository.findAll(Specification.where(spec1).and(spec2)); assertThat(userTom, isIn(results)); assertThat(userJohn, not(isIn(results))); }

Bây giờ, chúng ta hãy xem làm thế nào để tìm kiếm các tài khoảnkhông thực sự tồn tại :

@Test public void givenWrongFirstAndLast_whenGettingListOfUsers_thenCorrect() { UserSpecification spec1 = new UserSpecification(new SearchCriteria("firstName", ":", "Adam")); UserSpecification spec2 = new UserSpecification(new SearchCriteria("lastName", ":", "Fox")); List results = repository.findAll(Specification.where(spec1).and(spec2)); assertThat(userJohn, not(isIn(results))); assertThat(userTom, not(isIn(results))); }

Cuối cùng - hãy xem cách tìm Người dùng chỉ được cung cấp một phần của tên :

@Test public void givenPartialFirst_whenGettingListOfUsers_thenCorrect() { UserSpecification spec = new UserSpecification(new SearchCriteria("firstName", ":", "jo")); List results = repository.findAll(spec); assertThat(userJohn, isIn(results)); assertThat(userTom, not(isIn(results))); }

6. Kết hợp các thông số kỹ thuật

Tiếp theo - hãy xem kết hợp các Đặc điểm kỹ thuật tùy chỉnh của chúng tôi để sử dụng nhiều ràng buộc và lọc theo nhiều tiêu chí.

Chúng tôi sẽ triển khai một trình tạo - UserSpecificationBuilder - để kết hợp các Thông số kỹ thuật một cách dễ dàng và trôi chảy :

public class UserSpecificationsBuilder { private final List params; public UserSpecificationsBuilder() { params = new ArrayList(); } public UserSpecificationsBuilder with(String key, String operation, Object value) { params.add(new SearchCriteria(key, operation, value)); return this; } public Specification build() { if (params.size() == 0) { return null; } List specs = params.stream() .map(UserSpecification::new) .collect(Collectors.toList()); Specification result = specs.get(0); for (int i = 1; i < params.size(); i++) { result = params.get(i) .isOrPredicate() ? Specification.where(result) .or(specs.get(i)) : Specification.where(result) .and(specs.get(i)); } return result; } }

7. UserController

Cuối cùng - hãy sử dụng chức năng lọc / tìm kiếm liên tục mới này và thiết lập API REST - bằng cách tạo UserController với thao tác tìm kiếm đơn giản :

@Controller public class UserController { @Autowired private UserRepository repo; @RequestMapping(method = RequestMethod.GET, value = "/users") @ResponseBody public List search(@RequestParam(value = "search") String search) { UserSpecificationsBuilder builder = new UserSpecificationsBuilder(); Pattern pattern = Pattern.compile("(\\w+?)(:|)(\\w+?),"); Matcher matcher = pattern.matcher(search + ","); while (matcher.find()) { builder.with(matcher.group(1), matcher.group(2), matcher.group(3)); } Specification spec = builder.build(); return repo.findAll(spec); } }

Lưu ý rằng để hỗ trợ các hệ thống không phải tiếng Anh khác, đối tượng Mẫu có thể được thay đổi như:

Pattern pattern = Pattern.compile("(\\w+?)(:|)(\\w+?),", Pattern.UNICODE_CHARACTER_CLASS);

Đây là ví dụ về URL thử nghiệm để kiểm tra API:

//localhost:8080/users?search=lastName:doe,age>25

Và phản hồi:

[{ "id":2, "firstName":"tom", "lastName":"doe", "email":"[email protected]", "age":26 }]

Vì các tìm kiếm được phân tách bằng dấu “,” trong ví dụ Mẫu của chúng tôi , các cụm từ tìm kiếm không được chứa ký tự này. Mẫu cũng không khớp với khoảng trắng.

If we want to search for values containing commas, then we can consider using a different separator such as “;”.

Another option would be to change the pattern to search for values between quotes, then strip these from the search term:

Pattern pattern = Pattern.compile("(\\w+?)(:|)(\"([^\"]+)\")");

8. Conclusion

This tutorial covered a simple implementation that can be the base of a powerful REST query language. We've made good use of Spring Data Specifications to make sure we keep the API away from the domain and have the option to handle many other types of operations.

The full implementation of this article can be found in the GitHub project – this is a Maven-based project, so it should be easy to import and run as it is.

Tiếp theo » Ngôn ngữ truy vấn REST với Dữ liệu mùa xuân JPA và Querydsl « Ngôn ngữ truy vấn REST trước với Tiêu chí mùa xuân và JPA