Back-End

7 jul, 2017

Specifications com Spring Data

Publicidade

Specification é um conceito introduzido por Eric Evans, no Livro Domain Driven Design. A ideia do conceito é que você consiga encadear todas as suas regras de negócio como uma query utilizada em busca na base de dados.

Então, partindo da premissa de junção de regra de negócios, por que não usar Specifications para realizar consultas reutilizáveis em banco de dados? Por diversas vezes, temos que fazer consultas iguais, adicionar ou remover um critério e utilizando esse conceito, isso é possível.

Bom, então chega de falar sobre isso e vamos mostrar como utilizar esse conceito com spring-data. A primeira parte é criar um projeto e para isso vamos utilizar o start do spring, que facilita e muito.

Crie um projeto spring-boot e adicione as libs web e jpa, como na imagem acima. Em seguida, é preciso adicionar o plugin do Hibernate para geração de um metamodel das ORM’s, então, adicione no seu pom.xml às linhas de código abaixo:

<plugin>
    <groupId>org.bsc.maven</groupId>
    <artifactId>maven-processor-plugin</artifactId>
    <version>2.2.4</version>
    <executions>
        <execution>
            <id>process</id>
            <goals>
                <goal>process</goal>
            </goals>
            <phase>generate-sources</phase>
            <configuration>
                <processors>
                    <processor>org.hibernate.jpamodelgen.JPAMetaModelEntityProcessor</processor>
                </processors>
            </configuration>
        </execution>
    </executions>
    <dependencies>
        <dependency>
            <groupId>org.hibernate</groupId>
            <artifactId>hibernate-jpamodelgen</artifactId>
            <version>${hibernate.version}</version>
        </dependency>
    </dependencies>
</plugin>

Agora que o projeto já está configurado, é hora de criar a ORM. Vamos criar uma classe simples, chamada Pessoa, com os atributos id, Nome, Telefone e CPF. A classe abaixo está utilizando lombok para geração de getters e setters.

import lombok.Data;
 
import javax.persistence.*;
 
@Data
@Table
@Entity
public class Person {
 
    @Id
    @GeneratedValue(strategy = GenerationType.AUTO)
    private Integer id;
    private String name;
    private Integer cpf, phone;
}

Agora que a ORM está criada, execute um maven package no seu projeto para que o metamodel seja gerado. Ele será utilizado na criação do Specification.

O spring data provê uma interface para criação de Specification, então, a cada método devemos retornar sempre essa interface, e deve-se criar tantos métodos quanto os seus atributos buscáveis. Abaixo, um exemplo de Specification:

import br.com.specification.sample.domain.Person;
import br.com.specification.sample.domain.Person_;
import org.springframework.data.jpa.domain.Specification;
 
public class PersonSpecification {
 
    public static Specification<Person> name(String name) {
        return (root, criteriaQuery, criteriaBuilder) ->
                criteriaBuilder.equal(root.get(Person_.name), name);
    }
 
    public static Specification<Person> phone(Integer phone) {
        return (root, criteriaQuery, criteriaBuilder) ->
                criteriaBuilder.equal(root.get(Person_.phone), phone);
    }
 
    public static Specification<Person> cpf(Integer cpf) {
        return (root, criteriaQuery, criteriaBuilder) ->
                criteriaBuilder.equal(root.get(Person_.cpf), cpf);
    }
 
}

Criada a Specification, precisamos criar o repository do spring-data e informá-lo que ele deve ser capaz de fazer buscas recebendo como parâmetro uma Specification e para isso acontecer, o repository deve estender a classe JpaSpecificationExecutor.

import br.com.specification.sample.domain.Person;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.JpaSpecificationExecutor;
 
public interface PersonRepository extends JpaRepository<Person, Integer>, JpaSpecificationExecutor<Person> {}

Com o repository criado e também com o Specification, agora é só usá-los na suas classes de negócio, a combinação pode ser feita utilizando “and” e/ou “or”.

import br.com.specification.sample.domain.Person;
import br.com.specification.sample.repository.PersonRepository;
import br.com.specification.sample.specification.PersonSpecification;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
import org.springframework.stereotype.Service;
 
import static org.springframework.data.jpa.domain.Specifications.where;
 
@Service
public class PersonServiceImpl implements PersonService {
 
    @Autowired
    private PersonRepository personRepository;
 
    @Override
    public Page<Person> list(String name, Integer cpf, Integer phone, Pageable pageable) {
        return personRepository.findAll(where(PersonSpecification.name(name))
                .or(PersonSpecification.cpf(cpf)).and(PersonSpecification.phone(phone)), pageable);
    }
}

O conceito de Specification ajuda e muito na reutilização do código, pois agora você pode combinar da forma que precisar as consultas por nome, cpf e telefone. As suas classes de negócio ficam mais limpas e com menos if’s. Para ver o exemplo completo de código é só acessar esse link.

Espero que gostem do conteúdo e que compartilhem o que acharam.

Valeu e até a pŕoxima!