Jan Goyvaerts
Jan Goyvaerts

Reputation: 3033

Can't autowire repositories created by JpaRepositoryFactoryBean

I'm trying to autowire repositories that are created by a repository factory bean. I need to override the save() method of certain repositories with custom code. I've followed the example for the factory bean at Spring Data's introduction.

But whatever I do, I'm getting NoSuchBeanDefinitionException.

On the other hand, 'interface-only repositories' are created and injected just fine.

Very likely I'm making a silly mistake somewhere. As this is supposed to be Spring Data 101.

Can somebody give me a clue of what else I might to try to make it work ?

The specs

Hibernate 4.1.9.Final

Spring 3.2.1.RELEASE

Spring Data 1.3.0.RELEASE

JDK 1.6.0_39.

Arch Linux x64

The repository factory

In case the entity is of type Bar I'm returning a different repository. Otherwise the default behavior is applied.

public class RepositoryFactoryBean<R extends JpaRepository<T, I>, T, I extends Serializable>  extends JpaRepositoryFactoryBean<R, T, I> {

  protected RepositoryFactorySupport createRepositoryFactory(EntityManager entityManager) {
    return new MyRepositoryFactory(entityManager);
  }

  private static class MyRepositoryFactory<T, I extends Serializable> extends JpaRepositoryFactory {

    private EntityManager entityManager;

    public MyRepositoryFactory(final EntityManager entityManager) {
      super(entityManager);
      this.entityManager = entityManager;
    }

    protected Object getTargetRepository(final RepositoryMetadata metadata) {
      if (metadata.getDomainType().equals(Bar.class)) {
        return new BarDaoImpl((Class<Bar>) metadata.getDomainType(), entityManager);
      }
      return super.getTargetRepository(metadata);
    }

    protected Class<?> getRepositoryBaseClass(RepositoryMetadata metadata) {
      if (metadata.getDomainType().equals(Bar.class)) {
        return BarDao.class;
      }
      return super.getRepositoryBaseClass(metadata);
    }
  }
}

The repository class and interface

package com.bar.persistence.dao;

@NoRepositoryBean
public interface BarDao extends JpaRepository<Bar, Long> {
  // no methods - because I want to override save()
}



package com.bar.persistence.dao;

public class BarDaoImpl extends SimpleJpaRepository<Bar, Long> implements BarDao {

  public BarDaoImpl(final Class<Bar> domainClass, final EntityManager em) {
    super(domainClass, em);
  }

  @Override
  public <S extends Bar> S save(final S entity) {
    // some code to perform before actually saving the entity
    entity.setName("intercepted save !");

    // do the save
    return super.save(entity);
  }
}

The test injecting the repository

The test fails because it can't find a bean of type BarDao to inject.

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration("/persistence-beans.xml")
@DirtiesContext(classMode = DirtiesContext.ClassMode.AFTER_CLASS)
@TransactionConfiguration
public class SpringDataLearningTest {

  @Autowired
  private BarDao barDao;

  @Transactional
  @Test
  public void testModifiedSave() throws Exception {
    final Bar original = createRandomBar();
    final Bar saved = barDao.save(original);
    assertNotNull("No id was assigned to the saved entity", saved.getId());
    assertEquals("intercepted save !", saved.getName());
  }

  private static Bar createRandomBar() {...}
}

Spring configuration

<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:context="http://www.springframework.org/schema/context"
       xmlns:tx="http://www.springframework.org/schema/tx"
       xmlns:task="http://www.springframework.org/schema/task"
       xmlns:util="http://www.springframework.org/schema/util" xmlns:aop="http://www.springframework.org/schema/aop"
       xmlns:data="http://www.springframework.org/schema/data/jpa"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.2.xsd
          http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-3.2.xsd
          http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-3.2.xsd
          http://www.springframework.org/schema/task http://www.springframework.org/schema/task/spring-task-3.2.xsd
          http://www.springframework.org/schema/util http://www.springframework.org/schema/util/spring-util-3.2.xsd
          http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-3.2.xsd
          http://www.springframework.org/schema/data/jpa http://www.springframework.org/schema/data/jpa/spring-jpa.xsd">

  <!-- Tell Spring Data where to find the interfaces -->
  <data:repositories base-package="com.bar.persistence.dao" factory-class="com.bar.persistence.dao.RepositoryFactoryBean"/>

  <!-- include the definitions of the 'upstream' modules -->
  <import resource="common-beans.xml"/>
  <import resource="io-beans.xml"/>

  <!-- The exception translator service-->
  <bean class="org.springframework.orm.hibernate4.HibernateExceptionTranslator"/>

  <bean id="jpaTemplate" class="org.springframework.orm.jpa.JpaTemplate">
    <property name="entityManagerFactory" ref="entityManagerFactory"/>
  </bean>

  <!-- Use the classic Spring JPA Transaction Manager -->
  <bean id="transactionManager" class="org.springframework.orm.jpa.JpaTransactionManager">
    <property name="entityManagerFactory" ref="entityManagerFactory"/>
  </bean>

  <!-- Describes how to connect to the database -->
  <bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource" destroy-method="close">
    <property name="user" value="${database.user.name}"/>
    <property name="password" value="${database.user.password}"/>
    <property name="driverClass" value="${database.driver}"/>
    <property name="jdbcUrl" value="${database.url}"/>
    <property name="initialPoolSize" value="1"/>
    <property name="maxPoolSize" value="${database.max.pool.size}"/>
    <property name="minPoolSize" value="${database.min.pool.size}"/>
    <property name="acquireIncrement" value="1"/>
    <property name="acquireRetryAttempts" value="3"/>
    <property name="checkoutTimeout" value="10000"/>
  </bean>

  <!-- Describes how to create hibernate sessions -->
  <bean id="entityManagerFactory" class="org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean">
    <property name="persistenceProviderClass" value="org.hibernate.ejb.HibernatePersistence"/>
    <property name="persistenceUnitName" value="bars"/>
    <property name="dataSource" ref="dataSource"/>
    <property name="jpaProperties">
      <props>
        <prop key="hibernate.cache.region.factory_class">${hibernate.cache.region.factory_class}</prop>
        <prop key="hibernate.cache.use_second_level_cache">${hibernate.second.level.cache}</prop>
        <prop key="hibernate.cache.use_query_cache">${hibernate.query.cache}</prop>
        <prop key="hibernate.generate_statistics">true</prop>
        <prop key="hibernate.hbm2ddl.auto">${hibernate.hbm2ddl.auto}</prop>
        <prop key="hibernate.jdbc.batch_size">${hibernate.jdbc.batch_size}</prop>
        <prop key="hibernate.show_sql">${database.show.sql}</prop>
        <prop key="hibernate.dialect">${database.hibernate.dialect}</prop>
        <prop key="hibernate.search.default.directory_provider">filesystem</prop>
        <prop key="hibernate.search.default.indexBase">${hibernate.search.dir}</prop>
      </props>
    </property>
  </bean>
</beans>

Stack trace

org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'com.bar.persistence.dao.SpringDataLearningTest': Injection of autowired dependencies failed; nested exception is org.springframework.beans.factory.BeanCreationException: Could not autowire field: private com.bar.persistence.dao.barDao com.bar.persistence.dao.SpringDataLearningTest.barDao; nested exception is org.springframework.beans.factory.NoSuchBeanDefinitionException: No qualifying bean of type [com.bar.persistence.dao.BarDao] 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.postProcessPropertyValues(AutowiredAnnotationBeanPostProcessor.java:288)
    at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.populateBean(AbstractAutowireCapableBeanFactory.java:1120)
    at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.autowireBeanProperties(AbstractAutowireCapableBeanFactory.java:379)
    at org.springframework.test.context.support.DependencyInjectionTestExecutionListener.injectDependencies(DependencyInjectionTestExecutionListener.java:110)
    at org.springframework.test.context.support.DependencyInjectionTestExecutionListener.prepareTestInstance(DependencyInjectionTestExecutionListener.java:75)
    at org.springframework.test.context.TestContextManager.prepareTestInstance(TestContextManager.java:313)
    at org.springframework.test.context.junit4.SpringJUnit4ClassRunner.createTest(SpringJUnit4ClassRunner.java:211)
    at org.springframework.test.context.junit4.SpringJUnit4ClassRunner$1.runReflectiveCall(SpringJUnit4ClassRunner.java:288)
    at org.junit.internal.runners.model.ReflectiveCallable.run(ReflectiveCallable.java:12)
    at org.springframework.test.context.junit4.SpringJUnit4ClassRunner.methodBlock(SpringJUnit4ClassRunner.java:284)
    at org.springframework.test.context.junit4.SpringJUnit4ClassRunner.runChild(SpringJUnit4ClassRunner.java:231)
    at org.springframework.test.context.junit4.SpringJUnit4ClassRunner.runChild(SpringJUnit4ClassRunner.java:88)
    at org.junit.runners.ParentRunner$3.run(ParentRunner.java:238)
    at org.junit.runners.ParentRunner$1.schedule(ParentRunner.java:63)
    at org.junit.runners.ParentRunner.runChildren(ParentRunner.java:236)
    at org.junit.runners.ParentRunner.access$000(ParentRunner.java:53)
    at org.junit.runners.ParentRunner$2.evaluate(ParentRunner.java:229)
    at org.springframework.test.context.junit4.statements.RunBeforeTestClassCallbacks.evaluate(RunBeforeTestClassCallbacks.java:61)
    at org.springframework.test.context.junit4.statements.RunAfterTestClassCallbacks.evaluate(RunAfterTestClassCallbacks.java:71)
    at org.junit.runners.ParentRunner.run(ParentRunner.java:309)
    at org.springframework.test.context.junit4.SpringJUnit4ClassRunner.run(SpringJUnit4ClassRunner.java:174)
    at org.junit.runner.JUnitCore.run(JUnitCore.java:160)
    at com.intellij.junit4.JUnit4IdeaTestRunner.startRunnerWithArgs(JUnit4IdeaTestRunner.java:76)
    at com.intellij.rt.execution.junit.JUnitStarter.prepareStreamsAndStart(JUnitStarter.java:195)
    at com.intellij.rt.execution.junit.JUnitStarter.main(JUnitStarter.java:63)
    at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
    at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:39)
    at com.intellij.rt.execution.application.AppMain.main(AppMain.java:120)
Caused by: org.springframework.beans.factory.BeanCreationException: Could not autowire field: private com.bar.persistence.dao.BarDao com.bar.persistence.dao.SpringDataLearningTest.barDao; nested exception is org.springframework.beans.factory.NoSuchBeanDefinitionException: No qualifying bean of type [com.bar.persistence.dao.BarDao] 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:514)
    at org.springframework.beans.factory.annotation.InjectionMetadata.inject(InjectionMetadata.java:87)
    at org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor.postProcessPropertyValues(AutowiredAnnotationBeanPostProcessor.java:285)
    ... 29 more
Caused by: org.springframework.beans.factory.NoSuchBeanDefinitionException: No qualifying bean of type [com.bar.persistence.dao.BarDao] 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.support.DefaultListableBeanFactory.raiseNoSuchBeanDefinitionException(DefaultListableBeanFactory.java:967)
    at org.springframework.beans.factory.support.DefaultListableBeanFactory.doResolveDependency(DefaultListableBeanFactory.java:837)
    at org.springframework.beans.factory.support.DefaultListableBeanFactory.resolveDependency(DefaultListableBeanFactory.java:749)
    at org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor$AutowiredFieldElement.inject(AutowiredAnnotationBeanPostProcessor.java:486)
    ... 31 more

Upvotes: 1

Views: 5199

Answers (1)

Michail Nikolaev
Michail Nikolaev

Reputation: 3775

Something like that:

  1. remove @NoRepositoryBean from BarDao
  2. add @NoRepositoryBean on BarDaoImpl
  3. move BarDaoImpl and BarDao to com.bar.persistence.dao.bar
  4. add new interface BarDaoMyRepository extends BarDao to com.bar.persistence.dao.repository
  5. set base-package="com.bar.persistence.dao.repository"
  6. inject BarDaoMyRepository

You using way to add some functionality to every repository which extends BarDao (so you need to extend it and may add some declarative methods). If you want add functionality to only one repository - see

http://static.springsource.org/spring-data/data-jpa/docs/current/reference/html/repositories.html#repositories.introduction 1.4.1 Adding behaviour to single repositories

Upvotes: 2

Related Questions