TL; DR: W jaki sposób można replikować operacje JPD Join-Fetch przy użyciu specyfikacji w Spring Data JPA?Wiosenne dane JPA: tworzenie zapytań dotyczących specyfikacji zapytań
Próbuję zbudować klasę, która będzie obsługiwać dynamiczne tworzenie zapytań dla podmiotów JPA przy użyciu Spring Data JPA. Aby to zrobić, definiuję wiele metod, które tworzą obiekty Predicate
(takie jak sugerowane w Spring Data JPA docs i gdzie indziej), a następnie łączą je, gdy zostanie przesłany odpowiedni parametr zapytania. Niektóre z moich jednostek mają relacje jeden-do-wielu z innymi podmiotami, które pomagają je opisać, które są chętnie pobierane, gdy są wypytywane i łączone w kolekcje lub mapy do tworzenia DTO. Uproszczony przykład:
@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...
}
parametry ciągu zapytania przechodzą z klasy Controller
do klasy Service
jako pary klucza wartości, przy czym są one przetwarzane i montuje się 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...
}
W tym przykładzie za każdym razem, gdy chcę odzyskać rekord Gene
, chcę również jego skojarzone zapisy GeneAttribute
i GeneSymbolAlias
. Wszystko działa zgodnie z oczekiwaniami, a żądanie pojedynczego użytkownika Gene
wywoła 3 zapytania: po jednym dla tabel Gene
, GeneAttribute
i GeneSymbolAlias
.
Problem polega na tym, że nie ma powodu, aby 3 zapytania były uruchamiane w celu uzyskania pojedynczej jednostki Gene
z osadzonymi atrybutami i aliasami. Można to zrobić w zwykły SQL, a można to zrobić z kwerendy JPQL w moim repozytorium Wiosna danych JPA:
@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);
Jak można powielać tę strategię pobierania przy użyciu specyfikacji? Znalazłem this question here, ale wydaje się, że tylko leniwy pobieram do pożądliwych pobrań.
Czy próbowałeś z 'root.fetch()' wewnątrz 'toPredicate()'? Coś w rodzaju 'root.fetch (" attributes ", JoinType.LEFT)' –
@PredragMaric: To z niecierpliwością pobierze 'atrybuty', ale nadal wymaga dodatkowego zapytania. Chcę, aby wszystkie pobrania były częścią pojedynczego zapytania. – woemler
Tak, ale powinno być wykonane inne pobranie dla 'aliasów:' root.fetch ("aliasy", JoinType.LEFT) ' –