Репозитории данных

Репозитории данных Spring Data предоставляют полезную абстракцию для работы с сущностями, особенно для реализации бизнес-логики.

Репозитории данных Jmix построены на базе Spring Data, но внутри используют DataManager. Это позволяет использовать удобный интерфейс репозиториев и при этом иметь полную поддержку продвинутых возможностей доступа к данным Jmix, таких как события сущностей, ссылки между сущностями из разных хранилищ, проверки доступа к данным и т.п.

В текущей версии 2.2 API репозиториев данных Jmix помечен как экспериментальный. В следующем функциональном релизе в него могут быть внесены незначительные изменения и API станет стабильным.

Работа с репозиториями данных

Вы можете создать репозиторий данных Jmix двумя способами: используя Мастер создания репозитория данных или следуя шагам, описанным ниже.

  1. Создайте интерфейс, унаследованный от JmixDataRepository. Используйте класс сущности и класс идентификатора сущности в качестве параметров типа JmixDataRepository. Например:

    public interface CustomerRepository extends JmixDataRepository<Customer, UUID> {
    }
  2. Добавьте аннотацию @EnableJmixDataRepositories главному классу приложения или классу конфигурации дополнения:

    import io.jmix.core.repository.EnableJmixDataRepositories;
    // ...
    
    @SpringBootApplication
    @EnableJmixDataRepositories
    public class DemoApplication implements AppShellConfigurator {

    Jmix проинициализирует все репозитории данных, расположенные внутри и ниже базового пакета приложения или дополнения. Если требуется более тонкая настройка поиска репозиториев, используйте атрибуты аннотации basePackages, excludeFilters и includeFilters.

  3. Инжектируйте репозиторий в бины Spring или UI-контроллеры с помощью аннотации @Autowired:

    @Autowired
    private CustomerRepository customerRepository;

Особенности JmixDataRepository

Интерфейс JmixDataRepository расширяет стандартный интейфейс PagingAndSortingRepository из Spring Data. Он предоставляет несколько собственных методов, учитывающих специфику Jmix:

  • Методы загрузки, такие как findById() или findAll(), могут принимать фетч-план.

  • Метод create() создает новый экземпляр сущности.

  • Метод getById() с обязательным результатом загружает сущность по идентификатору и выбрасывает исключение, если сущность не найдена.

  • Метод getDataManager() возвращает DataManager для использования в методах по умолчанию.

  • Метод save() сохраняет предоставленную сущность и возвращает сохраненный экземпляр, загруженный с указанным планом выборки. Метод принимает сущность для сохранения и план выборки, который будет применен при перезагрузке сохраненной сущности. Сущность не может быть null, а план выборки должен быть применим к сущности.

Вы можете передавать io.jmix.core.querycondition.Condition и другие специфичные для Jmix параметры в репозитории данных Jmix с помощью параметра JmixDataRepositoryContext. Этот параметр применим как к стандартным, так и к методам запросов репозиториев данных Jmix, позволяя динамически изменять их во время выполнения.

Вы можете применить аннотацию io.jmix.core.repository.ApplyConstraints к своему репозиторию данных. Если значение аннотации равно false, репозиторий использует UnconstrainedDataManager вместо DataManager. Значение аннотации по умолчанию - true.

Аннотация @ApplyConstraints может быть использована не только для всего класса, но и для отдельных методов, чтобы игнорировать или, наоборот, включить ограничения только для них.

public interface OrderRepository extends JmixDataRepository<Order, UUID> {
    @Override
    Iterable<Order> findAll(Sort sort, @Nullable FetchPlan fetchPlan);

    @Override
    @ApplyConstraints(false)
    Iterable<Order> findAll(FetchPlan fetchPlan);

    @ApplyConstraints(false)
    List<Order> findByIdNotNull();
}

В приведенном выше примере @ApplyConstraints(false) применяется только к двум методам, и для них будет использоваться UnconstrainedDataManager.

В приведенном ниже примере ограничения отключены для всего класса, но включены точечно для отдельных методов:

@ApplyConstraints(false)
public interface ProductRepository extends JmixDataRepository<Product, UUID> {
    @Override
    Iterable<Product> findAll(Sort sort, @Nullable FetchPlan fetchPlan);

    @Override
    @ApplyConstraints
    Page<Product> findAll(Pageable pageable);

    List<Product> getByIdNotNull();

    @ApplyConstraints
    List<Product> searchByIdNotNull();

    List<Product> searchById(UUID id);
}

Методы findAll() и searchByIdNotNull() будут использовать обычный DataManager, в то время как все остальные методы будут использовать UnconstrainedDataManager.

Следующие аннотации можно использовать на кастомных методах запросов:

Если имя метода/запроса и параметры метода имеют разные значения для плана выборки и хинтов, то окончательные значения выбираются на основе приоритета, от самого высокого к самому низкому.

FetchPlan:

  1. Параметр FetchPlan. План выборки, явно указанный в качестве параметра метода, имеет наивысший приоритет.

  2. Параметр JmixDataRepositoryContext#fetchPlan.

  3. Значение аннотации @FetchPlan.

Hints:

  1. Параметр JmixDataRepositoryContext.

  2. Значение аннотации @QueryHints.

Для хинтов с одинаковым ключом значение из источника с более высоким приоритетом переопределит значение из источника с более низким приоритетом. Разные ключи будут объединены.

Примеры методов запросов

Репозитории данных Jmix поддерживают стандартную возможность Spring Data выводить запрос из имени метода, например:

List<Customer> findByEmailContainingIgnoreCase(String emailPart);

Аналогично Spring Data JPA, JPQL-запрос можно явно задать с помощью аннотации @io.jmix.core.repository.Query:

@Query("select c from sample_Customer c where c.email like :email")
List<Customer> findCustomersByEmail(@Param("email") String emailPart);

Методы запросов могут принимать объект Pageable для постраничной загрузки и сортировки:

Page<Customer> findByEmailContainingIgnoreCase(String emailPart, Pageable pageable);

Другой специально обрабатываемый параметр, который можно использовать в методах запросов - фетч-план:

List<Customer> findByEmailContainingIgnoreCase(String emailPart, FetchPlan fetchPlan);

Разделяемый фетч-план можно задавать по имени в аннотации @io.jmix.core.repository.FetchPlan:

@FetchPlan("customer-minimal")
List<Customer> findByEmail(String email);

Кэшируемый запрос:

@QueryHints(@QueryHint(name = PersistenceHints.CACHEABLE, value = "true"))
List<Customer> findByEmail(String email);