Spring Data Composable Репозитории

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

1. Вступление

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

Если вы новичок в этой концепции, ознакомьтесь сthis introductory tutorial, чтобы быстрее освоиться.

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

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

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

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


    org.springframework.data
    spring-data-jpa
    2.0.9.RELEASE

Нам также нужно будет настроить источник данных, чтобы наш уровень доступа к данным работал. set up an in-memory database like H2 - это хорошая идея для разработки и быстрого тестирования.

3. Фон

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

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

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

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

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

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

public interface LocationRepository extends JpaRepository {
}

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

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

public interface StoreRepository extends JpaRepository {
    List findStoreByLocationId(Long locationId);
}

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

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

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

public interface ItemTypeRepository
  extends JpaRepository, 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:

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

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

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

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

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

Теперь, в Spring 5, у нас есть возможностьenrich our JPA repository with multiple fragment repositories. Опять же, остается требование, чтобы эти фрагменты были парами интерфейса-реализации.

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

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, CustomItemTypeRepository, CustomItemRepository {
}

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

5. Имея дело с неопределенностью

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

В этом сценарииthe order of the declaration of the interfaces is used to resolve the ambiguity. Следовательно, в нашем случае будет использоваться метод внутриCustomItemTypeRepository, поскольку он объявлен первым.

Мы можем проверить это с помощью этого теста:

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

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

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

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

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

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

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

Фрагменты кода из этой статьи доступны какMaven project here on Github.