Spring Boot - Injecting Repository into Controller throws Consider defining a bean of type 'Type' in your configuration

Viewed 1188

I have a Spring/Boot REST controller which I inject a repository into like so:

package com.example.schooltimetable.controllers;

import com.example.schooltimetable.models.entities.StudentEntity;
import com.example.schooltimetable.repository.StudentRepository;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.CrossOrigin;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RestController;

@RestController
@CrossOrigin
@RequestMapping("student")
public class StudentController {

    private final StudentRepository studentRepository;

    @Autowired
    public StudentController(StudentRepository studentRepository) {
        this.studentRepository = studentRepository;
    }

    @RequestMapping(value = "/getAll", method = RequestMethod.GET)
    public Iterable<StudentEntity> getAllStudents() {
        return this.studentRepository.findAll();
    }
}

The problem I'm facing is that whenever i start my application it throws the following error:

***************************
APPLICATION FAILED TO START
***************************

Description:

Parameter 0 of constructor in com.example.schooltimetable.controllers.StudentController required a bean of type 'com.example.schooltimetable.repository.StudentsRepository' that could not be found.


Action:

Consider defining a bean of type 'com.example.schooltimetable.repository.StudentsRepository' in your configuration.


Process finished with exit code 1

I faced this issue before when trying to find an Entity in another class, I fixed this by using the @ComponentScan() annotation in Application.java, however, it doesn't work to fix this issue.

My main application file looks like this:

SchoolTimetableApplication

package com.example.schooltimetable;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.annotation.ComponentScan;

@SpringBootApplication
@ComponentScan({
    "com.example.schooltimetable.controllers",
    "org.springframework.stereotype",
    "com.example.schooltimetable.repository",
})
public class SchoolTimetableApplication {
    public static void main(String[] args) {
        SpringApplication.run(SchoolTimetableApplication.class, args);
    }
}

And my repository looks like this: StudentRepository

package com.example.schooltimetable.repository;

import com.example.schooltimetable.models.entities.StudentEntity;
import org.springframework.data.repository.CrudRepository;
import org.springframework.stereotype.Component;
import org.springframework.stereotype.Repository;

@Repository
@Component
public interface StudentRepository extends CrudRepository<StudentEntity, Long> { }

The StudentEntity referenced in the repository:

package com.example.schooltimetable.models.entities;

import com.example.schooltimetable.models.constants.StudentEntityConstants;
import com.example.schooltimetable.models.constants.TimetableEntityConstants;
import org.springframework.lang.NonNull;

import javax.persistence.*;
import java.util.UUID;

@Entity
@Table(name = StudentEntityConstants.TABLE_NAME)
public class StudentEntity {

    @Id
    @Column(name = StudentEntityConstants.ID)
    @GeneratedValue(strategy = GenerationType.AUTO)
    private long id;

    @Column(name = StudentEntityConstants.FIRST_NAME)
    @NonNull
    private String firstName;

    @Column(name = StudentEntityConstants.LAST_NAME)
    @NonNull
    private String lastName;

    @Column(name = StudentEntityConstants.REFERENCE)
    @NonNull
    @GeneratedValue(strategy = GenerationType.AUTO)
    private UUID reference;

    @OneToOne(targetEntity = TimeTableEntity.class)
    @JoinColumn(name = TimetableEntityConstants.REFERENCE)
    private TimeTableEntity timeTable;

    public long getId() {
        return this.id;
    }

    public void setId(long id) {
        this.id = id;
    }

    public String getFirstName() {
        return this.firstName;
    }

    public void setFirstName(String firstName) {
        this.firstName = firstName;
    }

    public String getLastName() {
        return this.lastName;
    }

    public void setLastName(String lastName) {
        this.lastName = lastName;
    }

    public UUID getReference() {
        return this.reference;
    }

    public void setReference(UUID reference) {
        this.reference = reference;
    }

    public TimeTableEntity getTimeTable() {
        return this.timeTable;
    }
}

And my POM file:

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.3.1.RELEASE</version>
        <relativePath/> <!-- lookup parent from repository -->
    </parent>
    <groupId>com.example</groupId>
    <artifactId>school-timetable</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <name>school-timetable</name>
    <description>Demo project for Spring Boot</description>

    <properties>
        <java.version>1.8</java.version>
    </properties>

    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-thymeleaf</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>

        <dependency>
            <groupId>com.h2database</groupId>
            <artifactId>h2</artifactId>
            <scope>runtime</scope>
        </dependency>
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <optional>true</optional>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
            <exclusions>
                <exclusion>
                    <groupId>org.junit.vintage</groupId>
                    <artifactId>junit-vintage-engine</artifactId>
                </exclusion>
            </exclusions>
        </dependency>
        <dependency>
            <groupId>org.springframework.data</groupId>
            <artifactId>spring-data-commons</artifactId>
            <version>2.3.1.RELEASE</version>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-jdbc</artifactId>
        </dependency>
        <dependency>
            <groupId>javax.annotation</groupId>
            <artifactId>javax.annotation-api</artifactId>
        </dependency>
    </dependencies>

    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
        </plugins>
    </build>

</project>

Is there something I've done wrong? I'm very new to Spring/Boot. An interesting note with this issue is that if I remove

@ComponentScan({
    "com.example.schooltimetable.controllers", <---- This
    "org.springframework.stereotype",
    "com.example.schooltimetable.repository",
})

Then the application loads but the controller will throw a 404 when trying to hit: http://localhost:8080/student/getAll

EDIT!!!

There was a few things wrong with the application at this point. Firstly, the POM file needed updating

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.3.1.RELEASE</version>
        <relativePath/> <!-- lookup parent from repository -->
    </parent>
    <groupId>com.example</groupId>
    <artifactId>school-timetable</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <name>school-timetable</name>
    <description>Demo project for Spring Boot</description>

    <properties>
        <java.version>1.8</java.version>
    </properties>

    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-thymeleaf</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>

        <dependency>
            <groupId>com.h2database</groupId>
            <artifactId>h2</artifactId>
            <scope>runtime</scope>
        </dependency>
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <optional>true</optional>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
            <exclusions>
                <exclusion>
                    <groupId>org.junit.vintage</groupId>
                    <artifactId>junit-vintage-engine</artifactId>
                </exclusion>
            </exclusions>
        </dependency>
        <dependency>
            <groupId>org.springframework.data</groupId>
            <artifactId>spring-data-commons</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-jdbc</artifactId>
        </dependency>
        <dependency>
            <groupId>javax.annotation</groupId>
            <artifactId>javax.annotation-api</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.data</groupId>
            <artifactId>spring-data-jpa</artifactId>
        </dependency>
        <dependency>
            <groupId>commons-configuration</groupId>
            <artifactId>commons-configuration</artifactId>
            <version>1.9</version>
        </dependency>
        <dependency>
            <groupId>org.hibernate</groupId>
            <artifactId>hibernate-core</artifactId>
        </dependency>
        <dependency>
            <groupId>org.hibernate</groupId>
            <artifactId>hibernate-entitymanager</artifactId>
        </dependency>
        <dependency>
            <groupId>javax.xml.bind</groupId>
            <artifactId>jaxb-api</artifactId>
            <version>2.3.0</version>
        </dependency>
    </dependencies>

    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
        </plugins>
    </build>

</project>

Secondly, the MAIN CLASS

package com.example.schooltimetable;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.autoconfigure.domain.EntityScan;
import org.springframework.context.annotation.PropertySource;
import org.springframework.data.jpa.repository.config.EnableJpaRepositories;

@SpringBootApplication
@PropertySource("classpath:application.properties")
@EntityScan("com.example.schooltimetable.models.entities")
@EnableJpaRepositories("com.example.schooltimetable.repository")
public class SchoolTimetableApplication {
    public static void main(String[] args) {
        SpringApplication.run(SchoolTimetableApplication.class, args);
    }
}

There were also things wrong with the entities when joining columns: They should be set to read and write: false

package com.example.schooltimetable.models.entities;

import com.example.schooltimetable.models.constants.StudentEntityConstants;
import com.example.schooltimetable.models.constants.TimetableEntityConstants;
import org.springframework.lang.NonNull;

import javax.persistence.*;
import java.util.UUID;

@Entity
@Table(name = StudentEntityConstants.TABLE_NAME)
public class StudentEntity {

    @Id
    @Column(name = StudentEntityConstants.ID)
    @GeneratedValue(strategy = GenerationType.AUTO)
    private long id;

    @Column(name = StudentEntityConstants.FIRST_NAME)
    @NonNull
    private String firstName;

    @Column(name = StudentEntityConstants.LAST_NAME)
    @NonNull
    private String lastName;

    @Column(name = StudentEntityConstants.REFERENCE)
    @GeneratedValue(strategy = GenerationType.AUTO)
    private UUID reference;

    @OneToOne(targetEntity = TimeTableEntity.class)
    @JoinColumn(name = TimetableEntityConstants.REFERENCE, updatable = false, insertable = false)
    private TimeTableEntity timeTable;

    public long getId() {
        return this.id;
    }

    public void setId(long id) {
        this.id = id;
    }

    public String getFirstName() {
        return this.firstName;
    }

    public void setFirstName(String firstName) {
        this.firstName = firstName;
    }

    public String getLastName() {
        return this.lastName;
    }

    public void setLastName(String lastName) {
        this.lastName = lastName;
    }

    public UUID getReference() {
        return this.reference;
    }

    public void setReference(UUID reference) {
        this.reference = reference;
    }

    public TimeTableEntity getTimeTable() {
        return this.timeTable;
    }
}

Thanks to @Abhinaba Chakraborty for pointing me in the right direction

2 Answers

First of all , you don't need a component scan with the annotation SpringBootApplication in place. Spring automatically scans all components annotated with Component,Service,RestController,Respository,Configuration etc. and creates the Beans.

The request mapping annotation value in StudentController should be prefixed with "/" eg. @RequestMapping("/student") - Although it has nothing to do with dependency injection . But this is the reason why you are getting a 404 at http://localhost:8080/student/getAll

In the repository interface, just use @Repository annotation instead of having both @Repository and @Component annotations.

Also I dont see a data-jpa dependency defined in your POM.xml yet How are you importing the org.springframework.data.repository.CrudRepository in your repository. You need that dependency.

As far as directory structure concerned, it looks OK ,but please verify:

FIXED!!!

So the problem was with the way I'd implemented the @OneToOne relationship between the student and the Timetable.

The edit in the question was a work around as stated by @M.Deinum. The correct solution was to first clean up the relationship between Student and Timetable, like so:

StudentEntity

package com.example.schooltimetable.models.entities;

import com.example.schooltimetable.models.constants.StudentEntityConstants;
import org.springframework.lang.NonNull;

import javax.persistence.*;

@Entity
@Table(name = StudentEntityConstants.TABLE_NAME)
public class StudentEntity {

    @Id
    @Column(name = StudentEntityConstants.ID)
    private long id;

    @Column(name = StudentEntityConstants.FIRST_NAME)
    @NonNull
    private String firstName;

    @Column(name = StudentEntityConstants.LAST_NAME)
    @NonNull
    private String lastName;

    @OneToOne(mappedBy = "student", cascade = CascadeType.ALL) // no need to join the columns on the Student. 
    private TimeTableEntity timeTable;

    public long getId() {
        return this.id;
    }

    public void setId(long id) {
        this.id = id;
    }

    public String getFirstName() {
        return this.firstName;
    }

    public void setFirstName(String firstName) {
        this.firstName = firstName;
    }

    public String getLastName() {
        return this.lastName;
    }

    public void setLastName(String lastName) {
        this.lastName = lastName;
    }

    public TimeTableEntity getTimeTable() {
        return this.timeTable;
    }

    public void setTimeTable(TimeTableEntity timeTable) { this.timeTable = timeTable; }
}

TimeTableEntity

package com.example.schooltimetable.models.entities;

import com.example.schooltimetable.models.constants.TimetableEntityConstants;

import javax.persistence.*;
import java.io.Serializable;
import java.util.LinkedHashSet;
import java.util.Set;

@Entity
@Table(name = TimetableEntityConstants.TABLE_NAME)
public class TimeTableEntity implements Serializable {

    @Id
    @Column(name = TimetableEntityConstants.ID)
    @GeneratedValue(strategy = GenerationType.AUTO)
    private long id;

    @OneToOne
    @MapsId // <-- This automatically knows to join by the student ID column
    private StudentEntity student;

    @OneToMany(fetch = FetchType.LAZY, cascade = CascadeType.ALL)
    private final Set<ModuleEntity> modules = new LinkedHashSet();

    public long getId() {
        return this.id;
    }

    public void setId(long id) {
        this.id = id;
    }

    public void setStudent(StudentEntity student) {
        this.student = student;
    }

    public Set<ModuleEntity> getModules() {
        return this.modules;
    }

    public void setModules(ModuleEntity module) {
        this.modules.add(module);
    }
}

Now, the next few refactors, I could just do, I don't know why it worked all of a sudden but I have a feeling it was to do with the bad entity setup.I was able to remove the @EntityScan("com.example.schooltimetable.models.entities") & @EnableJpaRepositories("com.example.schooltimetable.repository")

Annotations from my main class, and I was also able to remove a lot of @Scope and @Component annotations from my code. My main class now looks like this:

SchoolTimetableApplication

package com.example.schooltimetable;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.annotation.PropertySource;

@SpringBootApplication
@PropertySource("classpath:application.properties")
public class SchoolTimetableApplication {
    public static void main(String[] args) {
        SpringApplication.run(SchoolTimetableApplication.class, args);
    }
}

Much better! I also have data pulling through for the relationships rather than null (another issue I was facing).

Related