Autowiring CrudRepository based on type parameter

Viewed 337

I'd like to autowire a CrudRespository<Type,Key> in an abstract parent class, then use it with the child classes. Error tells me:

java.lang.IllegalStateException: Failed to load ApplicationContext
[...]
Caused by: org.springframework.beans.factory.UnsatisfiedDependencyException: Error creating bean with name 'accountExtractor': Unsatisfied dependency expressed through field 'repository'; nested exception is org.springframework.beans.factory.NoSuchBeanDefinitionException: No qualifying bean of type 'org.springframework.data.repository.CrudRepository<com.finnwa.adwords.adconnect.Account, java.lang.Long>' available: expected at least 1 bean which qualifies as autowire candidate. Dependency annotations: {@org.springframework.beans.factory.annotation.Autowired(required=true)}

Abstract parent defining the dependency.

@Component
public abstract class Extractor<T,KEY> {

    @Autowired
    protected CrudRepository<T,KEY> repository;
   // some business logic
}

Child class providing the parameters.

@Component
class AccountExtractor extends Extractor<Account, Long>{
    // some config
}

Other classes that might be relevant:

public interface AccountRepository extends CrudRepository<Account, Long>{}

@SpringBootApplication
@EnableJpaRepositories(basePackages = "package.my")
public class Application {
    public static void main(String[] args) {
         SpringApplication.run(Application.class);
    }
}

I learned from other questions that the dependency in the parent class may not be private. So I made it protected. Anything I'm missing?

EDIT: So Paul Janssens and M. Deinum have posted some nice workarounds. But why doesn't this work? What is going wrong here?

3 Answers

For starters I suggest not to use field injection, rather use constructor injection. I would also suggest to use the specific type, as with generics the change is that the type information is removed.

I would probably do something like

public abstract class Extractor<T,KEY> {

  private final CrudRepository<T,KEY> repository;

  protected Extractor(CrudRepository<T, KEY> repository) {
    this.repository=repository;
  }
}

Then in your specific class use the AccountRepository and pass it to the super class.

@Component
class AccountExtractor extends Extractor<Account, Long>{

  AccountExtractor(AccountRepository repository) {
    super(repository);
  }
}

This way in your super class and methods you still can use the base type CrudRepository. The added advantage is also that you can now quite easily write a unit test for the AcountExtractor and simply mock the AccountRepository without you having to bootstrap a Spring Boot application. (I know you could use @DataJpaTest but none the less a simple mock is faster).

just use an abstract method and perform the wiring in the subclass

public abstract CrudRepository<T,KEY> getRepository();

...

FooRepository implements CrudRepository<Foo,Integer>

...

@Autowired
protected FooRepository repository;

@Override
public CrudRepository<Foo,Integer> getRepository() {
    return repository;
}

I would say that AccountRepository lacks the "magic annotation":

@Repository // This tells Spring Data to actually create a "standard" repository instance for us
public interface AccountRepository extends CrudRepository<Account, Long>{}
Related