QUERY DINAMICHE CON SPRING DATA E SPECIFICATION
Quante volte vi sarà capitato di sviluppare una API Rest nell'ecosistema Spring Boot e di dover esporre dati da un database, sia esso relazionale o meno, in modo pulito restituendo solo un subset delle occorrenze e filtrando per specifiche proprietà della risorsa interrogata?
Ecco che le Specification rappresentano uno strumento utile in tutti questi casi in cui abbiamo la necessità di applicare dei filtri in modo condizionale su una determinata entità o su altre ad essa collegate, senza ricorrere a codici verbosi con metodi DAO dai nomi impronunciabili.
Partiamo con l'esempio. Lo starter è la risorsa sulla quale costruire una ricerca filtrata:
@Entitypublic class User { @Id @GeneratedValue(strategy = GenerationType.AUTO) private long id; private String firstname; private String lastname; private String address; private String email; // standard getters and setters }
Fatto questo, procediamo con la creazione del repository. A differenza di quanto fatto usualmente, in questo caso è necessario implementare anche l'interfaccia JpaSpecificationExecutor che appunto consente l'esecuzione delle specification con le JPA Criteria API.
@Repository
public interface UserRepository extends JpaRepository<User>, JpaSpecificationExecutor<User> {
}
L'ultimo passo è la creazione della classe con la logica per l'applicazione dei filtri: si tratta in sostanza di costruire una serie di predicati in modo condizionale a seconda dei filtri valorizzati nell'oggetto di request inviato dal client.
package com.grimaldi.cargo.repositories.specification;
import java.time.LocalDate;
import java.util.ArrayList;
import java.util.List;
import javax.persistence.criteria.CriteriaBuilder;
import javax.persistence.criteria.CriteriaQuery;
import javax.persistence.criteria.Join;
import javax.persistence.criteria.Predicate;
import javax.persistence.criteria.Root;
import org.springframework.data.jpa.domain.Specification;
import com.grimaldi.cargo.dto.statistiche.request.RequestFaccineDTO;
import com.grimaldi.cargo.model.Account;
import com.grimaldi.cargo.model.reports.DettaglioReportFaccine;
import com.grimaldi.cargo.model.reports.ReportFaccine;
public class UserSpecification implements Specification<User> {
private static final long serialVersionUID = -1418612780146044594L;
private final UserRequestDTO criteria;
public UserSpecification(UserRequestDTO criteria) {
this.criteria = criteria;
}
@Override
public Predicate toPredicate(Root<User> root, CriteriaQuery arg1, CriteriaBuilder criteriaBuilder) {
List<Predicate> predicates = new ArrayList<>();
if(StringUtils.hasText(criteria.getFirstname())) {
predicates.add(criteriaBuilder.equal(root.get("firstname"), criteria.getFirstname()));
}
if(StringUtils.hasText(criteria.getLastname())) {
predicates.add(criteriaBuilder.equal(root.get("year"), criteria.getLastname()));
}
if(StringUtils.hasText(criteria.getEmail())) {
predicates.add(criteriaBuilder.equal(root.get("year"), criteria.getEmail()));
}
return criteriaBuilder.and(predicates.toArray(new Predicate[predicates.size()]));
}
}
Nell'esempio sopra riportato, la classe UserSpecification presenta un attributo denominato criteria, che "wrappa" le informazioni sulle quali filtrare gli utenti a db.
Creata la logica di "filtraggio", sarà possibile passare questo predicato in input al repository della risorsa User:
var spec = new UserSpecification(requestDTO);
var limit = requestDTO.getLimit() != null ? requestDTO.getLimit() : LIMIT;
var page = requestDTO.getPage() != null ? requestDTO.getPage() - 1 : 0;
Page<User> records = userRepository.findAll(spec, PageRequest.of(page, limit, Sort.by("id").descending()));
Ecco che il gioco è fatto...il risultato è un service snello, non verboso e pulito. L'intera logica di filtraggio è demandata alla Specification evitando di ricorrere alle query in stile Spring Data con nomi complessi e col tempo poco leggibili.
Se ti è piaciuto l'articolo condividilo con qualche tuo conoscente in modo. Se invece ritieni che io abbia scritto qualche "scemenza", non esitare a commentare il post con suggerimenti o richieste.
Il codice di questo articolo è disponibile sul mio GitHub all'indirizzo:
Comments
Post a Comment