Question

I am using spring-data for mongodb with querydsl. I have a repository

public interface DocumentRepository extends MongoRepository<Document, String> ,QueryDslPredicateExecutor<Document> {}

and an entity

@QueryEntity
public class Document {

private String id;
private String name;
private String description;
private boolean locked;
private String message;

}

I need to load a list of documents with id and name informations. So only id and name should be loaded and set in my entity. I think query projection is the right word for it. Is this supported?

In addition I need to implement some lazy loading logic. Is there anything like "skip" and "limit" features in a repository?

Was it helpful?

Solution 4

According to the Answer of this -> Question <- I implemeted following solution.

Entity

@QueryEntity
public class Document extends AbstractObject {
}

Custom QuerydslMongoRepository

public interface CustomQuerydslMongoRepository<T extends AbstractObject,ID extends Serializable> extends MongoRepository<T, ID> ,QueryDslPredicateExecutor<T>{
    Page<T> findAll(Predicate predicate, Pageable pageable,Path... paths);
    Page<T> findAll(Predicate predicate, Pageable pageable,List<Path> projections);

}

Custom QuerydslMongoRepository Implementation

public class CustomQuerydslMongoRepositoryImpl<T extends AbstractObject,ID extends Serializable> extends QueryDslMongoRepository<T,ID> implements CustomQuerydslMongoRepository<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> pathBuilder;
    private final MongoOperations mongoOperations;
    public CustomQuerydslMongoRepositoryImpl(MongoEntityInformation<T, ID> entityInformation, MongoOperations mongoOperations) {
        this(entityInformation, mongoOperations,DEFAULT_ENTITY_PATH_RESOLVER);
    }

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

    @Override
    public Page<T> findAll( Predicate predicate, Pageable pageable,Path... paths) {
        Class<T> domainType = getEntityInformation().getJavaType();
        MongodbQuery<T> query = new SpringDataMongodbQuery<T>(mongoOperations, domainType);
        long total = query.count();
        List<T> content = total > pageable.getOffset() ? query.where(predicate).list(paths) : Collections.<T>emptyList();
        return new PageImpl<T>(content, pageable, total);
    }

    @Override
    public Page<T> findAll(Predicate predicate, Pageable pageable, List<Path> projections) {
        Class<T> domainType = getEntityInformation().getJavaType();
        MongodbQuery<T> query = new SpringDataMongodbQuery<T>(mongoOperations, domainType);
        long total = query.count();
        List<T> content = total > pageable.getOffset() ? query.where(predicate).list(projections.toArray(new Path[0])) : Collections.<T>emptyList();
        return new PageImpl<T>(content, pageable, total);
    }
}

Custom Repository Factory

public class CustomQueryDslMongodbRepositoryFactoryBean<R extends QueryDslMongoRepository<T, I>, T, I extends Serializable> extends MongoRepositoryFactoryBean<R, T, I> {


    @Override
    protected RepositoryFactorySupport getFactoryInstance(MongoOperations operations) {
        return new CustomQueryDslMongodbRepositoryFactory<T,I>(operations);
    }

    public static class CustomQueryDslMongodbRepositoryFactory<T, I extends Serializable> extends MongoRepositoryFactory {
        private MongoOperations operations;

        public CustomQueryDslMongodbRepositoryFactory(MongoOperations mongoOperations) {
            super(mongoOperations);
            this.operations = mongoOperations;
        }


        @SuppressWarnings({ "rawtypes", "unchecked" })
        protected Object getTargetRepository(RepositoryMetadata metadata) {
                return new CustomQuerydslMongoRepositoryImpl(getEntityInformation(metadata.getDomainType()), operations);
          }

        protected Class<?> getRepositoryBaseClass(RepositoryMetadata metadata) {
            return CustomQuerydslMongoRepository.class;
        }
    }
}

Entity Repository

public interface DocumentRepository extends CustomQuerydslMongoRepository<Document, String>{

}

Usage in Service

@Autowired
DocumentRepository repository;

public List<Document> getAllDocumentsForListing(){
return repository.findAll(  QDocument.document.id.isNotEmpty().and(QDocument.document.version.isNotNull()), new PageRequest(0, 10),QDocument.document.name,QDocument.document.version).getContent();
}

OTHER TIPS

There's quite a few aspects to this, as it is - unfortunately - not a single question but multiple ones.

For the projection you can simply use the fields attribute of the @Query annotation:

interface DocumentRepository extends MongoRepository<Document, String>, QuerydslPredicateExecutor<Document> {

  @Query(value = "{}", fields = "{ 'id' : 1, 'name' : 1 }")
  List<Document> findDocumentsProjected();
}

You can combine this with the query derivation mechanism (by not setting query), with pagination (see below) and even a dedicated projection type in the return clause (e.g. a DocumentExcerpt with only id and name fields).

Pagination is fully supported on the repository abstraction. You already get findAll(Pageable) and a Querydsl specific version of the method by extending the base interfaces. You can also use the pagination API in finder methods adding a Pageable as parameter and returning a Page

Page<Document> findByDescriptionLike(String description, Pageable pageable)

See more on that in the reference documentation.

Projection

For all I know projections are not supported by the default Spring Data repositories. If you want to make sure only the projection is sent from the DB to your application (e.g. for performance reasons) you will have to implement the corresponding query yourself. Adding custom methods to extensions of the standard repo should not be too much effort.

If you just want to hide the content of certain fields from some client calling your application, you would typically use another set of entity objects with a suitable mapping in between. Using the same POJO for different levels of detail is always confusing as you will not know if a field is actually null or if the value was just suppressed in a certain context.

Pagination

I am currently not able to test any code, but according to the documentation of QueryDslPredicateExecutor the method findAll(predicate, pageable) should be what you want:

  • it returns a Page object that is a regular Iterable for your Document
  • you have to pass it a Pageable for which you can e.g. use a PageRequest; initializing it for known values of skip and limit should be trivial

I also found this approach for JPA

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

I am currently trying to implement this for MongoDB.

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top