Spring Data: Custom repository и составной репозиторий

Мы уже писали пользовательский репозиторий, в котором запросы работают через JdbcTemplate. Также мы рассматривали базовый CrudRepository.

В этой статье мы объединим оба репозитория в единый интерфейс. Это позволит внедрять два репозитория в другие места единым бином, а не двумя отдельными бинами. Благодаря Spring это возможно.

Начнем с написания пользовательского репозитория — то есть репозитория, содержащего наши пользовательские собственноручно написанные методы. Хотя он уже и написан, но теперь будут важны названия.

Custom repository

Пользовательский репозиторий должен реализовывать пользовательский интерфейс. Пусть наш интерфейс называется CustomAnimalRepository:

public interface CustomAnimalRepository {

    Animal getById(long id);
    Animal insert(Animal animal);
    Animal update(Animal animal);
    List<Animal> getAll();
}

Соглашение о наименовании

Тогда реализация интерфейса должна называться CustomAnimalRepositoryImpl: 

@AllArgsConstructor
public class CustomAnimalRepositoryImpl implements CustomAnimalRepository {

    private final NamedParameterJdbcTemplate jdbc;

    @Override
    @Transactional
    public Animal insert(Animal animal) {
        Map<String, Object> params = new HashMap<>();
        params.put("name", animal.getName());
        SqlParameterSource paramSource = new MapSqlParameterSource(params);

        GeneratedKeyHolder holder = new GeneratedKeyHolder();

        jdbc.update("insert into animal (name) values (:name)", paramSource, holder);
        animal.setId(holder.getKey().longValue());

        return animal;
    }
   //другие методы, смотрите на GitHub

}
Название класса, реализующего пользовательский репозиторий, должно иметь постфикс Impl.

Базовый репозиторий

Базовый репозиторий (расширяющий CrudRepository и декларирующий Query methods) выглядит так:

public interface AnimalRepository extends PagingAndSortingRepository<Animal, Long> {

    Animal findByName(String name);
    //другие декларации
}

Объединение репозиториев

Чтобы объединить его с пользовательским, заставим его расширять CustomAnimalRepository:

public interface AnimalRepository extends PagingAndSortingRepository<Animal, Long>, CustomAnimalRepository {

     Animal findByName(String name);
    //другие декларации
}

Теперь AnimalRepository можно внедрять в бины и использовать методы из обоих репозиториев.

Например, в тесте вызываются методы:

@DataJdbcTest
class AnimalRepositoryTest {
    @Autowired
    private AnimalRepository dao;

    // пользовательский метод
    @Test 
    void givenAnimal_whenInsert_thenReturnsAnimal() {
        Animal animal = dao.insert(new Animal("mouse"));
        Assertions.assertEquals("mouse", animal.getName());
    }
    //Query method
    @Test
    void givenName_whenFindBy_thenReturnsAnimal() {
        Animal animal = dao.findByName("cat");
        Assertions.assertEquals("cat", animal.getName());
    }
}

Как Spring выбирает метод интерфейса

Если методы конфликтуют (одинаковые названия и сигнатуры в разных местах), то в Spring в первую очередь выбирает:

  1. пользовательский метод,
  2. затем Query Method,
  3. и только если не найдено метода в предыдущих двух местах, берется метод из CrudRepository.

Под капотом для вызова метода Spring использует Spring’s ProxyFactory API и MethodInterceptor, подробнее на Stackoverflow.

Исходный код

Код примера есть на GitHub.

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *