TL; DR: Wie replizieren Sie JPQL-Join-Fetch-Vorgänge mithilfe von Spezifikationen in Spring Data JPA?Spring-Daten-JPA: Erstellen von Abfrageabfragen für die Spezifikationsabfrage
Ich versuche, eine Klasse zu erstellen, die dynamische Abfrage erstellen für JPA-Entitäten mithilfe von Spring Data JPA. Um dies zu tun, ich definiere eine Anzahl von Methoden, die Predicate
Objekte (wie in der Spring Data JPA docs und anderswo vorgeschlagen) erstellen und dann verketten, wenn der entsprechende Abfrageparameter übergeben wird. Einige meiner Entitäten haben Eins-zu-Viele-Beziehungen mit anderen Entitäten, die ihnen helfen, sie zu beschreiben, die bei der Abfrage und Zusammenführung zu Sammlungen oder Karten für die DTO-Erstellung eifrig abgerufen werden. Ein vereinfachtes Beispiel:
@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...
}
Abfrage-String-Parameter aus der Controller
Klasse die Service
Klasse übergeben werden als Schlüssel-Wert-Paare, wo sie verarbeitet werden und zusammengebaut in 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...
}
In diesem Beispiel jedes Mal, wenn ich einen Gene
Datensatz abrufen möchte, möchte ich auch die zugehörigen GeneAttribute
und GeneSymbolAlias
Datensätze. Dies funktioniert alles wie erwartet, und eine Anfrage für eine einzige Gene
wird 3 Abfragen auslösen: jeweils eine zu den, GeneAttribute
und GeneSymbolAlias
Tabellen.
Das Problem ist, dass es keinen Grund gibt, dass 3 Abfragen ausgeführt werden müssen, um eine einzige Gene
Entität mit eingebetteten Attributen und Aliasen zu erhalten. Dies kann in einfachen SQL durchgeführt werden, und es kann mit einer JPQL Abfrage in meinem Spring Data JPA-Repository erfolgen:
@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);
Wie kann ich diese Abrufstrategie Spezifikationen mit replizieren? Ich habe this question here gefunden, aber es scheint nur faule Fetches in eifrige Fetches zu machen.
Haben Sie versucht mit 'root.fetch()' in 'toPredicate()'? Etwas wie 'root.fetch (" attributes ", JoinType.LEFT)' –
@PredragMaric: Das wird eifrig die 'Attribute' holen, aber es erfordert noch eine zusätzliche Abfrage. Ich möchte, dass alle Abrufe Teil einer einzigen Abfrage sind. – woemler
Ja, aber ein anderer Aufruf für 'Aliase 'sollte es tun:' root.fetch ("aliases", JoinType.LEFT) ' –