QueryDsl SpringData Jpa findAll如何避免count()

时间:2016-05-16 12:53:20

标签: java spring spring-data spring-data-jpa querydsl

我试图将QueryDSL与Spring Data JPA一起使用,我希望findAll使用分页,但是如果返回类型是List,则始终执行计数。 我不需要这个计数,因为它非常慢,我可以放弃分页的好处。

针对此问题的任何解决方案?

这是count(),它在MySQL上需要大约30秒:

Mysql too slow on simple query between two tables

在任何情况下,我都不想重复我需要的每个页面的计数,这些信息仅用于第一次通话。

3 个答案:

答案 0 :(得分:7)

由于QueryDslPredicateExecutor不支持将Slice作为findAll(Predicate, Pageable)的返回值返回,因此计数查询似乎是不可避免的。但是,您可以定义新的基本存储库接口,并以不对分页发出计数查询的方式实现findAll方法。对于初学者,您应该定义一个接口,该接口将用作所有其他存储库的基础接口:

/**
 * Interface for adding one method to all repositories.
 *
 * <p>The main motivation of this interface is to provide a way
 * to paginate list of items without issuing a count query
 * beforehand. Basically we're going to get one element more
 * than requested and form a {@link Page} object out of it.</p>
 */
@NoRepositoryBean
public interface SliceableRepository<T, ID extends Serializable>
        extends JpaRepository<T, ID>,
        QueryDslPredicateExecutor<T> {

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

然后,实现这个界面,如:

public class SliceableRepositoryImpl<T, ID extends Serializable>
        extends QueryDslJpaRepository<T, ID>
        implements SliceableRepository<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 SliceableRepositoryImpl(JpaEntityInformation<T, ID> entityInformation, EntityManager entityManager) {
        super(entityInformation, entityManager);
        path = DEFAULT_ENTITY_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) {
        int oneMore = pageable.getPageSize() + 1;
        JPQLQuery query = createQuery(predicate)
                .offset(pageable.getOffset())
                .limit(oneMore);

        Sort sort = pageable.getSort();
        query = querydsl.applySorting(sort, query);

        List<T> entities = query.list(path);

        int size = entities.size();
        if (size > pageable.getPageSize())
            entities.remove(size - 1);

        return new PageImpl<>(entities, pageable, pageable.getOffset() + size);
    }
}

基本上,此实现将获取比请求大小多一个元素,并使用结果构造Page。然后,您应该告诉Spring Data将此实现用作存储库基类:

@EnableJpaRepositories(repositoryBaseClass = SliceableRepositoryImpl.class)

最后将SliceableRepository扩展为您的基本界面:

public SomeRepository extends SliceableRepository<Some, SomeID> {}

答案 1 :(得分:0)

仅供参考,有一个春季吉拉问题:

https://jira.spring.io/browse/DATAJPA-289

让我们为这项改进投票

答案 2 :(得分:0)

如果有人在这里寻找如何在 Spring Data MongoDB 中实现与 Ali 上面对 Spring Data JPA 所做的相同的影响,这里是我的解决方案:

import java.io.Serializable;
import java.util.List;

import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageImpl;
import org.springframework.data.domain.Pageable;
import org.springframework.data.domain.Slice;
import org.springframework.data.domain.Sort;
import org.springframework.data.domain.Sort.Order;
import org.springframework.data.mongodb.core.MongoOperations;
import org.springframework.data.mongodb.repository.query.MongoEntityInformation;
import org.springframework.data.mongodb.repository.support.QueryDslMongoRepository;
import org.springframework.data.mongodb.repository.support.SpringDataMongodbQuery;
import org.springframework.data.querydsl.EntityPathResolver;
import org.springframework.data.querydsl.QSort;
import org.springframework.data.querydsl.QueryDslPredicateExecutor;
import org.springframework.data.querydsl.SimpleEntityPathResolver;
import org.springframework.data.repository.core.EntityInformation;

import com.querydsl.core.types.EntityPath;
import com.querydsl.core.types.Expression;
import com.querydsl.core.types.OrderSpecifier;
import com.querydsl.core.types.Predicate;
import com.querydsl.core.types.dsl.PathBuilder;
import com.querydsl.mongodb.AbstractMongodbQuery;

/**
 * Custom extension of {@link QueryDslMongoRepository} that avoids unnecessary MongoDB "count"
 * operations
 * <p>
 * {@link QueryDslPredicateExecutor#findAll(Predicate, Pageable)} returns a {@link Page} at
 * potentially great expense because determining the {@link Page}'s "totalElements" property
 * requires doing a potentially expensive MongoDB "count" operation. We'd prefer a "findAll"-like
 * method that returns a {@link Slice} (which doesn't have a "totalElements" property) but no such
 * method exists. See {@link #findAll(Predicate, Pageable)} for more details.
 *
 * @see https://github.com/spring-projects/spring-data-commons/issues/1011
 * @see https://stackoverflow.com/questions/37254385/querydsl-springdata-jpa-findall-how-to-avoid-count
 */
public class MyQueryDslMongoRepository<T, ID extends Serializable> extends QueryDslMongoRepository<T, ID>
            implements MyAbstractRepository<T, ID> {
    private final PathBuilder<T> builder;
    private final EntityInformation<T, ID> entityInformation;
    private final MongoOperations mongoOperations;

    public BTQueryDslMongoRepository(MongoEntityInformation<T, ID> entityInformation, MongoOperations mongoOperations) {
        this(entityInformation, mongoOperations, SimpleEntityPathResolver.INSTANCE);
    }

    public BTQueryDslMongoRepository(MongoEntityInformation<T, ID> entityInformation, MongoOperations mongoOperations,
            EntityPathResolver resolver) {
        super(entityInformation, mongoOperations, resolver);
        EntityPath<T> path = resolver.createPath(entityInformation.getJavaType());
        this.builder = new PathBuilder<T>(path.getType(), path.getMetadata());
        this.entityInformation = entityInformation;
        this.mongoOperations = mongoOperations;
    }

    /**
     * An override of our superclass method to return a fake but cheaper-to-compute {@link Page}
     * that's adequate for our purposes.
     */
    @Override
    public Page<T> findAll(Predicate predicate, Pageable pageable) {
        int pageSize = pageable.getPageSize();
        SpringDataMongodbQuery<T> query = new SpringDataMongodbQuery<T>(mongoOperations, entityInformation.getJavaType())
                .where(predicate)
                .offset(pageable.getOffset())
                .limit(pageSize + 1);
        applySorting(query, pageable.getSort());

        List<T> entities = query.fetch();

        int numFetched = entities.size();
        if (numFetched > pageSize) {
            entities.remove(numFetched - 1);
        }

        return new PageImpl<T>(entities, pageable, pageable.getOffset() + numFetched);
    }

    /**
     * Applies the given {@link Sort} to the given {@link MongodbQuery}.
     * <p>
     * Copied from {@link QueryDslMongoRepository}
     */
    private AbstractMongodbQuery<T, SpringDataMongodbQuery<T>> applySorting(
            AbstractMongodbQuery<T, SpringDataMongodbQuery<T>> query, Sort sort) {

        if (sort == null) {
            return query;
        }

        // TODO: find better solution than instanceof check
        if (sort instanceof QSort) {

            List<OrderSpecifier<?>> orderSpecifiers = ((QSort) sort).getOrderSpecifiers();
            query.orderBy(orderSpecifiers.toArray(new OrderSpecifier<?>[orderSpecifiers.size()]));

            return query;
        }

        for (Order order : sort) {
            query.orderBy(toOrder(order));
        }

        return query;
    }
    /**
     * Transforms a plain {@link Order} into a QueryDsl specific {@link OrderSpecifier}.
     * <p>
     * Copied from {@link QueryDslMongoRepository}
     */
    @SuppressWarnings({ "rawtypes", "unchecked" })
    private OrderSpecifier<?> toOrder(Order order) {

        Expression<Object> property = builder.get(order.getProperty());

        return new OrderSpecifier(
                order.isAscending() ? com.querydsl.core.types.Order.ASC : com.querydsl.core.types.Order.DESC, property);
    }
}

@NoRepositoryBean
public interface MyAbstractRepository<T, ID extends Serializable> extends Repository<T, ID>,
        QueryDslPredicateExecutor<T> {

    @Override
    Page<T> findAll(Predicate predicate, Pageable pageable);
}

以上适用于 Spring Data MongoDB 1.10.23,但我认为可以进行修改以使其适用于更现代的版本。