有没有办法通过 QueryDSL 中的谓词 API 急切地获取惰性关系?

Posted

技术标签:

【中文标题】有没有办法通过 QueryDSL 中的谓词 API 急切地获取惰性关系?【英文标题】:Is there a way to eager fetch a lazy relationship through the Predicate API in QueryDSL? 【发布时间】:2013-05-03 04:44:36 【问题描述】:

我正在使用来自 Spring Data JPA 项目的QueryDslPredicateExecutor,并且我正面临着急切获取惰性关系的需要。我知道我可以在 Repository 接口中使用本机 JPA-QL 查询,甚至可以使用 Query DSL 中的 JPAQLQuery,但我很感兴趣,如果这甚至可以为将来的需求构建查询提供便利。

【问题讨论】:

如果这可能是什么意思? QueryDslPredicateExecutor 提供了一小部分 Querydsl 功能,更多需要您自己编写查询。 是的,我知道,但我试图想办法避免这种情况并使用 Predicate API 来构建更复杂的查询,允许我随意获取关系,附加其他谓词,因此不必在 Repository 接口中为特定目的创建带有注释查询的方法。 【参考方案1】:

我遇到了类似的问题,我必须在使用 Predicates 和 QueryDslPredicateExecutor 时获取加入集合。

我所做的是创建一个自定义存储库实现,以添加一个允许我定义应获取的实体的方法。

不要被这里的代码量吓倒,它实际上非常简单,您只需做很少的更改即可在您的应用程序中使用它

这是自定义仓库的界面

@NoRepositoryBean
public interface JoinFetchCapableRepository<T, ID extends Serializable> extends     JpaRepository<T, ID>, QueryDslPredicateExecutor<T> 

    Page<T> findAll(Predicate predicate, Pageable pageable, JoinDescriptor... joinDescriptors);

JoinDescriptor

public class JoinDescriptor 
    public final EntityPath path;
    public final JoinType type;

    private JoinDescriptor(EntityPath path, JoinType type) 
        this.path = path;
        this.type = type;
    

    public static JoinDescriptor innerJoin(EntityPath path) 
        return new JoinDescriptor(path, JoinType.INNERJOIN);
    

    public static JoinDescriptor join(EntityPath path) 
        return new JoinDescriptor(path, JoinType.JOIN);
    

    public static JoinDescriptor leftJoin(EntityPath path) 
        return new JoinDescriptor(path, JoinType.LEFTJOIN);
    

    public static JoinDescriptor rightJoin(EntityPath path) 
        return new JoinDescriptor(path, JoinType.RIGHTJOIN);
    

    public static JoinDescriptor fullJoin(EntityPath path) 
        return new JoinDescriptor(path, JoinType.FULLJOIN);
    

自定义存储库的实现

public class JoinFetchCapableRepositoryImpl <T, ID extends Serializable> extends QueryDslJpaRepository<T, ID> implements JoinFetchCapableRepository<T, ID> 

    private static final EntityPathResolver DEFAULT_ENTITY_PATH_RESOLVER = SimpleEntityPathResolver.INSTANCE;

    private final EntityPath<T> path;
    private final PathBuilder<T> builder;
    private final Querydsl querydsl;

    public JoinFetchCapableRepositoryImpl(JpaEntityInformation<T, ID> entityInformation, EntityManager entityManager) 
        this(entityInformation, entityManager, DEFAULT_ENTITY_PATH_RESOLVER);
    

    public JoinFetchCapableRepositoryImpl(JpaEntityInformation<T, ID> entityInformation, EntityManager entityManager, EntityPathResolver resolver) 
        super(entityInformation, entityManager, resolver);
        this.path = resolver.createPath(entityInformation.getJavaType());
        this.builder = new PathBuilder<>(path.getType(), path.getMetadata());
        this.querydsl = new Querydsl(entityManager, builder);
    

    @Override
    public Page<T> findAll(Predicate predicate, Pageable pageable, JoinDescriptor... joinDescriptors) 
        JPQLQuery countQuery = createQuery(predicate);
        JPQLQuery query = querydsl.applyPagination(pageable, createFetchQuery(predicate, joinDescriptors));

        Long total = countQuery.count();
        List<T> content = total > pageable.getOffset() ? query.list(path) : Collections.<T> emptyList();

        return new PageImpl<>(content, pageable, total);
    

    protected JPQLQuery createFetchQuery(Predicate predicate, JoinDescriptor... joinDescriptors) 
        JPQLQuery query = querydsl.createQuery(path);
        for(JoinDescriptor joinDescriptor: joinDescriptors)
            join(joinDescriptor, query);
        return query.where(predicate);
    

    private JPQLQuery join(JoinDescriptor joinDescriptor, JPQLQuery query) 
        switch(joinDescriptor.type) 
            case DEFAULT:
                throw new IllegalArgumentException("cross join not supported");
            case INNERJOIN:
                query.innerJoin(joinDescriptor.path);
                break;
            case JOIN:
                query.join(joinDescriptor.path);
                break;
            case LEFTJOIN:
                query.leftJoin(joinDescriptor.path);
                break;
            case RIGHTJOIN:
                query.rightJoin(joinDescriptor.path);
                break;
            case FULLJOIN:
                query.fullJoin(joinDescriptor.path);
                break;
        
        return query.fetch();
    

工厂创建自定义存储库,替换默认的 QueryDslJpaRepository

public class JoinFetchCapableQueryDslJpaRepositoryFactoryBean<R extends JpaRepository<T, I>, T, I extends Serializable>
        extends JpaRepositoryFactoryBean<R, T, I> 

    protected RepositoryFactorySupport createRepositoryFactory(EntityManager entityManager) 

        return new JoinFetchCapableQueryDslJpaRepositoryFactory(entityManager);
    
    private static class JoinFetchCapableQueryDslJpaRepositoryFactory<T, I extends Serializable> extends JpaRepositoryFactory 

        private EntityManager entityManager;

        public JoinFetchCapableQueryDslJpaRepositoryFactory(EntityManager entityManager) 
            super(entityManager);
            this.entityManager = entityManager;
        

        protected Object getTargetRepository(RepositoryMetadata metadata) 
            return new JoinFetchCapableRepositoryImpl<>(getEntityInformation(metadata.getDomainType()), entityManager);
        

        protected Class<?> getRepositoryBaseClass(RepositoryMetadata metadata) 
            return JoinFetchCapableRepository.class;
        
    

最后一步是更改 jpa 配置,使其使用此工厂而不是默认工厂:

<jpa:repositories base-package="com.mycompany.repository"
                      entity-manager-factory-ref="entityManagerFactory"
                      factory-class="com.mycompany.utils.spring.data.JoinFetchCapableQueryDslJpaRepositoryFactoryBean" />

然后你可以像这样从你的服务层使用它:

public Page<ETicket> list(ETicketSearch eTicket, Pageable pageable) 
    return eticketRepository.findAll(like(eTicket), pageable, JoinDescriptor.leftJoin(QETicket.eTicket.order));

通过使用 JoinDescriptor,您将能够根据您的服务需求指定要加入的内容。

感谢 Murali 在这里的回复:Spring Data JPA and Querydsl to fetch subset of columns using bean/constructor projection 请看一下。

【讨论】:

感谢您的回答。虽然我最终使用了 Criteria 方法,但我将在一个较小的项目上测试这种方法,我有一个用于测试东西的项目。感谢您的努力... 您好,感谢您的回答。是否可以扩展 JoinDescriptor 以包含 onetomany(即 SetPath)而不仅仅是 EntityPath? @AngelVillalain “虽然我最终使用了 Criteria 方法” - 你能解释一下你是如何让它工作的吗? @Pranjal,对不起,那是很久以前的事了,我现在不记得细节了。我会尝试,如果我记得我会更新答案。对不起!【参考方案2】:

弹簧数据has introduced JPA Entity Graph support。当心那是does not currently work with graphs that are traversed via EmbeddedIds。

【讨论】:

当被问到这个问题时,没有对 JPA Entity Graph 的支持,确切地说它仍在开发中。但是现在可以使用了,我认为这个问题应该将其视为一种解决方案。

以上是关于有没有办法通过 QueryDSL 中的谓词 API 急切地获取惰性关系?的主要内容,如果未能解决你的问题,请参考以下文章

QueryDSL 在构建谓词查询时添加交叉连接

带有 Querydsl 的 JPA 谓词

为 oneTomany 基于关系的查询编写 queryDSL 谓词查询

如何使用 BooleanBuilder (QueryDSL) 为可选的 OnetoOne JPA/Hibernate 关系建模谓词?

QueryDSL删除方法

使用 QueryDSL 使用 Spring Data MongoDB 查询 DBRef