Spring Boot unable to scan MongoRepository

Viewed 78

I have a persistance interface that implements MongoRepository interface that looks as follows:

package org.prithvidiamond1.DB.Repositories;

import org.prithvidiamond1.DB.Models.SomeModel;
import org.springframework.data.mongodb.repository.MongoRepository;
import org.springframework.stereotype.Repository;

@Repository
public interface ServerRepository extends MongoRepository<SomeModel, String> {
}

Despite this, I keep getting the following error:

Exception encountered during context initialization - cancelling refresh attempt:
org.springframework.beans.factory.UnsatisfiedDependencyException: Error creating bean with name 'botApplication': 
Unsatisfied dependency expressed through constructor parameter 0; nested exception is org.springframework.beans.factory.NoSuchBeanDefinitionException: 
No qualifying bean of type 'org.prithvidiamond1.DB.Repositories.ServerRepository' available: expected at least 1 bean which qualifies as autowire candidate.
Dependency annotations: {}


PPLICATION FAILED TO START
***************************

Description:

Parameter 0 of constructor in org.prithvidiamond1.BotApplication required a bean of type 'org.prithvidiamond1.DB.Repositories.ServerRepository' that could not be found.


Action:

Consider defining a bean of type 'org.prithvidiamond1.DB.Repositories.ServerRepository' in your configuration.

I have tried many solutions (custom @ComponentScan, using @Service instead of @Component, etc.) I found on the internet but none could help me solve the problem, can someone explain to me what is wrong and how I should fix this?

Note: The directory structure is as follows (this is not the full directory structure, but I think this should be enough to get an idea):

org.prithvidiamond1
   |
   +--BotApplication.java
   |
   +--DB
      |
      +--Repository
      |
      +--ServerRepository.java

BotApplication.java looks as follows:

package org.prithvidiamond1;

import org.jc.api.Api;
import org.jc.api.ApiBuilder;
import org.jc.api.entity.server.Server;
import org.prithvidiamond1.DB.Models.Server;
import org.prithvidiamond1.DB.Repositories.ServerRepository;
import org.slf4j.Logger;
import org.springframework.context.annotation.Bean;
import org.springframework.stereotype.Component;

import java.util.Collection;

@Component
public class BotApplication {
    private final Api api;
    private final ServerRepository serverRepository;

    public BotApplication(ServerRepository serverRepository, Logger logger){
        String botToken = System.getenv().get("BOT_TOKEN");

        this.api = new ApiBuilder();

        this.serverRepository = serverRepository;

        appRuntime(logger);
    }

    public void appRuntime(Logger logger){
        logger.info("Bot has started!");

        // Handling server entries in the database
        if (this.serverRepository.findAll().isEmpty()) {
            logger.trace("server data repository empty, initializing data repository...");
            Collection<Server> servers = api.getServers();
            for (Server server : servers) {
                this.serverRepository.save(new Server(String.valueOf(server.getId())));
            }
            logger.trace("server data repository initialized");
        }
    }

    @Bean
    public Api getApi() {
        return this.api;
    }
}

Edit: Here is a link to a repository with all the code: https://github.com/prithvidiamond1/DiamondBot/tree/springboot-restructure

4 Answers

The problem is that you are using the primary source class(BotApplication.class) for executing custom code during start-up.

    public static void main(String[] args) {
        ApplicationContext AppContext = SpringApplication.run(BotApplication.class, args);
    }

    public BotApplication(DiscordServerRepository serverRepository, Logger logger){
        ...
        appRuntime(logger);
    }

It is not a good place for using repositories or any startup code.
In the SpringApplication.run method you need to pass only classes which provide beans definitions. These are @Configuration classes.
See the best practices Running code after Spring Boot starts

I describe one of the best solutions: ApplicationRunner

  1. BotApplication class must implement ApplicationRunner interface. The interface is used to indicate that a bean should run when it is contained within a SpringApplication.
    Execute all startup code at the run method.
@Component
public class BotApplication implements ApplicationRunner {
    private final DiscordApi api;
    private final DiscordServerRepository serverRepository;

    private final Logger logger;

    public BotApplication(DiscordServerRepository serverRepository, Logger logger){
        String botToken = System.getenv().get("BOT_TOKEN");
        this.logger = logger;
        this.serverRepository = serverRepository;
        this.api = new DiscordApiBuilder()
                .setToken(botToken)
                .setAllIntents()
                .setWaitForServersOnStartup(true)
                .setWaitForUsersOnStartup(true)
                .login().exceptionally(exception -> {    // Error message for any failed actions from the above
                    logger.error("Error setting up DiscordApi instance!");
                    logger.error(exception.getMessage());
                    return null;
                })
                .join();
    }

    public void appRuntime(Logger logger){
        logger.info("Bot has started!");

        // Handling server entries in the database
        if (this.serverRepository.findAll().isEmpty()) {
            logger.trace("Bot server data repository empty, initializing data repository...");
            Collection<Server> servers = api.getServers();
            for (Server server : servers) {
                this.serverRepository.save(new DiscordServer(String.valueOf(server.getId()), Main.defaultGuildPrefix));
            }
            logger.trace("Bot server data repository initialized");
        }
    }

    @Bean
    public DiscordApi getApi() {
        return this.api;
    }

    @Override
    public void run(ApplicationArguments args) throws Exception {
        appRuntime(logger);
    }
}
  1. Pass Main.class to the SpringApplication.run
@SpringBootApplication
@EnableMongoRepositories
public class Main {
    public static void main(String[] args) {
        ApplicationContext AppContext = SpringApplication.run(Main.class, args);
    }
}

use annotation @Autowired with the constructor of BotApplication class as shown below -

@Autowired
public BotApplication(ServerRepository serverRepository, Logger logger){
        String botToken = System.getenv().get("BOT_TOKEN");

        this.api = new ApiBuilder();

        this.serverRepository = serverRepository;

        appRuntime(logger);
    }

You need to use @EnableMongoRepositories on top of your main application class to enable scanning of mongo repository beans.

You have Created Repository bean (ServerRepository)within your application.

But in your BotApplication component (which itself is a bean), you are not telling spring to inject dependency of Repository bean (i.e. Dependancy Injection).

Such a dependancy injection can be achieved by constructor , field-based or setter based methodologies.

You can either remove ServerRepository serverRepository from public BotApplication(ServerRepository serverRepository, Logger logger) constructor & just use :

@Autowired
private final ServerRepository serverRepository;

Or as other answer suggested, use @Autowired in Constructor itself and remove field ServerRepository serverRepository :

`
@Autowired
public BotApplication(ServerRepository serverRepository, Logger logger)
`

Please note, here, this.serverRepository = serverRepository; is also not required in constructor as dependency injection will take care of this.

Related