Spring Data JPA:创建规范查询获取联接

woe*_*ler 26 java spring jpa criteria-api spring-data-jpa

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...

}
Run Code Online (Sandbox Code Playgroud)

查询字符串参数作为键值对从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...

}
Run Code Online (Sandbox Code Playgroud)

在这个例子中,每次我想要检索Gene记录时,我也想要它的关联GeneAttributeGeneSymbolAlias记录.这一切都按预期工作,并为一个单一的请求Gene将火过3个查询:每一个到Gene,GeneAttributeGeneSymbolAlias表.

问题是没有理由需要运行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);
Run Code Online (Sandbox Code Playgroud)

如何使用规范复制此提取策略?我在这里发现了这个问题,但它似乎只是让懒得的东西变成了渴望的东西.

Don*_*ken 21

规格等级:

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);
    }
}
Run Code Online (Sandbox Code Playgroud)

用法:

    List<Gene> list = GeneRepository.findAll(new MatchAllWithSymbol("Symbol"));
Run Code Online (Sandbox Code Playgroud)

  • 关于如何使规范与 SDJPA 可分页查询一起工作的好技巧。+1。 (2认同)

sur*_*ahl 7

您可以在创建规范时指定连接提取,但由于同一规范将由可分页方法使用,例如findAll(规范var1,可分页var2),并且计数查询将因连接提取而抱怨.因此,为了处理我们可以检查CriteriaQuery的resultType并仅在它不是Long时应用连接(计数查询的结果类型).见下面的代码:

    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);
    };
}
Run Code Online (Sandbox Code Playgroud)