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)); но в этом случае я не могу вручную построить "постраничный" результат, потому что я не знаю общее количество элементов.

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

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

4 ответа

Решение

Сильфон - это решение, которое я придумал. Напомню, что проблема, с которой я столкнулся, заключалась в том, что я не смог выполнить запрос с заданным объектом 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> {
}

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

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

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

Есть действительно самый простой способ сделать это, похожий на ваш код:

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

Но передача вашего запроса как BSONObject вместо String

      @Query("?0")
Page<Query> findByQuery(BSONObject query, Pageable pageable)

Я столкнулся с чем-то похожим недавно. Используя 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, который снова пропускает и ограничивает поиск (я знаю, что это не очень эффективно). Вернувшись к контроллеру, вы можете использовать метод toResource (страница страницы) PagedResourcesAssembler, чтобы обернуть его гиперссылками подкачки.

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

Поскольку репозитории 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;
    }

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

Другие вопросы по тегам