QueryDsl SpringData Jpa findAll, как избежать подсчета ()

Я пытаюсь использовать QueryDSL с Spring Data JPA, я хочу использовать findAll с нумерацией страниц, но подсчет выполняется всегда, даже если тип возвращаемого значения - List. Мне не нужен этот счетчик, потому что он очень медленный, и я могу потерять преимущество нумерации страниц.

Любые решения для этой проблемы?

Это count(), для MySQL требуется около 30 секунд:

Mysql слишком медленный при простом запросе между двумя таблицами< /а>

В любом случае я не хочу повторять подсчет для каждой требуемой страницы, эта информация требуется только для первого вызова.


person Etantonio    schedule 16.05.2016    source источник
comment
Не могли бы вы поделиться с нами кодом? Запрос count обычно является частью разбивки на страницы, чтобы вы могли представить общее количество страниц/результатов в пользовательском интерфейсе. Почему в вашем случае часть count работает медленно?   -  person miensol    schedule 16.05.2016
comment
Та же проблема, у нас бесконечная прокрутка, поэтому нет необходимости в общем подсчете. Это пустой запрос   -  person geg    schedule 17.12.2019


Ответы (3)


Поскольку QueryDslPredicateExecutor не поддерживает возврат Slice в качестве возвращаемого значения findAll(Predicate, Pageable), поэтому Count Query кажется неизбежным. Но вы можете определить новый интерфейс базового репозитория и реализовать метод 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> {}
person Ali Dehghani    schedule 16.05.2016
comment
Спасибо за ваш ответ, он решает эту проблему, но в конце кажется, что всегда необходима какая-то оптимизация для querydsl + Spring Data JPA, я искал более стандартизированный подход. Спасибо еще раз - person Etantonio; 18.05.2016
comment
Благодарю. Просто помните, что QueryDslPredicateExecutor теперь является QuerydslPredicateExecutor в SpringBoot 2. - person rjdkolb; 10.05.2018
comment
как это сделать при использовании Mongo? - person syd; 28.02.2019

FYI есть весенняя проблема jira:

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

Давайте проголосуем за это улучшение

person MattWMJ    schedule 26.11.2018

На случай, если кто-то попадет сюда в поисках того, как добиться такого же эффекта в Spring Data MongoDB, как это сделал Али выше для 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, но я предполагаю, что его можно изменить, чтобы он работал для более современных версий.

person Nathaniel Mishkin    schedule 09.02.2021