我试图将QueryDSL与Spring Data JPA一起使用,我希望findAll
使用分页,但是如果返回类型是List
,则始终执行计数。
我不需要这个计数,因为它非常慢,我可以放弃分页的好处。
针对此问题的任何解决方案?
这是count()
,它在MySQL上需要大约30秒:
Mysql too slow on simple query between two tables
在任何情况下,我都不想重复我需要的每个页面的计数,这些信息仅用于第一次通话。
答案 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)
答案 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,但我认为可以进行修改以使其适用于更现代的版本。