Dados da Primavera JPA @Query

Dados da Primavera JPA @Query

1. Visão geral

O Spring Data fornece várias maneiras de definir uma consulta que podemos executar. Um deles é a anotação@Query.

Neste tutorial, demonstraremoshow to use the @Query annotation in Spring Data JPA to execute both JPQL and native SQL queries.

Além disso, mostraremos como construir uma consulta dinâmica quando a anotação@Query não for suficiente.

Leitura adicional:

Métodos de consulta derivados em repositórios JPA de dados de primavera

Explore o mecanismo de derivação de consulta no Spring Data JPA.

Read more

Anotação JPA de Dados de Primavera @Modifying

Crie consultas DML e DDL no Spring Data JPA combinando as anotações @Query e @Modifying

Read more

2. Selecionar consulta

Para definir o SQL a ser executado para um método de repositório Spring Data, podemosannotate the method with the @Query annotation  — its value attribute contains the JPQL or SQL to execute.

A anotação@Query tem precedência sobre as consultas nomeadas, que são anotadas com@NamedQuery ou definidas em um arquivoorm.xml.

É uma boa abordagem colocar uma definição de consulta logo acima do método dentro do repositório, em vez de dentro de nosso modelo de domínio como consultas nomeadas. O repositório é responsável pela persistência, por isso é um lugar melhor para armazenar essas definições.

2.1. JPQL

Por padrão, a definição de consulta usa JPQL.

Vejamos um método de repositório simples que retorna entidadesUser ativas do banco de dados:

@Query("SELECT u FROM User u WHERE u.status = 1")
Collection findAllActiveUsers();

2.2. Nativo

Também podemos usar SQL nativo para definir nossa consulta. Tudo o que precisamos fazer é definir o valor do atributonativeQuery paratruee definir a consulta SQL nativa no atributovalue da anotação:

@Query(
  value = "SELECT * FROM USERS u WHERE u.status = 1",
  nativeQuery = true)
Collection findAllActiveUsersNative();

3. Definir pedido em uma consulta

Podemos passar um parâmetro adicional do tipoSort para uma declaração do método Spring Data que possui a anotação@Query. Ele será traduzido na cláusulaORDER BY que é passada para o banco de dados.

3.1. Classificação para métodos JPA fornecidos e derivados

Para os métodos, obtemos out-of-the-box comofindAll(Sort) ou aqueles que são gerados pela análise de assinaturas de método,we can only use object properties to define our sort:

userRepository.findAll(new Sort(Sort.Direction.ASC, "name"));

Agora imagine que queremos classificar pelo comprimento de uma propriedade de nome:

userRepository.findAll(new Sort("LENGTH(name)"));

Quando executarmos o código acima, receberemos uma exceção:

org.springframework.data.mapping.PropertyReferenceException: No property lENGTH(name) found for type User!

3.2. JPQL

When we use JPQL for a query definition, then Spring Data can handle sorting without any problem - tudo o que precisamos fazer é adicionar um parâmetro de método do tipoSort:

@Query(value = "SELECT u FROM User u")
List findAllUsers(Sort sort);

Podemos chamar este método e passar um parâmetroSort, que ordenará o resultado pela propriedadename do objetoUser:

userRepository.findAllUsers(new Sort("name"));

E porque usamos a anotação@Query,we can use the same method to get the sorted list of Users by the length of their names:

userRepository.findAllUsers(JpaSort.unsafe("LENGTH(name)"));

É crucial usarmosJpaSort.unsafe() para criar uma instância de objetoSort.

Quando usamos:

new Sort("LENGTH(name)");

então receberemos exatamente a mesma exceção que vimos acima para o métodofindAll().

Quando Spring Data descobre o pedidoSort inseguro para um método que usa a anotação@Query, ele apenas anexa a cláusula de classificação à consulta - ele ignora a verificação se a propriedade a ser classificada pertence ao modelo de domínio .

3.3. Nativo

Quando a anotação@Query usa SQL nativo, então não é possível definir umSort.

Se o fizermos, receberemos uma exceção:

org.springframework.data.jpa.repository.query.InvalidJpaQueryMethodException: Cannot use native queries with dynamic sorting and/or pagination

Como diz a exceção, a classificação não é compatível com consultas nativas. A mensagem de erro nos dá uma dica de que a paginação também causará uma exceção.

No entanto, há uma solução alternativa que permite a paginação e abordaremos na próxima seção.

4. Paginação

A paginação nos permite retornar apenas um subconjunto de um resultado inteiro emPage. Isso é útil, por exemplo, ao navegar por várias páginas de dados em uma página da web.

Outra vantagem da paginação é que a quantidade de dados enviados do servidor para o cliente é minimizada. Ao enviar dados menores, geralmente podemos ver uma melhoria no desempenho.

4.1. JPQL

O uso da paginação na definição de consulta JPQL é simples:

@Query(value = "SELECT u FROM User u ORDER BY id")
Page findAllUsersWithPagination(Pageable pageable);

Podemos passar um parâmetroPageRequest para obter uma página de dados. A paginação também é suportada para consultas nativas, mas requer um pouco de trabalho adicional.

4.2. Nativo

Podemosenable pagination for native queries by declaring an additional attribute countQuery - isso define o SQL a ser executado para contar o número de linhas em todo o resultado:

@Query(
  value = "SELECT * FROM Users ORDER BY id",
  countQuery = "SELECT count(*) FROM Users",
  nativeQuery = true)
Page findAllUsersWithPagination(Pageable pageable);

4.3. Versões JPA do Spring Data anteriores a 2.0.4

A solução acima para consultas nativas funciona bem para o Spring Data JPA versão 2.0.4 e posterior.

Antes dessa versão, quando tentamos executar tal consulta, receberemos uma exceção - a mesma que descrevemos na seção anterior sobre classificação.

Podemos superar isso adicionando um parâmetro adicional para paginação em nossa consulta:

@Query(
  value = "SELECT * FROM Users ORDER BY id \n-- #pageable\n",
  countQuery = "SELECT count(*) FROM Users",
  nativeQuery = true)
Page findAllUsersWithPagination(Pageable pageable);

”Como espaço reservado para o parâmetro de paginação. Isso informa ao Spring Data JPA como analisar a consulta e injetar o parâmetro paginável. Esta solução funciona para o banco de dadosH2.

Abordamos como criar consultas selecionadas simples via JPQL e SQL nativo. A seguir, mostraremos como definir parâmetros adicionais.

5. Parâmetros de consulta indexados

Existem duas maneiras possíveis de passar parâmetros de método para nossa consulta. Nesta seção, abordaremos os parâmetros indexados.

5.1. JPQL

Para parâmetros indexados em JPQL, Spring Data irápass method parameters to the query in the same order they appear in the method declaration:

@Query("SELECT u FROM User u WHERE u.status = ?1")
User findUserByStatus(Integer status);

@Query("SELECT u FROM User u WHERE u.status = ?1 and u.name = ?2")
User findUserByStatusAndName(Integer status, String name);

Para as consultas acima, o parâmetro de métodostatus será atribuído ao parâmetro de consulta com índice1,e o parâmetro de métodoname será atribuído ao parâmetro de consulta com índice2 .

5.2. Nativo

Os parâmetros indexados para as consultas nativas funcionam exatamente da mesma maneira que no JPQL:

@Query(
  value = "SELECT * FROM Users u WHERE u.status = ?1",
  nativeQuery = true)
User findUserByStatusNative(Integer status);

Na próxima seção, mostraremos uma abordagem diferente - passar parâmetros por meio do nome.

6. Parâmetros nomeados

Nós também podemospass method parameters to the query using named parameters.. Nós os definimos usando a anotação@Param dentro de nossa declaração de método de repositório.

Cada parâmetro anotado com@Param deve ter uma string de valor correspondente ao nome do parâmetro de consulta JPQL ou SQL correspondente. Uma consulta com parâmetros nomeados é mais fácil de ler e menos propensa a erros, caso seja necessário refatorar a consulta.

6.1. JPQL

Conforme mencionado acima, usamos a anotação@Param na declaração do método para combinar os parâmetros definidos pelo nome em JPQL com os parâmetros da declaração do método:

@Query("SELECT u FROM User u WHERE u.status = :status and u.name = :name")
User findUserByStatusAndNameNamedParams(
  @Param("status") Integer status,
  @Param("name") String name);

Observe que no exemplo acima, definimos nossos parâmetros de consulta e método SQL para ter os mesmos nomes, mas não é obrigatório, desde que as strings de valor sejam as mesmas:

@Query("SELECT u FROM User u WHERE u.status = :status and u.name = :name")
User findUserByUserStatusAndUserName(@Param("status") Integer userStatus,
  @Param("name") String userName);

6.2. Nativo

Para a definição de consulta nativa, não há diferença como passamos um parâmetro por meio do nome para a consulta em comparação com JPQL - usamos a anotação@Param:

@Query(value = "SELECT * FROM Users u WHERE u.status = :status and u.name = :name",
  nativeQuery = true)
User findUserByStatusAndNameNamedParamsNative(
  @Param("status") Integer status, @Param("name") String name);

7. Parâmetro de coleção

Vamos considerar o caso em que a cláusulawhere de nossa consulta JPQL ou SQL contém a palavra-chaveIN (ouNOT IN):

SELECT u FROM User u WHERE u.name IN :names

Nesse caso, podemos definir um método de consulta que levaCollection  como parâmetro:

@Query(value = "SELECT u FROM User u WHERE u.name IN :names")
List findUserByNameList(@Param("names") Collection names);

Como o parâmetro é umCollection, ele pode ser usado comList, HashSet, etc.

A seguir, mostraremos como modificar dados com a anotação @Modifying.

8. Atualizar consultas com@Modifying

Podemosuse the @Query annotation to modify the state of the database by also adding the @Modifying annotation para o método de repositório.

8.1. JPQL

O método de repositório que modifica os dados tem duas diferenças em comparação com a consultaselect - ele tem a anotação@Modifying e, é claro, a consulta JPQL usaupdate em vez deselect:

@Modifying
@Query("update User u set u.status = :status where u.name = :name")
int updateUserSetStatusForName(@Param("status") Integer status,
  @Param("name") String name);

O valor retornado define quantas linhas a execução da consulta atualizou. Os parâmetros indexados e nomeados podem ser usados ​​nas consultas de atualização.

8.2. Nativo

Podemos modificar o estado do banco de dados também com uma consulta nativa - só precisamos adicionar a anotação@Modifying:

@Modifying
@Query(value = "update Users u set u.status = ? where u.name = ?",
  nativeQuery = true)
int updateUserSetStatusForNameNative(Integer status, String name);

8.3. Inserções

Para realizar uma operação de inserção, temos que aplicar@Modifyinge usar uma consulta nativa desdeINSERT is not a part of the JPA interface:

@Modifying
@Query(
  value =
    "insert into Users (name, age, email, status) values (:name, :age, :email, :status)",
  nativeQuery = true)
void insertUser(@Param("name") String name, @Param("age") Integer age,
  @Param("status") Integer status, @Param("email") String email);

9. Consulta Dinâmica

Muitas vezes, encontraremos a necessidade de construir instruções SQL com base em condições ou conjuntos de dados cujos valores são conhecidos apenas em tempo de execução. E, nesses casos, não podemos apenas usar uma consulta estática.

9.1. Exemplo de uma consulta dinâmica

Por exemplo, vamos imaginar uma situação em que precisamos selecionar todos os usuários cujo e-mail éLIKE um de um conjunto definido em tempo de execução -email1,email2,…,emailn:

SELECT u FROM User u WHERE u.email LIKE '%email1%'
    or  u.email LIKE '%email2%'
    ...
    or  u.email LIKE '%emailn%'

Uma vez que o conjunto é construído dinamicamente, não podemos saber em tempo de compilação quantas cláusulasLIKE adicionar.

Neste caso,we can’t just use the @Query annotation since we can’t provide a static SQL statement.

Em vez disso, ao implementar um repositório composto personalizado, podemos estender a funcionalidade de baseJpaRepository e fornecer nossa própria lógica para construir uma consulta dinâmica. Vamos dar uma olhada em como fazer isso.

9.2. Repositórios personalizados e a API de critérios JPA

Luckily for us, Spring provides a way for extending the base repository through the use of custom fragment interfaces. Podemos então vinculá-los para criar umcomposite repository.

Começaremos criando uma interface de fragmento personalizada:

public interface UserRepositoryCustom {
    List findUserByEmails(Set emails);
}

E então, vamos implementá-lo:

public class UserRepositoryCustomImpl implements UserRepositoryCustom {

    @PersistenceContext
    private EntityManager entityManager;

    @Override
    public List findUserByEmails(Set emails) {
        CriteriaBuilder cb = entityManager.getCriteriaBuilder();
        CriteriaQuery query = cb.createQuery(User.class);
        Root user = query.from(User.class);

        Path emailPath = user.get("email");

        List predicates = new ArrayList<>();
        for (String email : emails) {
            predicates.add(cb.like(emailPath, email));
        }
        query.select(user)
            .where(cb.or(predicates.toArray(new Predicate[predicates.size()])));

        return entityManager.createQuery(query)
            .getResultList();
    }
}

Conforme mostrado acima, aproveitamosJPA Criteria API para construir nossa consulta dinâmica.

Also, we need to make sure to include the Impl postfix in the class name. Spring pesquisará a implementaçãoUserRepositoryCustom comoUserRepositoryCustomImpl. Como os fragmentos não são repositórios por si mesmos, o Spring conta com esse mecanismo para encontrar a implementação do fragmento.

9.3. Estendendo o Repositório Existente

Observe que todos os métodos de consulta da seção 2 - seção 7 estão emUserRepository.. Portanto, agora iremos integrar nosso fragmento estendendo a nova interface emUserRepository:

public interface UserRepository extends JpaRepository, UserRepositoryCustom {
    //  query methods from section 2 - section 7
}

9.4. Usando o Repositório

E, finalmente, podemos chamar nosso método de consulta dinâmica:

Set emails = new HashSet<>();
// filling the set with any number of items

userRepository.findUserByEmails(emails);

Criamos com sucesso um repositório composto e chamamos nosso método personalizado.

10. Conclusão

Neste artigo, cobrimos várias maneiras de definir consultas nos métodos de repositório Spring Data JPA usando a anotação@Query.

Além disso, aprendemos como implementar um repositório personalizado e criar uma consulta dinâmica.

Como sempre, os exemplos de código completos usados ​​neste tutorial estão disponíveisover on Github.