daniu
daniu

Reputation: 14999

@Primary JpaRepository not used for @Autowired

I have the simplest of Spring configuration cases so I don't know why I don't find how this is done.

I have an interface

public interface UserService {
    List<User> findAll();
}

with two implementations

@Component
public class SimpleUserService implements UserService {
    private List<User> users = Arrays.asList(new User());

    @Override
    public List<User> findAll() {
        return Collections.unmodifiableList(users);
    }
}

and

@Repository
@Primary
public interface UserRepository extends JpaRepository<User, Integer>, UserService {
}

Note the @Repository is marked @Primary, so when I do this:

@RestController
public class SimpleUserController implements UserController {
    @Autowired
    private UserService userService;

    @Override
    @GetMapping("/users")
    public List<User> getAllUsers() {
        return userService.userResources;
    }
}

it should be using the UserRepository, no? Instead, I get

Field userService in com.studying.user.SimpleUserController required a single bean, but 2 were found:

  • UserDaoService: defined in file [...\SimpleUserService.class]

  • userRepository: defined in null

I get that Spring creates an actual class to implement the repository, so I guess that could be the issue, but is there a way to still do this?

As an additional question, how would I create two configurations, with one using the simple service and the other the repository? It's not like I can instantiate the repository in a @Bean-annotated method in the Configuration. I'd add an @Autowired to a private member of the configuration and return it, but that won't work for the same reasons as above.

Upvotes: 1

Views: 919

Answers (1)

dunni
dunni

Reputation: 44535

For a Spring Data interface you need neither @Component nor @Repository. The repositories are detected by a different Spring Data mechanism, not the regular component scanning from Spring itself. That's also why your @Primary annotation doesn't have any effect.

A solution could look like this:

public interface UserService {}

@Component
public class SimpleUserService implements UserService {}

@Component
public class JpaUserService implements UserService {

    @Autowired
    UserRepository userRepository
}

public interface UserRepository extends JpaRepository<User, Integer> {}

Then you have a clear separation between your repository layer with the Spring Data repository and your service layer, which uses the repository layer. You also don't mix regular Spring bean annotations and Spring Data repository interfaces, which will also simplify your work.

You can then e.g. move the SimpleUserService into the test source folder, if it should only be available for unit tests, and mark it as @Primary. Or you can use the @Conditional annotation (https://docs.spring.io/spring-framework/docs/current/javadoc-api/org/springframework/context/annotation/Conditional.html) to provide an even more sophisticated way to decide, which implementation you want to use.

Upvotes: 3

Related Questions