Spring Data: внедрить 2 репозитория с одинаковым именем, но в 2 разных пакетах

Контекст

Я хочу использовать в одном и том же контексте Spring две разные базы данных, в которых есть объекты с одинаковым именем, но с разной структурой. Я полагаюсь на Spring Data MongoDB и JPA/JDBC. У меня есть два пакета, содержащие среди прочего следующие файлы:

  • com.bar.entity
    • Car.class
  • com.bar.repository
    • CarRepository.class
    • RepoBarMarker.класс
  • com.bar.config
    • MongoConfiguration.class
  • com.foo.entity
    • Car.class
  • com.foo.repository
    • CarRepository.class
    • RepoFooMarker.класс
  • com.foo.config
    • JPAConfiguration.class
    • SpecEntityManagerFactory.class

Содержимое каждого класса Car.class отличается, я не могу использовать их повторно. bar использует Spring-Mongo, а foo использует Spring-JPA, а репозитории инициализируются с помощью аннотаций @EnableMongoRepositories и @EnableJpaRepositories. Когда в одном из компонентов моего приложения я пытаюсь получить доступ к версии репозитория foo:

@Resource
private com.foo.repository.CarRepository carRepository;

У меня есть следующее исключение, когда создается класс, содержащий поле @Resource:

Caused by: org.springframework.beans.factory.BeanNotOfRequiredTypeException: Bean named 'carRepository' must be of type [com.foo.repository.CarRepository], but was actually of type [com.sun.proxy.$Proxy31]
    at org.springframework.beans.factory.support.AbstractBeanFactory.doGetBean(AbstractBeanFactory.java:374)
    at org.springframework.beans.factory.support.AbstractBeanFactory.getBean(AbstractBeanFactory.java:198)
    at org.springframework.context.annotation.CommonAnnotationBeanPostProcessor.autowireResource(CommonAnnotationBeanPostProcessor.java:446)
    at org.springframework.context.annotation.CommonAnnotationBeanPostProcessor.getResource(CommonAnnotationBeanPostProcessor.java:420)
    at org.springframework.context.annotation.CommonAnnotationBeanPostProcessor$ResourceElement.getResourceToInject(CommonAnnotationBeanPostProcessor.java:545)
    at org.springframework.beans.factory.annotation.InjectionMetadata$InjectedElement.inject(InjectionMetadata.java:155)
    at org.springframework.beans.factory.annotation.InjectionMetadata.inject(InjectionMetadata.java:87)
    at org.springframework.context.annotation.CommonAnnotationBeanPostProcessor.postProcessPropertyValues(CommonAnnotationBeanPostProcessor.java:305)
    ... 26 more

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

Caused by: java.lang.IllegalStateException: Cannot convert value of type [com.sun.proxy.$Proxy31 implementing com.bar.repository.CarRepository,org.springframework.data.repository.Repository,org.springframework.aop.SpringProxy,org.springframework.aop.framework.Advised] to required type [com.foo.repository.CarRepository]: no matching editors or conversion strategy found
        at org.springframework.beans.TypeConverterDelegate.convertIfNecessary(TypeConverterDelegate.java:267)
        at org.springframework.beans.TypeConverterDelegate.convertIfNecessary(TypeConverterDelegate.java:93)
        at org.springframework.beans.TypeConverterSupport.doConvert(TypeConverterSupport.java:64)
        ... 35 more

Если я вместо этого попытаюсь автоматически подключить репозиторий:

@Autowire
private com.foo.repository.CarRepository carRepository;

Я получаю следующее исключение:

Caused by: org.springframework.beans.factory.BeanCreationException: Could not autowire field: private com.foo.CarRepository com.shell.ShellApp.carRepository; nested exception is org.springframework.beans.factory.NoSuchBeanDefinitionException: No qualifying bean of type [com.foo.CarRepository] found for dependency: expected at least 1 bean which qualifies as autowire candidate for this dependency. Dependency annotations: {@org.springframework.beans.factory.annotation.Autowired(required=true)}
    at org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor$AutowiredFieldElement.inject(AutowiredAnnotationBeanPostProcessor.java:509)
    at org.springframework.beans.factory.annotation.InjectionMetadata.inject(InjectionMetadata.java:87)
    at org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor.postProcessPropertyValues(AutowiredAnnotationBeanPostProcessor.java:290)
    ... 26 more

Конфигурация Spring-данных

В пакете foo (JPA) JPAConfigration.class:

@Configuration
@EnableJpaRepositories(basePackageClasses = RepoFooMarker.class)
public class JPAConfiguration {

    @Autowired
    public DataSource dataSource;

    @Autowired
    public EntityManagerFactory entityManagerFactory;

    @Bean
    public EntityManager entityManager(final EntityManagerFactory entityManagerFactory) {
        return entityManagerFactory.createEntityManager();
    }

    @Bean
    public Session session(final EntityManager entityManager)
    {
        return entityManager.unwrap(Session.class);
    }

    @Bean
    public PlatformTransactionManager transactionManager() throws SQLException {

        final JpaTransactionManager txManager = new JpaTransactionManager();
        txManager.setEntityManagerFactory(entityManagerFactory);
        return txManager;
    }

    @Bean
    public HibernateExceptionTranslator hibernateExceptionTranslator() {
        return new HibernateExceptionTranslator();
    }
}

SpecEntityManagerFactory.class:

@Configuration
public class SpecEntityManagerFactory {

    @Bean
    public EntityManagerFactory entityManagerFactory(final DataSource dataSource) throws SQLException {

        final HibernateJpaVendorAdapter vendorAdapter = new HibernateJpaVendorAdapter();
        vendorAdapter.setGenerateDdl(false);
        vendorAdapter.setDatabase(Database.POSTGRESQL);

        final LocalContainerEntityManagerFactoryBean factory = new LocalContainerEntityManagerFactoryBean();
        factory.setJpaVendorAdapter(vendorAdapter);
        factory.setPackagesToScan("com.foo.entity");
        factory.setJpaProperties(getHibernateProperties());
        factory.setDataSource(dataSource);
        factory.afterPropertiesSet();

        return factory.getObject();
    }

    private Properties getHibernateProperties()
    {
        final Properties hibernateProperties = new Properties();
        hibernateProperties.setProperty("hibernate.temp.use_jdbc_metadata_defaults", "false");

        return hibernateProperties;
    }

}

В пакете bar (MongoDB) MongoConfiguration.class:

@Configuration
@EnableMongoRepositories(basePackageClasses = RepoBarMarker.class)
public class MongoConfiguration extends AbstractRepoConfig {

    @Override
    @Bean
    public MongoOperations mongoTemplate() {

        final MongoClient mongo = this.getMongoClient();
        final MongoClientURI mongoUri = this.getMongoClientUri();

        final MongoTemplate mongoTemplate = new MongoTemplate(mongo, mongoUri.getDatabase());
        mongoTemplate.setReadPreference(ReadPreference.secondaryPreferred());
        mongoTemplate.setWriteConcern(WriteConcern.UNACKNOWLEDGED);

        return mongoTemplate;
    }
}

Вопрос

Если я изменю в репозитории foo имя объекта на CarFoo.class, а репозиторий на CarFooRepository.class, то все будет работать. Но есть ли возможность избежать их переименования и по-прежнему иметь реальную проводку для каждого типа вместо имени (как это, кажется, делается здесь) для репозиториев Spring Data?


person Nicolas    schedule 22.09.2014    source источник
comment
Не могли бы вы показать свою конфигурацию Spring Data?   -  person geoand    schedule 22.09.2014
comment
@geoand Обновлено с помощью конфигураций Spring Data. Моя настройка более сложная, я упростил ее, сохранив при этом основную часть конфигурации Spring Data.   -  person Nicolas    schedule 22.09.2014
comment
Сомневаюсь, что это сработает, но не могли бы вы попробовать добавить @Repository("fooCarRepository") к com.foo.repository.CarRepository?   -  person geoand    schedule 22.09.2014
comment
Также вы можете попробовать добавить @Qualifier("fooCarRepository") к com.foo.repository.CarRepository. Дайте мне знать, если это работает   -  person geoand    schedule 22.09.2014
comment
@geoand Добавление @Repository("fooCarRepository") в com.foo.repository.CarRepository работает! Я пробовал только вариант @Qualifier, который не работает (если вы также не укажете этот @Qualifier при внедрении компонента). Но @Repository("fooCarRepository") — хороший и простой обходной путь, спасибо! Однако это показывает, что в этом случае проводка выполняется не просто по типу: также влияют имена.   -  person Nicolas    schedule 22.09.2014
comment
Я добавлю решение @Repository в качестве ответа, чтобы будущие читатели могли его увидеть, не читая комментарии. Если это не слишком сложно, в их интересах было бы принять это, чтобы они могли сразу же узнать, что это правильно.   -  person geoand    schedule 22.09.2014
comment
Мне нужно было бы проверить источники Spring Data, чтобы увидеть, как он регистрирует Spring Bean, но я уверен, что он просто использовал имя интерфейса по умолчанию.   -  person geoand    schedule 22.09.2014
comment
Готово. Судя по тому, что я видел, кажется, что имя, которое он использует, - это просто имя интерфейса, да, что вызывает проблему. Спасибо еще раз!   -  person Nicolas    schedule 22.09.2014


Ответы (1)


В вашем случае вы можете использовать

@Repository("fooCarRepository")

в объявлении интерфейса

com.foo.repository.CarRepository

Хотя при использовании Spring Data @Repository обычно не требуется в интерфейсе, однако в вашем случае вам необходимо предоставить его. Это потому, что вам нужно заставить Spring зарегистрировать реализацию bean-компонента с произвольным именем (в данном случае fooCarRepository), чтобы избежать конфликта имен.

person geoand    schedule 22.09.2014
comment
Таким образом, Spring игнорирует пакет ?? - person Kees de Kooter; 09.01.2017
comment
@KeesdeKooter Я не уверен, что понимаю :( - person geoand; 10.01.2017
comment
@geond com.foo.repository.CarRepository - это класс, отличный от com.bar.repository.CarRepository, но, по-видимому, Spring использует только имя класса, а не имя пакета в качестве имени компонента по умолчанию. И не предупреждает о дубликатах. - person Kees de Kooter; 10.01.2017