Técnico

Criando consultas com Specifications

springdata

O Spring Data é uma solução que unifica e facilita o acesso a diferentes tecnologias de armazenamento de dados, ele foi desenvolvido com base na especificação JPA 2 possibilitando assim a utilização de qualquer framework que siga tal especificação.

Independentemente da forma de armazenamento, as classes de “repositório” (conhecidas também como Data Access Objects ou DAOs) geralmente disponibilizam operações de CRUD (Create-Read-Update-Delete) para um determinado objeto de domínio ou entidade(Entity), além de métodos de consulta especificas e formas de paginação de dados e ordenação. O Spring Data oferece interfaces genéricas para essas funcionalidades (CrudRepository, JpaRepository e PagingAndSortingRepository).

Com o uso dessas interfaces o Spring Data abstrai a necessidade de criação de classes concretas para os repositórios, sendo necessário apenas criar uma interface específica para cada entidade. Onde podemos criar consultas usando apenas as assinaturas dos métodos no repositório. Esses métodos são chamados de query methods e poupam um grande tempo de programação.

Utilizando o Spring Data temos uma grande gama de recursos já disponíveis para consultas, mas infelizmente não conseguimos manipular uma query em tempo de execução. Esse tipo de consulta é bastante necessário quando temos por exemplo uma listagem com diferente formas de filtragem.

Ex: Uma lista de livros

NomeAutorEditoraAno Publicação
O SilmarillionJ. R .R TolkienWMF Martins Fontes2011
Prince of ThornsMark LawrenceDarkSide Books2013
O PistoleiroStephen KingObjetiva2004
StonehengeBernard CornwellRecord2008
A Dança dos DragõesGeorge R. R. MartinLeYa Brasil2014
O Rei do InvernoBernard CornwellRecord2001
ExcaliburBernard CornwellRecord2004
O HobbitJ. R .R TolkienWMF Martins Fontes2002

[markdown]
Digamos que gostaríamos de filtrar essa lista pelo autor e pelo ano de publicação da obra, e em outro momento pela Editoria e por parte do nome do livro. Como temos 4 campos de filtro e cada um deles podendo ser utilizado de forma independente, precisaríamos então desenvolver todas as variações possíveis para esses campos, sendo uma grande quantidade de consulta gerando assim um esforço para implementar um simples filtrar por.

###Trabalhando com o Specifications

Utilizando o Specifications conseguimos criar uma solução que permite criar e/ou manipular consultas em tempo de execução.

O contrato Specification determina que um predicado (condição SQL) é criado a partir das classes Root, CriteriaQuery e CriteriaBuilder. Esses três elementos fazem parte da API de Criteria do JPA 2, eles são gerenciados e injetados pelo próprio Spring.

Vamos começar definindo um Predicado(javax.persistence.criteria.Predicate) para nossa consulta no nome do livro

“`
public final class LivroSpecification {

public static Specification byNome(String name) {
return new Specification() {

@Override
public Predicate toPredicate(Root root, CriteriaQuery<?> query, CriteriaBuilder builder) {
return builder.like(root.get(“nome”), String.format(“%s%”, name.trim()));
}
};
}
}
“`

Criamos um classe LivroSpecification onde implementamos os predicados de cada filtro da nossa listagem. Nesse primeiro o campo da consulta é diretamente na Entidade Livro(Não esqueça de adicionar as % antes de depois do parâmetro caso você precise usar um like na sua consulta)

Na sequência vamos para o nome do autor, nesse caso vamos precisar de um Join na Entidade Autor.

“`
public class LivroSpecifications {

public static Specification whereAutor(String autor) {
return new Specification() {

@Override
public Predicate toPredicate(Root root, CriteriaQuery<?> query, CriteriaBuilder builder) {
Path pathAutor = root.join(“autor”).get(“autor”); // aqui é feito o join no atributo autor da entidade Livro
Path autorNome = pathAutor.get(“nome”);
return builder.like(autorNome, String.format(“%s%”, autor.trim()));
}
};
}
“`

Para o filtro na Editora seguimos a mesma ideia usada no Autor

“`
public static Specification whereEditora(String editora) {
return new Specification() {

@Override
public Predicate toPredicate(Root root, CriteriaQuery<?> query, CriteriaBuilder builder) {
Path pathEditora = root.join(“editora”).get(“editora”);
Path editoraNome = path.get(“nome”);
return builder.like(editoraNome, String.format(“%s%”, editora.trim()));
}
};
}
“`

Nesse exemplo quando usamos o root.join estamos fazendo o inner join convencional, porém se fosse necessário um left join podemos passar um JoinType no método de join ficando assim:

`
root.join(“editora”, JoinType.LEFT);
`

Na nossa classe de Serviço LivroServiceImpl colocamos uma pequena lógica validando a existência ou não dos parâmetros enviados pela tela.
Ficando assim, o usuário escolhe quais filtros que deseja usar, a tela enviar para o serviço de listagem os parâmetros do filtro, onde validamos e adicionamos conforme a necessidade na nossa consulta.

“`
@Override
public Page findAll(String nomeLivro, String autor, String editora, Pageable page) {

Page list = null;
Specification where = null;

if(nomeLivro != null){
where = addClausula(where, byNome(idSistema));
}

if(autor != null){
where = addClausula(where, whereAutor(idUnidade));
}

if(editora != null){
where = addClausula(where, whereEditora(ambiente));
}

list = livroRepository.findAll(where, page);

return list;
}

private Specification addClausula(Specification where, Specification novaClausula){
if(where == null){
return where(novaClausula);
} else {
return where(where).and(novaClausula);
}
}
“`

Dessa forma podemos montar a nossa consulta apenas com os parâmetro usados e não precisamos implementar cada uma das variações necessárias para atender essa listagem com filtros.

Caso, o serviço não receba nenhum parâmetro com valor, ou seja, igual a NULL. O Specification where será passado para o findAll como NULL.
Com isso, nosso repository fará a consulta sem o Where.

Nesse exemplo utilizo a Interface org.springframework.data.domain.Pageable apenas para que a consulta seja paginada, apenas removendo esse parâmetro no findAll a nossa consulta retornar um List de Livros(List) ou outra forma de coleção do Java.

Nosso Repository o LivroRepository precisa estender uma interface especifica para o uso o Specification que é org.springframework.data.jpa.repository.JpaSpecificationExecutor.

“`
public interface LivroRepository extends JpaRepository<Livro, Long>, JpaSpecificationExecutor {

}
“`

Utilizando o Specification conseguimos montar a nossa consulta em tempo de execução, nesse exemplo usamos ele para evitar a criação de várias consultas na nossa listagem por filtro, essa é apenas uma pequena demonstração do que podemos fazer com o Specification.

[/markdown]

Deixe um comentário

O seu endereço de e-mail não será publicado. Campos obrigatórios são marcados com *

Compartilhe isso: