1. Введение

При моделировании реальной системы или процесса репозитории в стиле доменно-ориентированного проектирования (DDD) являются хорошим вариантом. Именно для этой цели мы можем использовать Spring Data JPA в качестве уровня абстракции доступа к данным.

Если вы плохо знакомы с этой концепцией, ознакомьтесь с этим вводным руководством, которое поможет вам освоиться.

В этом уроке мы сосредоточимся на концепции создания пользовательских, а также компонуемых репозиториев, которые создаются с использованием меньших репозиториев, называемых фрагментами.

2. Зависимости Maven

Возможность создания компонуемых репозиториев доступна, начиная с Spring 5.

Давайте добавим необходимую зависимость для Spring Data JPA:

Нам также нужно настроить источник данных, чтобы наш уровень доступа к данным работал. Рекомендуется настроить базу данных в памяти, такую ​​​​как H2, для разработки и быстрого тестирования.

<dependency>
    <groupId>org.springframework.data</groupId>
    <artifactId>spring-data-jpa</artifactId>
    <version>2.2.2.RELEASE</version>
</dependency>

3. Предыстория

3.1. Спящий режим как реализация JPA

Spring Data JPA по умолчанию использует Hibernate как реализацию JPA. Мы можем легко спутать одно с другим или сравнить их, но они служат разным целям.

Spring Data JPA — это уровень абстракции доступа к данным, ниже которого мы можем использовать любую реализацию. Например, мы могли бы заменить Hibernate на EclipseLink.

3.2. Репозитории по умолчанию

Во многих случаях нам не нужно самим писать какие-либо запросы.

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

И это само по себе позволит нам выполнять стандартные операции — CRUD, пейджинг и сортировку. — на объекте Location с первичным ключом типа Long.

public interface LocationRepository extends JpaRepository<Location, Long> {
}

Кроме того, Spring Data JPA оснащен механизмом построения запросов, который позволяет генерировать запросы от нашего имени, используя соглашения об именах методов:

3.3. Пользовательские репозитории

public interface StoreRepository extends JpaRepository<Store, Long> {
    List<Store> findStoreByLocationId(Long locationId);
}

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

Например, здесь мы обогащаем наш ItemTypeRepository, расширяя репозиторий фрагментов:

Здесь CustomItemTypeRepository — это еще один интерфейс:

public interface ItemTypeRepository 
  extends JpaRepository<ItemType, Long>, CustomItemTypeRepository {
}

Его реализация может быть репозиторием любого типа, а не только JPA:

public interface CustomItemTypeRepository {
    void deleteCustomById(ItemType entity);
}

Нам просто нужно убедиться, что он имеет постфикс Impl. Однако мы можем установить пользовательский постфикс, используя следующую конфигурацию XML:

public class CustomItemTypeRepositoryImpl implements CustomItemTypeRepository {
 
    @Autowired
    private EntityManager entityManager;

    @Override
    public void deleteCustomById(ItemType itemType) {
        entityManager.remove(itemType);
    }
}

или используя эту аннотацию:

<repositories base-package="com.baeldung.repository" repository-impl-postfix="CustomImpl" />

4. Составление репозиториев с использованием нескольких фрагментов

@EnableJpaRepositories(
  basePackages = "com.baeldung.repository", repositoryImplementationPostfix = "CustomImpl")

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

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

Теперь с Spring 5 у нас есть возможность обогатить наш репозиторий JPA несколькими репозиториями фрагментов. Опять же, остается требование, чтобы эти фрагменты были парами интерфейс-реализация.

Чтобы продемонстрировать это, давайте создадим два фрагмента:

Конечно, нам нужно написать их реализации. Но вместо того, чтобы подключать эти пользовательские репозитории со связанными функциями к их собственным репозиториям JPA, мы можем расширить функциональность одного репозитория JPA:

public interface CustomItemTypeRepository {
    void deleteCustom(ItemType entity);
    void findThenDelete(Long id);
}

public interface CustomItemRepository {
    Item findItemById(Long id);
    void deleteCustom(Item entity);
    void findThenDelete(Long id);
}

Теперь у нас будет вся связанная функциональность в один единый репозиторий.

public interface ItemTypeRepository 
  extends JpaRepository<ItemType, Long>, CustomItemTypeRepository, CustomItemRepository {
}

5. Работа с двусмысленностью

Поскольку мы наследуем от нескольких репозиториев, у нас могут возникнуть проблемы с определением того, какая из наших реализаций будет использоваться в случае конфликта. Например, в нашем примере оба репозитория фрагментов имеют метод findThenDelete() с одинаковой сигнатурой.

В этом сценарии порядок объявления интерфейсов используется для устранения неоднозначности. Следовательно, в нашем случае будет использоваться метод внутри CustomItemTypeRepository, поскольку он объявлен первым.

Мы можем проверить это, используя этот тестовый пример:

6. Заключение

@Test
public void givenItemAndItemTypeWhenDeleteThenItemTypeDeleted() {
    Optional<ItemType> itemType = composedRepository.findById(1L);
    assertTrue(itemType.isPresent());

    Item item = composedRepository.findItemById(2L);
    assertNotNull(item);

    composedRepository.findThenDelete(1L);
    Optional<ItemType> sameItemType = composedRepository.findById(1L);
    assertFalse(sameItemType.isPresent());

    Item sameItem = composedRepository.findItemById(2L);
    assertNotNull(sameItem);
}

«В этой статье мы рассмотрели различные способы использования репозиториев Spring Data JPA. Мы увидели, что Spring упрощает выполнение операций с базой данных над объектами предметной области без написания большого количества кода или даже SQL-запросов.

Эта поддержка значительно настраивается за счет использования компонуемых репозиториев.

Фрагменты кода из этой статьи доступны в виде проекта Maven здесь, на GitHub.

«