programing

Spring Data JPA: 규격 쿼리 가져오기 조인 만들기

topblog 2023. 9. 20. 20:02
반응형

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

}

쿼리 문자열 매개 변수가 에서 전달됩니다.Controller에 걸맞은Servicekey-value pairs로 분류되며, 여기서 이들은 다음과 같습니다.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기록.이 모든 것이 예상대로 작동하고, 단 한 명의 요청이 있습니다.Gene3개의 쿼리를 발사할 것입니다. 각 하나씩.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);

사양을 사용하여 이 가져오기 전략을 복제하려면 어떻게 해야 합니까?저는 여기서 이 질문을 발견했지만, 그것은 게으른 페치를 열성적인 페치로 만드는 것처럼만 보입니다.

규격 클래스:

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"));

규격을 생성하는 동안 조인 페치를 지정할 수 있지만, 동일한 규격이 모두 찾기(규격 var1, 페이지 가능 var2)와 같은 페이지 가능한 메서드에서도 사용되므로 조인 페치로 인해 카운트 쿼리가 불만을 제기합니다.따라서 처리하기 위해서는 결과를 확인할 수 있습니다.CriteriaQuery 유형이며 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);
    };
}

언급URL : https://stackoverflow.com/questions/29348742/spring-data-jpa-creating-specification-query-fetch-joins

반응형