Репозитории Spring Data Mongo великолепны. Они позволяют разработчикам создавать 90% своих функций, используя возможности методов запросов. Но sиногда вы не сможете полагаться на сгенерированные методы запроса и вам потребуется разработать собственную реализацию. В этой статье вы узнаете, как создавать собственные репозитории Spring Data Mongo, в которых вы можете самостоятельно реализовать методы доступа к данным.

Прежде чем читать эту статью, я хочу уточнить некоторые ожидания. Я предполагаю, что у вас есть некоторый опыт работы с репозиториями Spring Data, так как в этом посте основное внимание будет уделено тому, как добавлять методы с пользовательской реализацией.

Организация аэропорта

Предположим, нам нужно хранить аэропорты в базе данных Mongo. Мы можем смоделировать наш аэропорт, как вы видите во фрагменте ниже.

@Document
public record Airport(String id,
                      String icaoCode,
                      @TextIndexed String name,
                      int nbRunways) {
}

Стандартный репозиторий Spring Mongo

Определить репозиторий Mongo очень просто. Вы можете создать новый интерфейс и расширить Repository‹T, ID› или интерфейс, который его расширяет, например CrudRepository‹T, ID›. Чтобы получить доступ к специфичным для Mongo функциям, вы можете расширить MongoRepository‹T, ID›. После этого вы можете добавить в интерфейс сигнатуры методов запроса, а Spring позаботится обо всем остальном.

public interface AirportRepository extends MongoRepository<Airport, String> {
    
   // Add query methods...
   List<Airport> findByName(String name);
}

Добавление методов с пользовательскими реализациями

Чтобы создать собственный репозиторий Spring Mongo, вы можете выполнить следующий процесс:

  1. Определите собственный интерфейс репозитория, содержащий методы, которые вы хотите реализовать самостоятельно.
  2. Реализовать пользовательский интерфейс репозитория
  3. Сделайте свой стандартный репозиторий сущностей расширяющим интерфейс пользовательского репозитория.

Давайте посмотрим на этот процесс в действии.

Определение пользовательского интерфейса репозитория

public interface CustomAirportRepository {
    // This is not a query method, we need to implement it
    List<Airport> findByFullTextSearch(String text);
}

Метод, определенный в этом интерфейсе, называется findByFullTextSearch, и не является методом запроса. Spring не знает, как сгенерировать для него реализацию прокси, поэтому мы должны предоставить ее сами.

Реализация пользовательского интерфейса репозитория

class CustomAirportRepositoryImpl implements CustomAirportRepository {
    private final MongoTemplate mongoTemplate;

    public CustomAirportRepositoryImpl(MongoTemplate mongoTemplate) {
        this.mongoTemplate= mongoTemplate;
    }

    @Override
    public List<Airport> findByTextSearch(String text) {
        var criteria = TextCriteria
                .forDefaultLanguage()
                .matchingPhrase(text);

        var query = TextQuery.queryText(criteria).sortByScore();

        return this.mongoTemplate.find(query, Airport.class);
    }
}

Я создал новый класс CustomAirportRepositoryImpl, который реализует наш пользовательский интерфейс. Этот класс использует экземпляр MongoTemplate для выполнения полнотекстового поиска на основе предоставленного текста.

Репозиторий сущностей расширяет интерфейс пользовательского репозитория

Осталась последняя вещь, которую нам нужно сделать, чтобы собрать все эти кусочки головоломки воедино. Нам также нужно сделать так, чтобы стандартный интерфейс объекта (AirportRepository) расширял наш пользовательский интерфейс.

// Standard interface also extends CustomAirportRepository
public interface AirportRepository extends 
       MongoRepository<Airport, String>,
       CustomAirportRepository {
    
   List<Airport> findByName(String name);
}

А то, что происходит дальше, просто волшебно. Spring будет использовать вашу пользовательскую реализацию для методов, объявленных в пользовательских интерфейсах, при создании прокси-методов.

// We still use the standard repository interface
private final AirportRepository repository;

// When we call findByTextSearch, the custom implementation is used
var result = repository.findByTextSearch("Heathrow");