Перейти к основному содержимому

Компонуемые репозитории данных Spring

· 4 мин. чтения

1. Введение

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

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

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

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

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

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

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

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

3. Фон

3.1. Hibernate как реализация JPA

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

public class CustomItemTypeRepositoryImpl implements CustomItemTypeRepository {

@Autowired
private EntityManager entityManager;

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

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

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

или с помощью этой аннотации:

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

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

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

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

Теперь с Spring 5 у нас есть возможность обогатить наш репозиторий 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);
}

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

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

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

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

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

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

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

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

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

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

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

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