Динамические запросы MongoRepository

У меня следующая проблема. Допустим, у меня есть следующий объект модели:

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

В карту свойств можно вставить любое свойство, ограничений нет.

Вышеупомянутый объект сохраняется в MongoDB, который выглядит следующим образом:

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

Когда человек сохраняется в хранилище, Map<String, String> properties сглаживается. Например, если у нас есть следующий объект:

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

документ, сохраненный в MongoRepository, будет следующим:

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

Теперь моя проблема в том, что я должен искать объекты на основе (например), есть ли у них определенное свойство или нет. Допустим, мне нужны все лица, для которых определено свойство возраста. Одно важное дополнительное требование состоит в том, что я должен возвращать постраничный результат.

Я пытался использовать

findAll(Example<Person> example, Pageable pageable)

Но это не работает по какой-то причине. Я подозреваю, что дело в том, что мой объект модели и документ MongoDB имеют разные структуры.

Я также пытался использовать QueryDsl (здесь у вас есть пример: http://www.baeldung.com/queries-in-spring-data-mongodb), но также безуспешно, а также для меня это решение не является элегантным (необходимо поддерживать сгенерированные классы и тому подобное. Также у меня есть ощущение, что это не сработает из-за моего члена объекта Map<String, String> properties).

Другое решение, которое пришло мне в голову и было бы достаточно элегантным, состоит в том, чтобы иметь следующую функцию:

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

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

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

Еще одним решением может быть использование mongoTemplate и запроса с учетом некоторого Criteria, как в следующем примере:

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

    mongoTemplate. find(query, Person.class);

Проблема с этим решением заключается в том, что оно возвращает список объектов вместо постраничного результата. Он также возвращает определенную страницу, если я добавляю query.with(new PageRequest(3, 2));, но в этом случае я не могу вручную построить результат "выгружаемого по страницам", потому что я не знаю общего количества элементов.

У вас есть другие идеи, которые могли бы мне помочь?

Заранее спасибо!


person Cristian Balint    schedule 24.05.2017    source источник
comment
Репозиторий Spring Mongo на самом деле дерьмо, мы использовали его некоторое время и никогда не были им довольны. Затем мы переключились на mongotemplate, и теперь он отлично работает, разбивка на страницы довольно проста, просто используйте лимит и пропускайте.   -  person Sercan Ozdemir    schedule 24.05.2017
comment
mongoTemplate может быть решением. Проблема в том, что результат должен быть доставлен через конечную точку REST, для которой нужны навигационные ссылки (таким образом, постраничный результат). К сожалению, mongoTemplate не поддерживает эту функциональность.   -  person Cristian Balint    schedule 24.05.2017
comment
Вы можете реализовать свою собственную разбивку на страницы, я напишу ответ через несколько минут.   -  person Sercan Ozdemir    schedule 24.05.2017


Ответы (3)


Bellow - это решение, которое я придумал. Итак, напомню, проблема, с которой я столкнулся, заключалась в том, что я не мог выполнить запрос с заданным объектом Query в качестве входных данных, чтобы повысить гибкость критериев фильтрации. Решение оказалось довольно простым, нужно было только внимательно прочитать документацию :).

  1. шаг

Расширяет MongoRepository и добавляет ваши пользовательские функции:

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

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

Создайте реализацию для этого интерфейса:

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. шаг

Установите свою реализацию в качестве реализации MongoRepository по умолчанию:

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

Создайте репозиторий для вашего пользовательского объекта:

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

Теперь, если у вас есть несколько объектов, которые вы хотите сохранить в хранилище документов mongo, достаточно определить интерфейс, который расширяет ваш ResourceRepository (как показано на шаге 4), и у вас будет доступен пользовательский метод запроса Page<T> findAll(Query query, Pageable pageable). Я нашел это решение самым элегантным из решений, которые я пробовал.

Если у вас есть предложения по улучшению, поделитесь ими с сообществом.

С уважением, Кристиан

person Cristian Balint    schedule 31.07.2017

Недавно столкнулся с чем-то подобным. Используя Clazz в качестве замены моего доменного класса, я сделал следующее:

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);

Я использую spring-boot-starter-data-mongodb:1.5.5, а Query + MongoTemplate обрабатывает пейджинг, вроде как. Итак, сначала я получил полный подсчет всех документов, соответствующих запросу. Затем я снова провел поиск, используя peageable, который снова пропустил и ограничил поиск (не очень эффективно, я знаю). Вернувшись к контроллеру, вы можете использовать PagedResourcesAssembler toResource(Page page) для того, чтобы связать это с гиперссылками подкачки.

 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);
}
person Hermann Steidel    schedule 27.07.2017

Поскольку репозитории spring mongo недостаточно зрелы, чтобы обрабатывать все пользовательские требования (это было, когда мы использовали его еще в 2016 году), я бы посоветовал вам использовать mongotemplate, который мы используем в настоящее время и довольны всеми требованиями. Вы можете реализовать собственную логику разбиения на страницы. Позволь мне объяснить.

В своих вызовах REST вы можете запросить page и pageSize. И тогда можно просто посчитать, что писать в значения limit и skip.

request.getPage() * request.getPageSize()

это даст вам значение пропуска, и

request.getPageSize()

даст вам предельное значение. Подробно ниже реализация может быть улучшена:

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;
    }

Надеюсь, поможет.

person Sercan Ozdemir    schedule 24.05.2017