9

I am using the QueryDslPredicateExecutor from Spring Data JPA project, and I am facing the need to eager fetch a lazy relation. I know that I can use a native JPA-QL query in the Repository interface, or even used the JPAQLQuery from Query DSL, but I was intrigued if this is even possible in order to facilitate building queries for future needs.

Angel Villalain
  • 595
  • 4
  • 16
  • What do you mean by if this is even possible? `QueryDslPredicateExecutor` provides a small subset of Querydsl functionality, for more you need to write queries yourself. – Timo Westkämper May 10 '13 at 09:10
  • Yeah I know that, but I was trying to think of a way of avoiding that and using the Predicate API, to build more complex queries, that allow me to fetch relationships at will, append other predicates, thus not having to create a method with an annotated query for a specific purpose in the Repository interface. – Angel Villalain May 10 '13 at 12:32

2 Answers2

13

I had a similar problem where I had to fetch join a Collection while using Predicates and QueryDslPredicateExecutor.

What I did was to create a custom repository implementation to add a method that allowed me to define the entities that should be fetched.

Don't be daunted by the amount of code in here, it's actually very simple and you will need to do very few changes to use it on your application

This is the interface of the custom repository

@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);
    }
}

Implementation of the custom repository

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();
    }
}

Factory to create the custom repositories, replacing the default 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;
        }
    }
}

Last step is to change the jpa configuration so it uses this factory instead of the default one:

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

Then you can use it from your service layer like this:

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

By using JoinDescriptor you will be able to specify what you want to join based on your service needs.

I was able to do this thanks to the Murali's response here: Spring Data JPA and Querydsl to fetch subset of columns using bean/constructor projection Please take a look.

Community
  • 1
  • 1
Adrian Lopez
  • 1,776
  • 1
  • 17
  • 35
  • 1
    Thanks for your answer. Although I ended up working with the Criteria approach, I will test this approach on a smaller project I have for testing stuff. Thanks for your effort... – Angel Villalain Feb 12 '14 at 14:10
  • Hi, thanks for the answer. Is it possible to extend JoinDescriptor to include onetomany (i.e SetPath) instead of only EntityPath? – Shay Elkayam Oct 21 '15 at 11:25
  • @AngelVillalain "Although I ended up working with the Criteria approach" - Can you explain how did you get it to work? – Pranjal Nov 21 '16 at 06:01
  • @Pranjal, sorry that was long time ago, I don't remember the details right now. I will try, and if I remember I will update the answer. Sorry! – Angel Villalain Jan 17 '17 at 09:07
0

Spring data has introduced JPA Entity Graph support. Beware that is does not currently work with graphs that are traversed via EmbeddedIds.

Ted Gulesserian
  • 277
  • 2
  • 8
  • When this question was asked, there was no support for JPA Entity Graph, well it was still in development to be precise. But now that this is available I think this question should consider this as a solution. – Angel Villalain Oct 21 '17 at 10:44