Spring Data JPA: la Création de la Spécification de la Requête d'Extraction des Jointures
TL;DR: Comment avez-vous répliquer JPQL Rejoignez-opérations d'Extraction à l'aide de spécifications dans Spring Data JPA?
Je suis en train de construire une classe qui va gérer les dynamiques de construction des requêtes pour des entités JPA à l'aide de Spring Data JPA. Pour ce faire, je suis à la définition d'un certain nombre de méthodes qui créent Predicate
des objets (comme le suggère le Spring Data JPA docs et ailleurs), et ensuite chaîne quand le paramètre de requête est présentée. Certains de mes entités ont un-à-plusieurs relations avec d'autres entités qui aident à décrire, qui sont très récupéré lorsqu'il est interrogé et se fondirent dans des collections ou des cartes pour DTO création. Un exemple simplifié:
@Entity
public class Gene {
@Id
@Column(name="entrez_gene_id")
privateLong id;
@Column(name="gene_symbol")
private String symbol;
@Column(name="species")
private String species;
@OneToMany(mappedBy="gene", fetch=FetchType.EAGER)
private Set<GeneSymbolAlias> aliases;
@OneToMany(mappedBy="gene", fetch=FetchType.EAGER)
private Set<GeneAttributes> attributes;
//etc...
}
@Entity
public class GeneSymbolAlias {
@Id
@Column(name = "alias_id")
private Long id;
@Column(name="gene_symbol")
private String symbol;
@ManyToOne(fetch=FetchType.LAZY)
@JoinColumn(name="entrez_gene_id")
private Gene gene;
//etc...
}
Paramètres de chaîne de requête sont transmises à partir du Controller
classe à la Service
classe comme paires clé-valeur, où ils sont traités et assemblés en Predicates
:
@Service
public class GeneService {
@Autowired private GeneRepository repository;
@Autowired private GeneSpecificationBuilder builder;
public List<Gene> findGenes(Map<String,Object> params){
return repository.findAll(builder.getSpecifications(params));
}
//etc...
}
@Component
public class GeneSpecificationBuilder {
public Specifications<Gene> getSpecifications(Map<String,Object> params){
Specifications<Gene> = null;
for (Map.Entry param: params.entrySet()){
Specification<Gene> specification = null;
if (param.getKey().equals("symbol")){
specification = symbolEquals((String) param.getValue());
} else if (param.getKey().equals("species")){
specification = speciesEquals((String) param.getValue());
} //etc
if (specification != null){
if (specifications == null){
specifications = Specifications.where(specification);
} else {
specifications.and(specification);
}
}
}
return specifications;
}
private Specification<Gene> symbolEquals(String symbol){
return new Specification<Gene>(){
@Override public Predicate toPredicate(Root<Gene> root, CriteriaQuery<?> query, CriteriaBuilder builder){
return builder.equal(root.get("symbol"), symbol);
}
};
}
//etc...
}
Dans cet exemple, chaque fois que je veux récupérer un Gene
record, je veux aussi ses associés GeneAttribute
et GeneSymbolAlias
enregistrements. Tout cela fonctionne comme prévu, et une demande pour un seul Gene
se déclenche 3 requêtes: un pour le Gene
, GeneAttribute
, et GeneSymbolAlias
tables.
Le problème est qu'il n'y a aucune raison pour que les 3 requêtes doivent exécuter pour obtenir un seul Gene
entité incorporées des attributs et des alias. Cela peut être fait dans la plaine SQL, et il peut être fait avec une requête JPQL dans mon Spring Data JPA référentiel:
@Query(value = "select g from Gene g left join fetch g.attributes join fetch g.aliases where g.symbol = ?1 order by g.entrezGeneId")
List<Gene> findBySymbol(String symbol);
Comment puis-je reproduire ce type de stratégies de chargement à l'aide de Spécifications? J'ai trouvé cette question ici, mais il semble que pour rendre paresseux extrait en hâte extrait.
- Avez-vous essayé avec
root.fetch()
à l'intérieur detoPredicate()
? Quelque chose commeroot.fetch("attributes", JoinType.LEFT)
- Qui seront heureux de récupérer le
attributes
, mais elle nécessite encore une autre requête. Je veux tous les extractions de faire partie d'une seule requête. - Oui, mais une autre extraction de
aliases
devrait le faire:root.fetch("aliases", JoinType.LEFT)
- J'ai essayé cela avant, comme il était suggéré dans la question que j'ai lié, mais il ne permet pas d'atteindre le résultat souhaité. Le problème n'est pas que l'extraction des entités liées ne peut être fait avec une seule spécification de la requête, le problème est que la seule spécification de la requête 3 requêtes SQL pour obtenir ces entités, ce qui est totalement inutile.
- Je n'ai pas tout? Ce que vous voulez exactement que vous avez besoin de la Liste de gènes entité avec l'ensemble des alises et de l'attribut par la rédaction de cahier des charges? Dans le cas où vous voulez la Liste de Gène avec le cahier des charges afin que je puisse vous donner une solution appropriée?
- j'ai écrit une bibliothèque de recherche sur une entité avec des paramètres, il crée des requêtes hql à l'aide d'objets de paramètre. j'ai utiliser le paramètre classes au lieu de la table de hachage. github.com/ekremucar/hqlplus si vous définissez une propriété de l'objet parameter il sera ajouté à la clause where. vous pouvez définir le type de recherche (comme, eq) et de définir type d'extraction d'alias.
Vous devez vous connecter pour publier un commentaire.
Spécification de la classe:
Utilisation:
Vous pouvez spécifier le join fetch lors de la création de la Spécification, mais depuis le même cahier des charges sera utilisé par paginable ailleurs, les méthodes de
comme findAll(Spécification var1, Paginable var2) et à compter de la requête va se plaindre à cause de join fetch. Par conséquent, pour le gérer, nous pouvons vérifier l'resultType de CriteriaQuery et appliquer joindre seulement si il n'est pas Long (type de résultat pour le comte de la requête). voir code ci-dessous:
Je suggère cette bibliothèque pour la spécification.
https://github.com/tkaczmarzyk/specification-arg-resolver
À partir de cette bibliothèque : https://github.com/tkaczmarzyk/specification-arg-resolver#join-fetch
Vous pouvez utiliser @JoinFetch annotation pour spécifier les chemins d'accès pour effectuer l'extraction de rejoindre sur le. Par exemple: