#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.