javaspringspring-bootspring-mybatis

NoUniqueBeanDefinitionException thrown though there is only one implementation


I have a MyService class which has repository field supposed to be injected.

public class MyService {
    @Autowired
    private MyRepository repository;
// ...ommited
}

And there is MyRepository interface only implemented by MyRepositoryImpl class with @Mapper annotation of MyBatis.

public interface MyRepository {
// ...ommited
}

@Mapper
public interface MyRepositoryImpl extends MyRepository {
// ...ommited
}

When I try to start SpringBootApplication, NoUniqueBeanDefinitionException is thrown.

@SpringBootApplication(nameGenerator = FullyQualifiedAnnotationBeanNameGenerator.class)
@MapperScan(nameGenerator = FullyQualifiedAnnotationBeanNameGenerator.class)
public class App {
    public static void main(String[] args) {
        SpringApplication.run(App.class, args);
    }
}
(...omitted)
Caused by: org.springframework.beans.factory.UnsatisfiedDependencyException:
Error creating bean with name 'com.example.MyService':
Unsatisfied dependency expressed through field 'repository'; 
nested exception is org.springframework.beans.factory.NoUniqueBeanDefinitionException: 
No qualifying bean of type 'com.example.MyRepository' available: 
expected single matching bean but found 2: com.example.MyRepositoryImpl,com.example.MyRepository
(...omitted)

Why is MyRepository interface registered as one of bean even though it doesn't have @Component annotation nor isn't included any bean configurations?

And I found that everything work fine if I don't use FullyQualifiedAnnotationBeanNameGenerator as nameGenerator.

Any ideas?


Solution

  • There can be many other ways to mark an interface as a bean. Some of them are:

    1. @RepositoryDefinition above MyRepository interface
    2. MyRepository extends CrudRepository or JpaRepository
    Check if any of these exist.

    Update 1:-
    The problem seems to be in @MapperScan. What it does is scans for all the interfaces in a package and register them as bean; and if I am not wrong MyRepository and MyRepositoryImpl are in the same package. That's the reason why 2 beans are being created with names com.example.MyRepositoryImpl, com.example.MyRepository and basically both are of same type as MyRepositoryImpl extends MyRepository.
    Now when you are using @Autowired on repository field of MyService, it gets confused as in which one to inject. To resolve this the easiest approach is to use @Primary over MyRepositoy which will give that interface a priority while injecting. Another approach is to use @Qualifier("uniqueName") over both the interfaces and also above repository field of MyService along with @Autowired specifying which one you want to inject. You can visit official documentation from here to learn more.
    Hope this helps a bit .