Uso dos Parâmetros de Consulta JPA
1. Introdução
Construir consultas usando JPA não é difícil; no entanto, às vezes esquecemos coisas simples que fazem uma enorme diferença.
Uma dessas coisas são os parâmetros de consulta JPA, e é sobre isso que vamos falar.
2. O que são parâmetros de consulta?
Vamos começar explicando o que são parâmetros de consulta.
Os parâmetros de consulta são uma maneira de criar e executar consultas parametrizadas. Então, em vez de:
SELECT * FROM employees e WHERE e.emp_number = '123';
Faríamos:
SELECT * FROM employees e WHERE e.emp_number = ?;
Usando uma instrução preparada JDBC, precisamos definir o parâmetro antes de executar a consulta:
pStatement.setString(1, 123);
3. Por que devemos usar parâmetros de consulta?
Em vez de usar parâmetros de consulta, poderíamos ter escolhido usar literais, porém, não é a maneira recomendada de fazer isso, como veremos agora.
Vamos reescrever a consulta anterior para obter funcionários poremp_number usando a API JPA, mas em vez de usar um parâmetro, usaremos um literal para que possamos ilustrar claramente a situação:
String empNumber = "A123"; TypedQueryquery = em.createQuery( "SELECT e FROM Employee e WHERE e.empNumber = '" + empNumber + "'", Employee.class); Employee employee = query.getSingleResult();
Essa abordagem tem algumas desvantagens:
-
A incorporação de parâmetros apresenta um risco de segurança, tornando-nos vulneráveis aJPQL injection attacks. Em vez do valor esperado, um invasor pode injetar qualquer expressão JPQL inesperada e possivelmente perigosa
-
Dependendo da implementação da JPA que usamos e das heurísticas do nosso aplicativo, o cache da consulta pode ficar esgotado. Uma nova consulta pode ser criada, compilada e armazenada em cache cada vez que a usamos com cada novo valor / parâmetro. No mínimo, não será eficiente e também pode levar a umOutOfMemoryError inesperado
4. Parâmetros de consulta JPA
Semelhante aos parâmetros de instrução preparados pelo JDBC, o JPA especifica duas maneiras diferentes de gravar consultas parametrizadas usando:
-
Parâmetros posicionais
-
Parâmetros nomeados
Podemos usar parâmetros posicionais ou nomeados, mas não devemos misturá-los na mesma consulta.
4.1. Parâmetros Posicionais
O uso de parâmetros posicionais é uma maneira de evitar os problemas mencionados acima listados anteriormente.
Vamos ver como escreveríamos essa consulta com a ajuda de parâmetros posicionais:
TypedQuery query = em.createQuery(
"SELECT e FROM Employee e WHERE e.empNumber = ?1", Employee.class);
String empNumber = "A123";
Employee employee = query.setParameter(1, empNumber).getSingleResult();
Como vimos no exemplo anterior,we declare these parameters within the query by typing a question mark followed by a positive integer number. Vamos começar com1e seguir em frente, incrementando em um a cada vez.
Podemos usar o mesmo parâmetro mais de uma vez na mesma consulta, o que torna esses parâmetros mais semelhantes aos parâmetros nomeados.
A numeração de parâmetros é um recurso muito útil, pois melhora a usabilidade, a legibilidade e a manutenção.
No entanto, é importante mencionar que,as per the JPA specification, we cannot safely use this feature with native queries since the spec does not mandate it. Embora algumas implementações possam suportá-lo, isso pode afetar a portabilidade de nosso aplicativo.
4.2. Parâmetros posicionais com valor de coleção
Como mencionado anteriormente, também podemos usar parâmetros com valor de coleção:
TypedQuery query = entityManager.createQuery(
"SELECT e FROM Employee e WHERE e.empNumber IN (?1)" , Employee.class);
List empNumbers = Arrays.asList("A123", "A124");
List employees = query.setParameter(1, empNumbers).getResultList();
4.3. Parâmetros nomeados
Parâmetros nomeados são bastante semelhantes aos parâmetros posicionais; no entanto, ao usá-los, tornamos os parâmetros mais explícitos e a consulta se torna mais legível:
TypedQuery query = em.createQuery(
"SELECT e FROM Employee e WHERE e.empNumber = :number" , Employee.class);
String empNumber = "A123";
Employee employee = query.setParameter("number", empNumber).getSingleResult();
O exemplo de consulta anterior é igual ao primeiro, mas usamos:number, um parâmetro nomeado, em vez de?1.
Podemos ver que declaramos o parâmetro com dois pontos seguidos por um identificador de seqüência de caracteres (identificador JPQL), que é um espaço reservado para o valor real que será definido em tempo de execução. Antes de executar a consulta, o parâmetro ou parâmetros devem ser definidos emitindo o métodosetParameter.
Uma coisa interessante a observar é quethe TypedQuery supports method chaining, que se torna muito útil quando vários parâmetros devem ser definidos.
Vamos continuar e criar uma variação da consulta anterior usando dois parâmetros nomeados para ilustrar o encadeamento do método:
TypedQuery query = em.createQuery(
"SELECT e FROM Employee e WHERE e.name = :name AND e.age = :empAge" , Employee.class);
String empName = "John Doe";
int empAge = 55;
List employees = query
.setParameter("name", empName)
.setParameter("empAge", empAge)
.getResultList();
Aqui, estamos recuperando todos os funcionários com o nome e idade fornecidos. Como podemos ver claramente e pode-se esperar, podemos construir consultas commultiple parameters and as many occurrences of them as required.
Se, por algum motivo, precisarmos usar o mesmo parâmetro muitas vezes na mesma consulta, precisamos apenas defini-lo uma vez emitindo o método “setParameter”. No tempo de execução, os valores especificados substituirão cada ocorrência do parâmetro.
Por último, vale a pena mencionar quethe Java Persistence API specification does not mandate named parameters to be supported by native queries. Mesmo quando algumas implementações como o Hibernate o suportam, precisamos levar em consideração que, se a usarmos, a consulta não será tão portátil.
4.4. Parâmetros nomeados com valor de coleção
Para maior clareza, vamos também demonstrar como isso funciona com parâmetros com valor de coleção:
TypedQuery query = entityManager.createQuery(
"SELECT e FROM Employee e WHERE e.empNumber IN (:numbers)" , Employee.class);
List empNumbers = Arrays.asList("A123", "A124");
List employees = query.setParameter("numbers", empNumbers).getResultList();
Como podemos ver, funciona de maneira semelhante aos parâmetros posicionais.
5. Parâmetros de consulta de critérios
Uma consulta JPA pode ser construída usandothe JPA Criteria API, queHibernate’s official documentation explica em detalhes.
Nesse tipo de consulta, representamos parâmetros usando objetos em vez de nomes ou índices.
Vamos construir a mesma consulta novamente, mas desta vez usando a API Criteria para demonstrar como lidar com os parâmetros de consulta ao lidar comCriteriaQuery:
CriteriaBuilder cb = em.getCriteriaBuilder();
CriteriaQuery cQuery = cb.createQuery(Employee.class);
Root c = cQuery.from(Employee.class);
ParameterExpression paramEmpNumber = cb.parameter(String.class);
cQuery.select(c).where(cb.equal(c.get(Employee_.empNumber), paramEmpNumber));
TypedQuery query = em.createQuery(cQuery);
String empNumber = "A123";
query.setParameter(paramEmpNumber, empNumber);
Employee employee = query.getResultList();
Para este tipo de consulta, a mecânica do parâmetro é um pouco diferente, pois usamos um objeto de parâmetro, mas, em essência, não há diferença.
No exemplo anterior, podemos ver o uso da classeEmployee_. Geramos essa classe com o gerador de metamodelos Hibernate. Esses componentes fazem parte do metamodelo JPA estático, que permite que as consultas de critérios sejam construídas de maneira fortemente tipada.
6. Conclusão
Neste artigo, nos concentramos na mecânica de construção de consultas usando parâmetros de consulta JPA ou parâmetros de entrada.
Aprendemos que temos dois tipos de parâmetros de consulta, posicionais e nomeados. Depende de nós qual se encaixa melhor em nossos objetivos.
Também vale a pena observar que todos os parâmetros de consulta devem ser de valor único, exceto para expressõesin. Para expressõesin, podemos usar parâmetros de entrada com valor de coleção, como matrizes ouLists, conforme mostrado nos exemplos anteriores.
O código-fonte deste tutorial, como de costume, éavailable on GitHub.