Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Autowiring CrudRepository based on type parameter

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?

like image 843
Stefan Fischer Avatar asked Dec 06 '25 04:12

Stefan Fischer


1 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).

like image 192
M. Deinum Avatar answered Dec 08 '25 19:12

M. Deinum