Search code examples
jpaspring-data-jpaquerydsl

Spring Data JPA and Querydsl to fetch subset of columns using bean/constructor projection


I have an entity class as below:

@Entity
public class UserDemo implements Serializable {

    @Id
    private Long id;

    private String username;

    private String createdBy;
    @Version
    private int version;

    /***
     *
     * Getters and setters
     */
}


Using Spring Data JPA and Querydsl how do I fetch a page of UserDemo with only id and username properties populated? I need to use paging as well as searching. In short I would like to achieve the same result as

Page<UserDemo> findAll(Predicate predicate, Pageable pageable);

but with limited field of UserDemo populated.


Solution

  • Looks like custom repository implementation is the way to go for now until something similar available in spring data.

    I have gone through http://static.springsource.org/spring-data/data-jpa/docs/current/reference/html/repositories.html#repositories.custom-implementations

    Here is my implementation which works. However it would be good to have this method available directly in Spring-Data-JPA

    Step 1: Intermediate interface for shared behavior

    public interface CustomQueryDslJpaRepository <T, ID extends Serializable>
            extends JpaRepository<T, ID>, QueryDslPredicateExecutor<T> {
        /**
         * Returns a {@link org.springframework.data.domain.Page} of entities matching the given {@link com.mysema.query.types.Predicate}.
         * This also uses provided projections ( can be JavaBean or constructor or anything supported by QueryDSL
         * @param constructorExpression this constructor expression will be used for transforming query results
         * @param predicate
         * @param pageable
         * @return
         */
        Page<T> findAll(FactoryExpression<T> factoryExpression, Predicate predicate, Pageable pageable);
    }
    

    Step 2: Implementation of intermediate interface

    public class CustomQueryDslJpaRepositoryImpl<T, ID extends Serializable> extends QueryDslJpaRepository<T, ID>
            implements CustomQueryDslJpaRepository<T, ID> {
    
        //All instance variables are available in super, but they are private
        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 CustomQueryDslJpaRepositoryImpl(JpaEntityInformation<T, ID> entityInformation, EntityManager entityManager) {
            this(entityInformation, entityManager, DEFAULT_ENTITY_PATH_RESOLVER);
        }
    
        public CustomQueryDslJpaRepositoryImpl(JpaEntityInformation<T, ID> entityInformation, EntityManager entityManager,
                                     EntityPathResolver resolver) {
    
            super(entityInformation, entityManager);
            this.path = resolver.createPath(entityInformation.getJavaType());
            this.builder = new PathBuilder<T>(path.getType(), path.getMetadata());
            this.querydsl = new Querydsl(entityManager, builder);
        }
    
        @Override
        public Page<T> findAll(FactoryExpression<T> factoryExpression, Predicate predicate, Pageable pageable) {
            JPQLQuery countQuery = createQuery(predicate);
            JPQLQuery query = querydsl.applyPagination(pageable, createQuery(predicate));
    
            Long total = countQuery.count();
            List<T> content = total > pageable.getOffset() ? query.list(factoryExpression) : Collections.<T> emptyList();
    
            return new PageImpl<T>(content, pageable, total);
        }
    }
    

    Step 3: Create a custom repository factory to replace the default

    public class CustomQueryDslJpaRepositoryFactoryBean<R extends JpaRepository<T, I>, T, I extends Serializable>
            extends JpaRepositoryFactoryBean<R, T, I> {
    
        protected RepositoryFactorySupport createRepositoryFactory(EntityManager entityManager) {
    
            return new CustomQueryDslJpaRepositoryFactory(entityManager);
        }
        private static class CustomQueryDslJpaRepositoryFactory<T, I extends Serializable> extends JpaRepositoryFactory {
    
            private EntityManager entityManager;
    
            public CustomQueryDslJpaRepositoryFactory(EntityManager entityManager) {
                super(entityManager);
                this.entityManager = entityManager;
            }
    
            protected Object getTargetRepository(RepositoryMetadata metadata) {
                return new CustomQueryDslJpaRepositoryImpl<>(getEntityInformation(metadata.getDomainType()), entityManager);
            }
    
            protected Class<?> getRepositoryBaseClass(RepositoryMetadata metadata) {
                return CustomQueryDslJpaRepository.class;
            }
        }
    }
    

    Step 4: Use the custom repository factory

    Using annotation

    @EnableJpaRepositories(repositoryFactoryBeanClass=CustomQueryDslJpaRepositoryFactoryBean.class)
    

    OR using XML

    <repositories base-package="com.acme.repository"  factory-class="com.acme.CustomQueryDslJpaRepositoryFactoryBean" />
    

    Note: Don't place custom repository interface and implementation in the same directory as base-package. If you are placing then exclude them from scanning otherwise spring will try to create beans for them

    Sample usage

    public interface UserDemoRepository extends CustomQueryDslJpaRepository<UserDemo, Long>{
    }
    
    public class UserDemoService {
        @Inject 
        UserDemoRepository userDemoRepository;
    
        public Page<User> findAll(UserSearchCriteria userSearchCriteria, Pageable pageable) {
            QUserDemo user = QUserDemo.userDemo;
            return userDemoRepository.findAll(Projections.bean(UserDemo.class, user.id, user.username), UserPredicate.defaultUserSearch(userSearchCriteria), pageable);
        }
    
    }