24 April, 2012

Ищем с помощью Spring Data JPA

Рассмотрим подробнее одну из наиболее полезных вещей в Spring Data JPA - генерация JPQL-запросов на основе имени метода. Spring Data JPA умеет автоматически генерировать запросы используя для подсказки название метода. Например, метод User.findByLoginAndPassword сгенерирует примерно следующий код:
    FROM User u where u.login = :login and password = :password
Вообще Spring Data JPA пытается быть умным, поэтому реализация findBy{...} методов ищется следующим образом:
  1. Сначала смотрится аннотация @Query на объявлении метода, если она есть, то используется.
  2. Затем смотрится аннотация @NamedQuery с именем вида Entity.findMethodName, для вышеприведённого случая это будет User.findByLoginAndPassword.
  3. Если ничего не нашли. то по сигнатуре метода генерируется запрос.
У @Query следующие плюсы:
  1. Позволяет не засорять объявление доменной сущности.
  2. Сильно помогает, если у нас в запросе есть неявные джойны, потому что для таких запросов Spring Data JPA не умеет корректно генерировать запрос SELECT COUNT(*), который нужен в тех случаях, когда метод должен вернуть Page.
Очевидно, что при использовании запросов нам необходимо каким-то образом указывать параметры для запросов. Для этого есть аннотация @Param:
    @Query("select u from User u where u.login = :login and u.password = :password")
    Page<User> findByLoginAndPassword(@Param("login") String login, @Param("password") String password);
Кроме того, Spring Data JPA поддерживает отличную концепцию спецификаций. Спецификации позволяют делать составлять сложные запросы из набора простых. Для поддержки спецификаций необходимо объявить метод в репозитории:
    Page<User> findAll(Specification<User> spec, Pageable pageable);
Спецификация по сути является фильтром и позволяет комбинирование фильтров, что даёт мощный инструмент для построения запросов. Пример использования спецификаций: Объявляем наши спецификации:
        public static Specification<User> firstNameOrLastNameOrLoginLike(final String search) {
            return new Specification<User>() {
                @Override
                public Predicate toPredicate(Root<User> root, CriteriaQuery<?> query, CriteriaBuilder builder) {

                    Predicate loginPredicate = builder.like(root.get(User_.login), search);
                    Predicate firstnamePredicate = builder.like(root.get(User_.firstname), search);
                    Predicate lastnamePredicate = builder.like(root.get(User_.lastname), search);

                    return builder.or(loginPredicate, firstnamePredicate, lastnamePredicate);
                }
            };
        }

        public static Specification<User> hasRole(final Role role) {
            return new Specification<User>() {
                @Override
                public Predicate toPredicate(Root<User> root, CriteriaQuery<?> query, CriteriaBuilder builder) {
                    return builder.equal(root.get(User_.role), role);
                }
            };
        }
И в нашем сервисе комбинируем их:

        public Page<User> searchUser(Role role, String search, Pageable pageable) {
            Specifications<User> mainSpec = where(hasRole(role));

            // уточняем запрос, если была передана строка для поиска
            if (StringUtils.isNotBlank(search)) {
                mainSpec = mainSpec.and(firstNameOrLastNameOrLoginLike(search));
            } 

            return userRepository.findAll(mainSpec, pageable);
        }

No comments:

Post a Comment