MongoRepository dynamic queries

◇◆丶佛笑我妖孽 提交于 2019-12-10 23:32:43

问题


I have the following problem. Lets say I have the following model object:

class Person {
    String id;
    String firstName;
    String lastName;
    Map<String, String> properties;
}

In the properties map, you can insert any kind of property, there are no constrains.

The above object is saved in a MongoDB which looks like this:

public interface PersonRepo extends MongoRepository<Person, String> {
}

When a person is saved into the repository the Map<String, String> properties is flatten up. As an example, if we have the following object:

Person: {
    id := 1;
    firstName := John,
    lastName  := Doe,
    properties := {
        age: 42
    }
}

the document saved in the MongoRepository will be the following:

Person: {
    id := 1;
    firstName := John,
    lastName  := Doe,
    age := 42
}

Now my problem is that I have to look for objects based on (for example), if they have a specific property or not. Lets say I want all Persons for which an age property has been defined. One important additional requirement is that I should return a paged result.

I've tried using the

findAll(Example<Person> example, Pageable pageable)

But this does not work for some reason. I suspect that it's the fact that my model object and the MongoDB Document have different structures.

I've also tried with the QueryDsl (here you have an example: http://www.baeldung.com/queries-in-spring-data-mongodb) but with no success either, and also to me this solution is not to elegant (having to mantain generated classes and alike. Also I have a feeling it will not work because of my Map<String, String> properties object member).

Another solution that came to my mind and would be elegant enough, is to have the following function:

@Query(value = "?0")
Page<Query> findByQuery(String query, Pageable pageable)

In this case I would be able to manually construct the query and I wouldn't have to hardcode the key by which I run the search. My question now is, how can set the query value to be exactly my first parameter? With the example showned above I get the following error

java.lang.ClassCastException: java.lang.String cannot be cast to com.mongodb.DBObject

One other solution would be to use mongoTemplate and query given some Criteria as in the following example:

    final Query query = new Query();
    query.addCriteria(Criteria.where("age").regex(".*"));

    mongoTemplate. find(query, Person.class);

The problem with this solution is that it returns a list of objects instead for a paged result. It cal also return a specific page if I add query.with(new PageRequest(3, 2)); but in this case I cannot manually construct the "paged" result because I do not know the total number of elements.

Do you have any other ideas that could help me?

Thanks in advance!


回答1:


Bellow is the solution I've come up with. So just to recap, the problem that I had, was that I wan't able to execute a query given an Query object as input to have increased flexibility over the filtering criterias. The solution turned out to be quite simple, I just had to carefully read the documentation :).

  1. step

Extends MongoRepository and add your custom functions:

@NoRepositoryBean
public interface ResourceRepository<T, I extends Serializable> extends MongoRepository<T, I> {

    Page<T> findAll(Query query, Pageable pageable);
}
  1. step

Create an implementation for this interface:

public class ResourceRepositoryImpl<T, I extends Serializable> extends SimpleMongoRepository<T, I> implements ResourceRepository<T, I> {

    private MongoOperations mongoOperations;
    private MongoEntityInformation entityInformation;

    public ResourceRepositoryImpl(final MongoEntityInformation entityInformation, final MongoOperations mongoOperations) {
        super(entityInformation, mongoOperations);

        this.entityInformation = entityInformation;
        this.mongoOperations = mongoOperations;
    }

    @Override
    public Page<T> findAll(final Query query, final Pageable pageable) {
        Assert.notNull(query, "Query must not be null!");

        return new PageImpl<T>(
                mongoOperations.find(query.with(pageable), entityInformation.getJavaType(), entityInformation.getCollectionName()),
                pageable,
                mongoOperations.count(query, entityInformation.getJavaType(), entityInformation.getCollectionName())
        );
    }
}
  1. step

Set your implementation as the default MongoRepository implementation:

@EnableMongoRepositories(repositoryBaseClass = ResourceRepositoryImpl.class)
public class MySpringApplication {
    public static void main(final String[] args) {
        SpringApplication.run(MySpringApplication.class, args);
    }
}
  1. step

Create a repository for your custom object:

public interface CustomObjectRepo extends ResourceRepository<CustomObject, String> {
}

Now if you have multiple objects which you want to save in a mongo document store, it is enough to define an interface which extends your ResourceRepository (as seen in step 4), and you will have available the Page<T> findAll(Query query, Pageable pageable) custom query method. I have found this solution to be the most elegant of the solution I've tried.

If you have any suggestions for improvements, please share them with the community.

Regards, Cristian




回答2:


I faced something similar recently. Using Clazz as a substitute for my domain class, here is what I did:

long count = mongoTemplate.count(query, clazz.class);
query.with(pageable);
List<Clazz> found = mongoTemplate.find(query, clazz.class);
Page<Clazz> page = new PageImpl<T>(found, pageable, count);

I'm using spring-boot-starter-data-mongodb:1.5.5 and Query + MongoTemplate handles paging, sort of. So, first I got the full count of all documents that matched the query. Then I did the search again using the peageable which searches again skipped and limited (not very efficient, I know). Back at the Controller, you can use the PagedResourcesAssembler's toResource(Page page) method to get that wrapped up with the paging Hyperlinks.

 public ResponseEntity search(@Valid @RequestBody MyParams myParams, Pageable pageable, PagedResourcesAssembler assembler){
    Page<Clazz> page= searchService.search(myParams, pageable, Clazz.class);
    PagedResources<Clazz> resources = assembler.toResource(page);
    return new ResponseEntity<>(resources, HttpStatus.OK);
}



回答3:


As spring mongo repositories are not mature enough to handle all custom requirements (it was when we were using it back at 2016) I would suggest you to use mongotemplate which we're currently using and happy with all requirements. You can implement your own pagination logic. Let me explain.

In your REST calls you may request page and pageSize. And then you can simply calculate what to write to limit and skip values.

request.getPage() * request.getPageSize()

this will give you the skip value, and

request.getPageSize()

will give you the limit value. In detail, below implementation can be improved more:

Integer limit = request.getPageItemCount() == null ? 0 : request.getPageItemCount();
Integer skip = getSkipValue( request.getPage(), request.getPageItemCount() );


 private Integer getSkipValue( Integer page, Integer pageItemCount ){
        if( page == null || pageItemCount == null ){ return 0; }
        int skipValue = ( page - 1 ) * pageItemCount;
        if( skipValue < 0 ){ return 0; }
        return skipValue;
    }

Hope it helps.



来源:https://stackoverflow.com/questions/44151977/mongorepository-dynamic-queries

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!