Spring Data JPA:创建规范查询获取连接
Posted
技术标签:
【中文标题】Spring Data JPA:创建规范查询获取连接【英文标题】:Spring Data JPA: Creating Specification Query Fetch Joins 【发布时间】:2015-06-03 14:49:52 【问题描述】:TL;DR:如何使用 Spring Data JPA 中的规范复制 JPQL Join-Fetch 操作?
我正在尝试构建一个类,该类将使用 Spring Data JPA 处理 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 完成,也可以通过我的 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);
如何使用规范复制此获取策略?我找到了this question here,但它似乎只是将惰性获取变成了急切的获取。
【问题讨论】:
您尝试在toPredicate()
中使用root.fetch()
吗?类似root.fetch("attributes", JoinType.LEFT)
@PredragMaric:这将急切地获取attributes
,但它仍然需要额外的查询。我希望所有提取都成为单个查询的一部分。
是的,但是aliases
的另一个提取应该这样做:root.fetch("aliases", JoinType.LEFT)
我之前尝试过这个,正如我链接的问题中所建议的那样,但它没有达到预期的结果。问题不在于单个规范查询无法获取链接实体,问题在于单个规范查询需要 3 个 SQL 查询来获取这些实体,这是完全没有必要的。
我没听懂?通过编写规范,您到底想要什么具有一组别名和属性的基因实体列表?如果您想要带有规格的基因列表,我可以给您适当的解决方案?
【参考方案1】:
规格类:
public class MatchAllWithSymbol extends Specification<Gene>
private String symbol;
public CustomSpec (String symbol)
this.symbol = symbol;
@Override
public Predicate toPredicate(Root<Gene> root, CriteriaQuery<?> query, CriteriaBuilder cb)
//This part allow to use this specification in pageable queries
//but you must be aware that the results will be paged in
//application memory!
Class clazz = query.getResultType();
if (clazz.equals(Long.class) || clazz.equals(long.class))
return null;
//building the desired query
root.fetch("aliases", JoinType.LEFT);
root.fetch("attributes", JoinType.LEFT);
query.distinct(true);
query.orderBy(cb.asc(root.get("entrezGeneId")));
return cb.equal(root.get("symbol"), symbol);
用法:
List<Gene> list = GeneRepository.findAll(new MatchAllWithSymbol("Symbol"));
【讨论】:
关于如何使规范与 SDJPA 可分页查询一起使用的好技巧。 +1。 它可以工作,但问题休眠在内存和控制台中进行分页我看到这条消息'HHH000104:firstResult/maxResults specified with collection fetch;应用在内存中! ,我尝试使用 fetch join 和实体图来解决它,但我没有找到完美的解决方案。【参考方案2】:您可以在创建规范时指定连接提取,但由于可分页方法也将使用相同的规范 像 findAll(Specification var1, Pageable var2) 和 count 查询会因为 join fetch 而报错。因此,为了处理这个问题,我们可以检查 CriteriaQuery 的 resultType 并仅在它不是 Long 时应用 join(计数查询的结果类型)。见以下代码:
public static Specification<Item> findByCustomer(Customer customer)
return (root, criteriaQuery, criteriaBuilder) ->
/*
Join fetch should be applied only for query to fetch the "data", not for "count" query to do pagination.
Handled this by checking the criteriaQuery.getResultType(), if it's long that means query is
for count so not appending join fetch else append it.
*/
if (Long.class != criteriaQuery.getResultType())
root.fetch(Person_.itemInfo.getName(), JoinType.LEFT);
return criteriaBuilder.equal(root.get(Person_.customer), customer);
;
【讨论】:
以上是关于Spring Data JPA:创建规范查询获取连接的主要内容,如果未能解决你的问题,请参考以下文章
在为嵌套对象创建自定义 Spring Data JPA 查询时获取 IllegalArgumentException