简体   繁体   中英

Group by in Query DSL + Spring Data JPA throwing NoSuchElementException

I am trying to execute group by query with Spring Data JPA and Query DSL.

But, I am getting following exception:-

org.springframework.beans.factory.UnsatisfiedDependencyException: 
Error creating bean with name 'japanWHTDaoImpl': 
   Unsatisfied dependency expressed through field 'wht21940000DataRepo'; 
       nested exception is org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'japanWHT21940000DataRepository': 
    Invocation of init method failed; nested exception is java.util.NoSuchElementException

I tried to write custom repository implementation and giving below my interfaces and impl classes:

Custom interface:

public interface JapanWHT21940000DataRepositoryCustom {
        List<WHT21940000Royalties> findLocalCcyAmtsByRemarks();
}

Custom Impl class:

@Repository
@Transactional
public class JapanWHT21940000DataRepositoryCustomImpl extends QueryDslRepositorySupport implements JapanWHT21940000DataRepositoryCustom {

    public JapanWHT21940000DataRepositoryCustomImpl(Class<?> domainClass) {
    super(domainClass);
    // TODO Auto-generated constructor stub
    }

    @PersistenceContext
    private EntityManager entityManager;

    @Override
    public List<WHT21940000Royalties> findLocalCcyAmtsByRemarks() {
    QWHT21940000Data wht21940000Data = QWHT21940000Data.wHT21940000Data;
    JPAQuery<WHT21940000Royalties> query = new JPAQuery<WHT21940000Royalties>(entityManager);
    query.from(wht21940000Data).groupBy(wht21940000Data.remarks).select(wht21940000Data.remarks, wht21940000Data.localCcyAmt.sum());

    return null;
    }

}

Spring data JPA interface:

public interface JapanWHT21940000DataRepository
    extends JpaRepository<WHT21940000Data, Long>, 
        QueryDslPredicateExecutor<WHT21940000Data>, 
        JapanWHT21940000DataRepositoryCustom {

}

and in DAO class:

@Repository
@Transactional("japanWhtTransactionManager")
public class JapanWHTDaoImpl implements JapanWHTDao {
    @Autowired
    JapanWHT21940000DataRepository wht21940000DataRepo;
    // more code to follow...

EDIT: Or is there a simpler and better way to do group by query in Spring data JPA + Query DSL than what I am trying?

I think that the real issue is executing custom querydsl queries from JpaRepositories, though I have to suggest using a custom extended base repository class, as following.

First, comes the extended base repository class.

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

    <T1> Page<T1> findAll(JPQLQuery jpqlQuery, Pageable pageable);
}

And related implementation.

public class ExtendedQueryDslJpaRepositoryImpl<T, ID extends Serializable>
    extends QueryDslJpaRepository<T, ID> implements ExtendedQueryDslJpaRepository<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;

    private EntityManager entityManager;

    public ExtendedQueryDslJpaRepositoryImpl(JpaEntityInformation<T, ID> entityInformation, EntityManager entityManager) {
        this(entityInformation, entityManager, DEFAULT_ENTITY_PATH_RESOLVER);
    }

    public ExtendedQueryDslJpaRepositoryImpl(JpaEntityInformation<T, ID> entityInformation, EntityManager entityManager, EntityPathResolver
        resolver) {

        super(entityInformation, entityManager);
        this.path = resolver.createPath(entityInformation.getJavaType());
        this.builder = new PathBuilder(this.path.getType(), this.path.getMetadata());
        this.querydsl = new Querydsl(entityManager, this.builder);
        this.entityManager = entityManager;
    }

    @Override
    public <T1> Page<T1> findAll(JPQLQuery jpqlQuery, Pageable pageable) {

        final JPQLQuery<?> countQuery = jpqlQuery;

        JPQLQuery<T1> query = querydsl.applyPagination(pageable, jpqlQuery);

        return PageableExecutionUtils.getPage(query.fetch(), pageable, countQuery::fetchCount);
    }
}

The above classes could be placed in a configuration package.

Then, we define ExtendedQueryDslJpaRepositoryImpl as default class from which JpaRepository classes should extend, as following:

@Configuration
@EnableJpaRepositories(basePackageClasses = Application.class, repositoryBaseClass = ExtendedQueryDslJpaRepositoryImpl.class)
public class JpaConfig {

    @PersistenceContext
    private EntityManager entityManager;

    @Bean
    public JPAQueryFactory jpaQueryFactory() {
        return new JPAQueryFactory(entityManager);
    }
}

The next step is to define a repository for an Entity of the application, eg. CustomEntity .

public interface CustomRepository extends ExtendedQueryDslJpaRepository<CustomEntity, Long>, CustomRepositorySupport {
}

Next we define interface CustomRepositorySupport for the custom methods definition.

public interface CustomRepositorySupport {

    JPQLQuery<CustomListDto> createCustomIndexQuery();
}

And finally the custom repository implementation.

@Repository
public class CustomRepositoryImpl implements CustomRepositorySupport {

    private JPAQueryFactory queryFactory;

    @Autowired
    public CustomRepositoryImpl(JPAQueryFactory queryFactory) {
        this.queryFactory = queryFactory;
    }

    @Override
    public JPQLQuery<CustomListDto> createCustomIndexQuery() {

        QCustomEntity qCustomEntity = QCustomEntity.customEntity;

        BooleanBuilder predicate = new BooleanBuilder();

        // Create predicate as desired
        // predicate.and(...);

        // Create projection of fields
        /* FactoryExpression<CustomListDto> factoryExpression = Projections.bean(CustomListDto.class,
            qCustomEntity.fieldA,
            qCustomEntity.fieldB,
            qCustomEntity.fieldC,
            qCustomEntity.fieldD,
            qCustomEntity.fieldE,
            qCustomEntity.fieldF); */

        return queryFactory.from(qCustomEntity).select(factoryExpression).where(predicate);
    }
}

And the final step is to actually call the method from a Service class as following.

public interface CustomFinder {

    Page<CustomListDto> findIndex(Pageable pageable);
}

-

@Service
public class CustomFinderImpl implements CustomFinder {

    private CustomRepository customRepository;

    @Autowired
    public CustomFinderImpl(CustomRepository customRepository) {
        this.customRepository = customRepository;
    }

    @Override
    public Page<CustomListDto> findIndex(Pageable pageable) {

        JPQLQuery<CustomListDto> query = customRepository.createCustomIndexQuery();

        return customRepository.findAll(query, pageable);
    }
}

The same way we implemented public <T1> List<T1> findAll(JPQLQuery query, FactoryExpression<T1> factoryExpression) , we can simply implement required desirable methods as public <T1> List<T1> findAll(JPQLQuery query, FactoryExpression<T1> factoryExpression) , <T1> List<T1> findList(JPQLQuery query, FactoryExpression<T1> factoryExpression) etc.

This way, we add capabilities to all our repositories, in order to take advantage of all types of querydsl queries, not only group by or projections .

It might seem a little over-complicated, but if you choose to implement it, and see how it works in action, you would stick to it. It is being used successfully in real apps, providing access to projection queries (without loading full entities where they are not needed), paging queries, exists subqueries etc.

Finally, using a kind of central base repository class is a kind of reusable code which is written only once.

Hope that helps.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM