TL; DR: как вы реплицируете операции JPY Join-Fetch с использованием спецификаций в Spring Data JPA?
Я пытаюсь создать класс, который будет обрабатывать динамическое построение запросов для объектов JPA, используя Spring Data JPA. Для этого я определяю несколько методов, которые создают объекты Predicate
(такие как предлагается в Spring Data JPA docs и в другом месте), а затем связывание их при отправке соответствующего параметра запроса. Некоторые из моих сущностей имеют отношения "один ко многим" с другими сущностями, которые помогают их описать, которые охотно извлекаются при запросе и объединении в коллекции или карты для создания DTO. Упрощенный пример:
@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...
}
Параметры строки запроса передаются из класса Controller
классу Service
в качестве пар ключ-значение, где они обрабатываются и собираются в 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...
}
В этом примере каждый раз, когда я хочу получить запись Gene
, я также хочу иметь связанные записи GeneAttribute
и GeneSymbolAlias
. Все это работает так, как ожидалось, и запрос на один Gene
будет сбрасывать 3 запроса: каждый из них соответствует таблицам Gene
, GeneAttribute
и GeneSymbolAlias
.
Проблема заключается в том, что нет причин, по которым необходимо запросить 3 запроса, чтобы получить единственный объект Gene
со встроенными атрибутами и псевдонимами. Это можно сделать в простом SQL, и это можно сделать с помощью JPQL-запроса в моем репозитории данных JPA Spring:
@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);
Как я могу реплицировать эту стратегию выбора с помощью спецификаций? Я нашел этот вопрос здесь, но он только кажется, делает ленивые выборки в нетерпеливые выборки.