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 в качестве входных данных, чтобы повысить гибкость по сравнению с критериями фильтрации. Решение оказалось довольно простым, мне просто нужно было внимательно прочитать документацию:).
- шаг
Расширяет MongoRepository и добавляет ваши пользовательские функции:
@NoRepositoryBean
public interface ResourceRepository<T, I extends Serializable> extends MongoRepository<T, I> {
Page<T> findAll(Query query, Pageable pageable);
}
- шаг
Создайте реализацию для этого интерфейса:
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())
);
}
}
- шаг
Установите вашу реализацию как реализацию MongoRepository по умолчанию:
@EnableMongoRepositories(repositoryBaseClass = ResourceRepositoryImpl.class)
public class MySpringApplication {
public static void main(final String[] args) {
SpringApplication.run(MySpringApplication.class, args);
}
}
- шаг
Создайте репозиторий для своего пользовательского объекта:
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;
}
Надеюсь, поможет.