TL;DR: 如何使用Spring Data JPA中的规范复制JPQL Join-Fetch操作?
我正在尝试构建一个类,使用Spring Data JPA处理JPA实体的动态查询。为此,我定义了一些方法,创建Predicate
对象(如Spring Data JPA文档和其他地方建议的那样),并在适当的查询参数提交时链接它们。我的一些实体与其他实体具有一对多的关系,这些实体在查询时会被急切地获取,并被合并到集合或映射中以便于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
表。问题在于获取单个带有嵌入属性和别名的
Gene
实体没有理由运行3个查询。这可以用普通SQL完成,并且可以使用Spring Data JPA存储库中的JPQL查询来完成:@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);
我该如何使用规范来复制这个获取策略?我在这里找到了这个问题, 但它似乎只能将惰性获取转换为急切获取。
toPredicate()
内部使用root.fetch()
了吗?像这样:root.fetch("attributes", JoinType.LEFT)
。 - Predrag Maricaliases
的获取应该可以解决它:root.fetch("aliases", JoinType.LEFT)
。 - Predrag Maric