Se Song
Se Song

Reputation: 1663

Create Custom Repository to Spring Data JPA

I try to create a custom repository by following this tutorial: https://www.baeldung.com/spring-data-jpa-method-in-all-repositories

My app build fail with error:

NoSuchBeanDefinitionException: No qualifying bean of type 'com.example.demo.dao.ExtendedStudentRepository' available: expected at least 1 bean which qualifies as autowire candidate. Dependency annotations: {@org.springframework.beans.factory.annotation.Autowired(required=true)}

My full source code: https://github.com/sesong11/springjpa-custom-repo

There are plenty of similar questions, but none of them fixed for me. Perhaps it's Spring issue on current version 2.1.1, or I missed some configuration.

Upvotes: 2

Views: 7930

Answers (8)

Andrew
Andrew

Reputation: 49656

Before we run the test, let's make sure the main application is running. Actually, it has some issues.

1. IllegalArgumentException: Not a managed type: class com.example.demo.entity.Student.

The problem is @EntityScan("com.example.demo.entity.*"). The package name isn't correct, therefore the Student class isn't scanned.

The solution is @EntityScan("com.example.demo.entity").

2. PropertyReferenceException: No property attributeContainsText found for type Student!

The problem is that the repository base class isn't set, and JpaQueryLookupStrategy thinks it should construct a RepositoryQuery from the method name findByAttributeContainsText. It recognises the pattern findBy{EntityPropertyName} and fails to find the field attributeContainsText in Student.

The repository base class isn't set, because the configuration StudentJPAH2Config isn't applied. A configuration doesn't get applied if it's unable to be scanned. @ComponentScan("com.example.demo.dao") doesn't scan the package where StudentJPAH2Config resides.

The solution is @ComponentScan("com.example.demo") which will scan both "com.example.demo" and "com.example.demo.dao" packages1.

Now, when the application is fine, let's get back to the test.

3. NoSuchBeanDefinitionException: No qualifying bean of type com.example.demo.dao.ExtendedStudentRepository available.

The problem is that StudentJPAH2Config doesn't constitute the whole configuration, and some beans are missing.

The solution is to list all the configuration classes.

@ContextConfiguration(classes = { StudentJPAH2Config.class, DemoApplication.class })

or

@ContextConfiguration(classes = DemoApplication.class)

or

@SpringBootTest

4. JpaSystemException: No default constructor for entity com.example.demo.entity.Student.

The problem is that Hibernate2 requires the default constructor for Student:

@Entity
public class Student {

    @Id
    private long id;
    private String name;

    public Student() {}

    public Student(int id, String name) {
        this.id = id;
        this.name = name;
    }

    // getters & setters

}

1 @ComponentScan("com.example.demo.dao") is redundant since this package will be scanned because of @SpringBootApplication located in there.
2 Hibernate is the default JPA provider in Spring applications.

Upvotes: 2

Cepr0
Cepr0

Reputation: 30449

To get your test working, you have to do the following:

1) Replace the incorrect definition of basePackages in StudentJPAH2Config to com.example.demo.dao, or better remove it as redundant:

@Configuration
@EnableJpaRepositories(repositoryBaseClass = ExtendedRepositoryImpl.class)
public class StudentJPAH2Config {
}

2) Also replace basePackages in @ComponentScan and @EntityScan in DemoApplication class to com.example.demo.dao and com.example.demo.entity. Or better removed those and @EnableTransactionManagement annotations at all:

@SpringBootApplication
public class DemoApplication {
    public static void main(String[] args) {
        SpringApplication.run(DemoApplication.class, args);
    }
}

3) Add a no-argument constructor to the entity Student.

4) Correct your test class - use @DataJpaTest to test DAO layer and import your StudentJPAH2Config configuration:

@RunWith(SpringRunner.class)
@DataJpaTest
@Import(StudentJPAH2Config.class)
public class ExtendedStudentRepositoryIntegrationTest {
   //...
}

With these corrections, I've run your test successful.

Upvotes: 4

Vivek Kurmi
Vivek Kurmi

Reputation: 158

You have to remove @ContextConfiguration(classes = { StudentJPAH2Config.class }) and add @SpringBootTestannotations on your test class ExtendedStudentRepositoryIntegrationTest it will resolve the error NoSuchBeanDefinitionException you currently have.

But again I have got error like:

Caused by: java.lang.IllegalArgumentException: Not a managed type: class com.example.demo.entity.Student

which is because Spring is not able to scan Entity Student.

For this you have to change the @EntityScan("com.example.demo.entity.*") to @EntityScan("com.example.demo.entity") on DemoApplication class.

Again I got the error:

Caused by: org.springframework.data.mapping.PropertyReferenceException: No property attributeContainsText found for type Student!

Which is because the method findByAttributeContainsText() is not a valid method.

Upvotes: -1

aweigold
aweigold

Reputation: 6889

Your implementation of the custom Spring Data JPA repository base class does work. There were just a few items with the Spring Boot Application Context that needed to be addressed. You did have Spring Boot Starter modules declared as dependencies, so I submitted a pull request with changes that moved all the responsibility for manually configuring the context to Spring Auto Configuration factory providers.

Here are some notes about changes that were required to get the tests to pass, along with some brief details on each. Note, to run the Spring Boot Application outside of the test scope, you will want to add an appropriate JDBC vendor dependency to the classpath, along with any spring application properties to define the datasource properties. Spring Auto Config should take care of any EntityManagers and TransactionManagers with reasonable settings after the datasource properties are defined. See Spring Boot features - Working with SQL Databases.

Notes on changes:

  • Removed all Spring Configuration annotations (with exception to @SpringBootApplication and @EnableJpaRepositories). Application pom is configured to depend on Spring Boot Starter modules that provide Spring Auto Configuration classes already through spring.factories while also ensuring all other dependencies are met.
  • @EnableJpaRepositories does not need to declare basePackages as Auto Config modules will default to using all packages beneath the Spring Main Application class. Declaration of the additional repositoryBaseClass is still necessary.
  • @ComponentScan removed as Auto Config modules will perform a ComponentScan for all packages beneath the Spring Main Application class, which is already at the top level package for the project. Furthermore, this is declaring a basePackages to scan value that will not include the @Configuration class StudentJPAH2Config.
  • @EntityScan removed as Auto Config modules will perform a scan in all packages beneath the Spring Main Application class.
  • @EnableTransactionManagement removed as Auto Config modules will perform the same action.
  • Removed default application.properties as they depend on H2, however the pom only provides that dependency within the test scope's classpath. Furthermore all configuration properties set within the property file are generally Auto Configured by Spring Starters on presence of H2.
  • Added a default constructor on Student for hibernate. Hibernate requires this constructor while managing entities, unless @PersistenceConstructor or @Immutable is being used which is outside the scope of this issue.
  • Configured Spring Boot Auto Configuration for unit test application context in ExtendedStudentRepositoryIntegrationTest. This is the same as the context configuration defined for the Spring Boot integration test in DemoApplicationTests. When using @ContextConfiguration directly with StudentJPAH2Config, the configuration that was defined on the Spring Boot Main Application class (which was the ComponentScan, EntityScan, TransactionManagement) was not being applied to the Spring Test Application Context.

A few notes on Spring Auto Configuration as it was the main issue impeding this project to successfully run:

Spring Application Starters are (generally) split into two types of dependencies.

  • autoconfigure modules that declare a minimal set of dependencies in it's POM, however are built with many dependencies during it's compilation. The auto configure modules also advertise Configuration classes and factories via a spring.factories file located in META-INF of the package. Spring Boot during it's bootstrapping phase will load these configuration classes. Lastly the configuration classes will selectively declare beans and make other application context changes/enhancements based on other beans declared in the context and what dependencies are actually found on the classpath. This allows for general/reasonable default configurations all based on what libraries you are pulling in and a minimal set of properties.
  • starter modules (generally) do not provide much/any classes. Their purpose is to provide POMs that declare dependencies required for good baselines of development for a particular Spring project. The starter modules also depend on applicable autoconfigure modules, which pair up with these dependencies to allow you to start running with your application quickly.

For the most part, when using Spring Starters, the main driver should not be figuring out what you need to manually configure in the Spring context, but rather, what you should un-configure in case its baseline does not suit your needs. When it comes to following tutorials/examples online like you have, keep in mind that some items surrounding configuration may be shown. There may be times these configuration instructions are necessary, but often they are to show details for configuration required in non-boot Spring applications or for transparency.

Upvotes: 0

Moshiour
Moshiour

Reputation: 673

Following changes were made:

@SpringBootApplication
@ComponentScan("com.example.demo.dao")
@EntityScan("com.example.demo.entity")
@EnableJpaRepositories(basePackages = "com.example.demo.dao",
        repositoryBaseClass = ExtendedRepositoryImpl.class)
@EnableTransactionManagement
public class DemoApplication {
    public static void main(String[] args) {
        SpringApplication.run(DemoApplication.class, args);
    }


}

and for StudentJPAH2Config class

@Configuration
@ComponentScan("com.example.demo")
@EnableJpaRepositories(basePackages = "com.example.demo.dao",
        repositoryBaseClass = ExtendedRepositoryImpl.class)
public class StudentJPAH2Config {
    // additional JPA Configuration
}

Student class which was missing empty constructor:

@Entity
public class Student {

    public Student() {
    }

    public Student(long id, String name) {
        this.id = id;
        this.name = name;
    }

    @Id
    private long id;
    private String name;

    public long getId() {
        return id;
    }

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

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }
}

And the result

enter image description here

and application running state

enter image description here

Upvotes: 1

Vivek Kurmi
Vivek Kurmi

Reputation: 158

You have to put @Primaryannotation on the ExtendedRepositoryImpl class. It should work.

Spring basically not able to qualify the dependency. @Primary will make sure wherever you are injecting dependency for ExtendedRepository interface first qualifying bean(class) would be ExtendedRepositoryImpl.

Upvotes: -1

Satish
Satish

Reputation: 1065

You need to make below changes to resolve this issue. Also, i executed this with JDK 8 as i don't have JDK 11 in my local. Also, i executed ExtendedStudentRepositoryIntegrationTest.java for the purpose of issue reproduction.

  1. In StudentJPAH2Config.java, component scan need to be added.
@Configuration
@ComponentScan("com.example.demo")
@EnableJpaRepositories(basePackages = "com.example.demo.dao", 
  repositoryBaseClass = ExtendedRepositoryImpl.class)
public class StudentJPAH2Config {
  1. In DemoApplication the base package name has to be corrected.
@SpringBootApplication
@ComponentScan("com.example.demo.dao")
@EntityScan("com.example.demo.entity")
@EnableTransactionManagement
public class DemoApplication {

The reason for #1 change is without component scan in test config, spring was not able to look for the dependencies in right packages. #2 is needed as your Student.java is inside com.example.demo.entity package directly, not in any subpackage of it. You can also specify @EntityScan in test config file StudentJPAH2Config, though it's not needed.

Now, this resolve the immediate issue faced by you but still you will be welcomed with some other issues which are pretty straight forward and you would be able to take it forward from here.

Upvotes: -1

ibercode
ibercode

Reputation: 1373

You need an annotation in your implemented class. You must add the @Component or @Service annotation in order for Spring to pick up the injection

@Component
public class ExtendedRepositoryImpl<T, ID extends Serializable>
extends SimpleJpaRepository<T, ID> implements ExtendedRepository<T, ID> {

Upvotes: -1

Related Questions