Почему репозитории spring data jpa принудительно используют только интерфейсы?

#java #spring-boot #spring-data-jpa #spring-data

#java #spring-boot #spring-data-jpa #spring-data

Вопрос:

Я настраиваю многомодульный проект spring-boot, имеющий следующую структуру:

  • root
    • ядро (имеет класс TestingHandler, вводит TestingRepository для сохранения данных)
    • persistence-api (имеющий пользовательский репозиторий для тестирования интерфейса репозитория)
    • сохраняемость-память (наличие абстрактного класса MemoryTestingRepository, расширяющего как TestingRepository, так и JpaRepository)
    • main (имеющий точку входа приложения и runner, который вызывает TestingHandler для сохранения данных)

Я пытаюсь создать простое приложение spring boot, которое сохранит образец записи в своей базе данных h2 in-mem. но я продолжаю получать следующую ошибку:

 The injection point has the following annotations:
    - @org.springframework.beans.factory.annotation.Autowired(required=true)


Action:

Consider defining a bean of type 'com.****.***.services.testing.persistence.memory.MemoryTestingRepository' in your configuration.
  

Чего мне не хватает?

Я попробовал следующее:

  • Не используется иерархия репозиториев. Просто используя единый интерфейс, который расширяется JpaRepository , и он работает, но я должен иметь иерархию интерфейсов из-за пользовательских методов и должен поддерживать несколько реализаций db в будущем.
  • пробовал использовать MemoryRepository непосредственно в TestingHandler, не сработало. Если я преобразовываю MemoryRepository в interface (и избавляюсь от пользовательских методов), это работает. но, как я уже упоминал, мне нужно иметь пользовательский метод, который должен поддерживать иерархию интерфейсов для поддержки устаревшего кода.
  • пробовал много играть с аннотациями, такими как @EnableJpaRepositories, @ComponentScan и т. Д., Никакой помощи.
  • Testing.java это довольно стандартный POJO с аннотацией @Entity и @Id, очень простой.

Вот класс обработчика:

 package com.***.**.services.testing.core;

@Service
public class TestingHandler {

    @Autowired
    private TestingRepository testingRepository;

    @NotNull
    public Testing create(@NotNull final TestingDto testingDto) {
        Testing testing = new Testing("123", testingDto.getName(), testingDto.getDescription(), testingDto.Key(), testingDto.getVersion());
        testingRepository.create(testing);
        return testing;
    }
}
  

Обратите внимание, что он вводит репозиторий тестирования интерфейса.

Вот интерфейс TestingRepository (простой и понятный):

 package com.***.**.services.testing.persistence.api;

@Repository
public interface TestingRepository {
  void create();
  void findByCustomAttr();
}
  

Вот класс impl для TestingRepository:

 package com.***.**.services.testing.persistence.memory;

@Repository
public abstract class MemoryTestingRepository implements JpaRepository<Testing, Integer>, TestingRepository {
 @Override
    public void create(@NotNull Testing testing) {
        save(testing); //the real jpa method called.
    }

@Override
public void findByCustomAttr(){
//some impl....
}

}

  

наконец, основной класс выглядит так:

 package com.***.**.services.testing.main;
@SpringBootApplication(scanBasePackages = "com.***.**.services.testing")
@EnableJpaRepositories("com.***.**.services.testing.persistence")
@EntityScan(basePackages = {"com.***.**.services.testing.persistence"})
public class TestingApplication {

    public static void main(String[] args) {
        SpringApplication.run(TestingApplication.class, args);
    }

}
  

у меня также есть класс runner, который вызывает метод обработчика:

 package com.***.**.services.testing.main;

@Component
public class TestingService implements CommandLineRunner {

    @Autowired
    private TestingHandler testingHandler;

    @Override

    public void run(final String... args) throws Exception {
        test();
    }


    public void test() {
        testingHandler.create(new TestingDto("name", "desc", "some_mapping_id", Version.create()));
    }

}
  

Какие-либо указатели?

Ответ №1:

Ваш MemoryTestingRepository абстрактный, поэтому он не может быть создан Spring. Кроме того, интерфейсы репозиториев управляются Spring Data Jpa, и у вас не должно быть такой зависимости в вашем основном модуле.

Исходя из моего (я признаю, небольшого) опыта работы с многомодульными проектами, в настоящее время я создаю интерфейсы SAM в своем основном модуле для каждого запроса поставщика данных. Затем реализуйте эти интерфейсы в конкретном классе поставщика данных в моем модуле поставщика данных, вводя требуемый JpaRepository.

В вашем случае это означает:

Основной модуль:

 @Service
public class TestingHandler {

    @Autowired
    private TestingCreator testingCreator;

    @NotNull
    public Testing create(@NotNull final TestingDto testingDto) {
        Testing testing = ...;
        testingCreator.createTesting(testing);
        return testing;
    }

}
  
 public interface TestingCreator {

    void createTesting(Testing testing);

}
  

Модуль сохранения

 @Repository
public class TestingDataprovider implements TestingCreator /*, ...other interfaces from core module*/ {

    @Autowired
    private TestingRepository testingRepository;

    @Override
    public void createTesting(Testing testing) {
        testingRepository.save(testing);
    }

    // other methods like findByCustomAttr

}
  
 public interface TestingRepository extends JpaRepository<Testing, Integer> {
    // custom methods
}
  

Таким образом, вы также отделяете свои основные объекты от объектов сохранения.


Для справки, самое полезное руководство, которое я нашел, — это репозиторий github с отличным README с другими полезными ссылками.

Комментарии:

1. Спасибо за ваше предложение. Хотя это поможет добиться чистого разделения слоев. но это не решает проблему, которая у меня есть. в вашем случае мне нужно будет создать MemoryTestingRepository, который обеспечит реализацию методов, объявленных в TestingRepository . но если я сделаю MemoryTestingRepository классом, это заставит также реализовать методы JpaRepository, которые я не хочу. итак, единственный оставшийся у меня выбор — это абстрактный класс, который, «как вы сказали», не может быть создан. какой у меня есть другой выбор?

2. Дело в том, что у вас нет MemoryTestingRepository, а есть TestingDataprovider, который будет просто вызывать методы интерфейса TestingRepository (никакой логики, кроме преобразования / сопоставления объектов). Просто позвольте Spring обрабатывать реализацию репозитория.

3. @vikeshpandey если я не ошибаюсь, вы не собираетесь настраивать свой базовый репозиторий (добавлять или изменять обычную логику JpaRepository), а просто ссылаетесь на свой репозиторий из основного модуля. ИМХО, этот ответ предоставляет вам способ добиться этого без увеличения сложности модуля сохранения (или, по крайней мере, я нахожу это менее сложным, чем ответ Joao)

4. да, вы правы. я не хочу переопределять предоставленные JpaRepo impls. Проблема, с которой я сталкиваюсь, заключается в том, что я не могу представить поставщиков, поскольку мне нужно изменить архетип, за которым следует org. архетип упоминается в вопросе, где у нас есть persistence-api и persistence-memory, аналогично у нас будут persistence-postgres и persistence-mongo в будущем в качестве подмодулей для поддержки нескольких баз данных. любой способ добиться этого с помощью текущей структуры модуля, которая у меня есть?

5. я могу заставить это работать !!! :). Я только что создал dataprovider, специфичный для Memoryimpl, и ввел в него MemoryTestingRepository, и это сработало. В будущем у меня будут разные поставщики данных для разных реализаций базы данных. В основном я немного изменил ваше предложение. TestingCreator становится TestingRepository, а TestingRepository становится MemoryTestingRepository, и у меня есть провайдер, связывающий все это вместе. еще раз спасибо. я собираюсь отметить ваше предложение как ответ.

Ответ №2:

мне кажется, что ваш MemoryTestingRepository не только не реализует интерфейс TestingRepository, но и является абстрактным, что меня немного смущает. Это фактический код или вы допустили какую-то ошибку редактирования при публикации? Если это так, то ответ может заключаться в том, что у Spring могут возникнуть проблемы с созданием компонента из абстрактного класса.

Проверьте документацию https://docs.spring.io/spring-data/jpa/docs/current/reference/html/#repositories.single-repository-behavior поскольку в нем объясняется, как настроить пользовательские реализации репозитория

РЕДАКТИРОВАТЬ: на основе вашего комментария и обновления я теперь понимаю вашу проблему.

вы хотите, чтобы ваш конкретный репозиторий расширял SimplaJpaRepository, чтобы ваша реализация могла иметь доступ к методу сохранения JpaRepository.

посмотрите пример в https://docs.spring.io/spring-data/jpa/docs/current/reference/html/#repositories.customize-base-repository

ваш репозиторий будет выглядеть примерно так

 package com.***.**.services.testing.persistence.memory;

@Repository
public class MemoryTestingRepository extends SimpleJpaRepository<Testing, Integer> implements TestingRepository {
 @Override
    public void create(@NotNull Testing testing) {
        save(testing); //the real jpa method called.
    }

@Override
public void findByCustomAttr(){
//some impl....
}

}
  

Комментарии:

1. Да, это была ошибка редактирования. я исправил это в примере кода. Поскольку он расширяет JpaRepository и TestingRepository, он не может быть ни конкретным классом, ни интерфейсом (поскольку он не хочет переопределять JpaRepo.. метод, но хочет определить методы TestingRepo..)..

2. я попытался следовать предложенному вами подходу. Приведенный выше класс репозитория заставил бы иметь contstructor. когда я добавляю это как: ` public MemoryTestingRepositoryImpl(конечный класс <Тестирование> DomainClass, конечный EntityManager em) { super (DomainClass, em); }`, он не может автоматически подключать класс тестирования. Компонент тестирования типа не найден. сама IDE показывает эту ошибку. Testing.java однако он помечен символом @Entity.