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.
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.
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.
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
and application running state
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.
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> {
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.
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 {
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.
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.
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.
Related
I have a multi module project (gradle) with following structure:
Root project 'mp-search'
+--- Project ':analyzer'
+--- Project ':common'
| \--- Project ':common:es-model'
...
Description:
:analyzer: contains a spring boot application
Contains #SpringBootApplication and all other needed dependencies (Web, Feign, etc.)
:common:es-model: contains models + repositories for Spring Data Elasticsearch
Contains only the "spring-boot-starter-data-elasticsearch" dependency (no #SpringBootApplication)
Say I have the following classes in :common:es-model with package com.example.esmodel.document.model:
package com.example.esmodel.document.model;
//imports
#org.springframework.data.elasticsearch.annotations.Document(indexName = "document")
public class Document {
#Id
private String documentId;
#Field
private String content;
// Getter + Setter + Constructor
}
package com.example.esmodel.document.repository;
// imports
#Repository
public interface DocumentRepository extends ElasticsearchRepository<Document, String> {
}
Furthermore I created a configuration class with #ComponentScan to find them
package com.example.esmodel.document.configuration;
// Imports
#Configuration
#ComponentScan(basePackages = "com.example.esmodel.document")
public class DocumentConfiguration {
}
And an custom annotation for simple inclusion in the application which imports the configuration:
package com.example.esmodel.document;
//Imports
#Retention(RetentionPolicy.RUNTIME)
#Target(ElementType.TYPE)
#Import(DocumentConfiguration.class)
public #interface EnableDocumentModel {
}
I want to use the DocumentRepository in a controller in my application project (:analyzer). So I want to include it via the EnableDocumentModel annotation like this:
package com.example.analyzer;
import com.example.esmodel.document.EnableDocumentModel;
// Other imports
#SpringBootApplication
#EnableFeignClients
#EnableDocumentModel // [1]
//#Import(DocumentConfiguration.class) // [2]
//#ComponentScan(basePackages = "com.example.esmodel.document") // [3]
public class AnalyzerApplication {
public static void main(String[] args) {
SpringApplication.run(AnalyzerApplication.class, args);
}
}
Of these three tests [1] and [2] are not working. The classes (EnableDocumentModel, DocumentConfiguration) are found and application tries to start, but fails with a UnsatisfiedDependencyException:
***************************
APPLICATION FAILED TO START
***************************
Description:
Parameter 0 of constructor in com.example.analyzer.controller.DocumentController required a bean of type 'com.example.esmodel.document.repository.DocumentRepository' that could not be found.
But test [3], using the same #ComponentScan(basePackages = "com.example.esmodel.document") from DocumentConfiguration in the AnalyzerApplication, works fine. But this is not what I desire.
Do I miss something? Any ideas?
Thanks in advance!
Edit:
Just to make sure the DocumentConfiguration is considered by spring at all, I added #Import(DocumentRepository.class) in DocumentConfiguration, but it throws an BeanInstantiationException because it's an interface (which is of course reasonable).
Caused by: org.springframework.beans.BeanInstantiationException: Failed to instantiate [com.example.esmodel.document.repository.DocumentRepository]: Specified class is an interface
at org.springframework.beans.factory.support.SimpleInstantiationStrategy.instantiate(SimpleInstantiationStrategy.java:70) ~[spring-beans-5.3.19.jar:5.3.19]
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.instantiateBean(AbstractAutowireCapableBeanFactory.java:1326) ~[spring-beans-5.3.19.jar:5.3.19]
... 31 common frames omitted
So Spring definitely considers DocumentConfiguration otherwise the exception wouldn't be thrown. So somehow the configuration is considered, but #ComponentScan(basePackages = "de.sva.medpower.esmodel.document") doesn't do its job...
I also found a tutorial on medium (https://medium.com/trendyol-tech/how-to-write-a-spring-boot-library-project-7064e831b63b) doing it that way, but somehow it doesn't work for me..
#ComponentScan does not trigger Spring Data repository initialization. You'd need an #EnableElasticsearchRepositories somewhere in your configuration (ideally on a class in the package that you'd want to scan for ES repositories).
This is even activated automatically if Boot finds Spring Data Elasticsearch on the execution classpath, but in your case doesn't show any effect as the application's root package is com.example.analyzer (the package that your #SprignBootApplication class resides in).
Annotating DocumentConfiguration with #EnableElasticsearchRepositories and point that to the packages that the ES repositories reside in should fix the issue.
Spring Boot JDBC with Spring Boot: 2.2.4.RELEASE, in gradle the dep:
implementation ("org.springframework.boot:spring-boot-starter-data-jdbc")
The dep to -data-jpa (see "duplicated question")
//implementation ("org.springframework.boot:spring-boot-starter-data-jpa") is commented
Got this error ONLY when run it from the integration tests:
"A constructor parameter name must not be null to be used with Spring Data JDBC!"
Error creating bean with name 'myRepository':
where my repository is defined as this. An interface. It can not have a constructor.
public interface MyRepository extends CrudRepository<MyData, UUID> {
the way it is injected is:
#Inject
MyRepository myRepository;
The integration test itself:
#ActiveProfiles("test")
#ExtendWith(SpringExtension.class)
#SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT, classes = Application.class)
public class MyResourceTest {
It used to work for a lower spring boot version from the tests.
It works Ok. from IDE when starting tests.
It works Ok. when starting the app and hit the endpoints - it does inject the repositories.
Q: How to make Spring DATA JDBC work without this "A constructor parameter name must not be null to be used with Spring Data JDBC!" error for the spring data jdbc repositories?
UPDATE: just checked and double-checked.
class MyData {
//#Id Dont use the Id annotation here because I use a mapper when #Query(.. by this object
private UUID id;
private String name;
private MyData2 mydata2;
public MyData() {}
public MyData(String name, MyData2 mydata2) {...}
.. all getters and setters
}
MyData2 is the same - with the default + all arg Constructor. All is made manual. No lombok.
Update 2:
if I create a folder "jdbc" put there the config class with # EnableJdbcRepositories and move all my data jdbc repositories there "to be in one module". I still have the same exception. IF run from the integration test.
I have a spring-boot application (Java8, spring-boot 2.1.4-RELEASE).
One of the services in the business layer need to #Autowire a bean from a certain jars in my classpath. In order to achieve that i had to add #ComponentScan({"package.to.my.bean.inside.the.jar"}) and it was magically scanned and wired successfully (this was added to the main spring-boot class which declare the main method).
However, since then my controllers aren't scanned hence the DispatcherServlet is returning 404 for every request i trigger (default dispatcher).
Actually my entire spring-boot app annotations is being ignored - no scan is performed.
Just to emphasis - the application worked perfectly before adding the #ComponentScan.
Main spring-boot app class:
package com.liav.ezer;
// This is the problematic addition that cause the endpoints to stop
// inside a jar in the classpath, in com.internal.jar package resides an
// object which i need to wire at run time
#ComponentScan({"com.internal.jar"})
#SpringBootApplication
public class JobsApplication {
public static void main(String[] args) {
SpringApplication.run(JobsApplication .class, args);
}
}
Controller example:
package com.liav.ezer.controller;
#RestController
#EnableAutoConfiguration
#RequestMapping(path = "/jobs")
public class JobController {
#GetMapping(path="/create", produces = "application/json")
#ResponseStatus(HttpStatus.OK)
String createJob(#RequestParam() String jobName) String jobName){
return "job created...";
}
}
I tried adding my spring-boot app base package to the list of packages in the #ComponentScan with no luck.
I tried narrowing down the scope of the package declaration to be only on the class which i need with no luck.
Here is the code
According to Spring documentation
Configures component scanning directives for use with #Configuration
classes. Provides support parallel with Spring XML's
element. Either basePackageClasses() or
basePackages() (or its alias value()) may be specified to define
specific packages to scan. If specific packages are not defined,
scanning will occur from the package of the class that declares this
annotation.
In your case when you are adding
#ComponentScan({"com.internal.jar"})
you are disabling scanning of
com.liav.ezer.controller
To fix it you can do the following configuration
#ComponentScan(basePackages = {"com.internal.jar", "com.liav.ezer.controller"})
If so, remove #ComponentScan, can declare that bean in yourself configuration.
try below
#SpringBootApplication
public class JobsApplication {
public static void main(String[] args) {
SpringApplication.run(JobsApplication .class, args);
}
#Bean
public BeanInOtherJar xxBean(){
return new com.internal.jar.XXX();
}
}
I have a problem trying to get my autoconfiguration working. I have two jars as follows, each have a spring.factories file where these two are enabled for EnableAutoConfigurationProperties.
This configuration is in my-package-mock.jar, it depends on my-package-real.jar below:
package org.packages.package.packageA;
#Configuration
#AutoConfigureBefore(AutoConfigurationB.class)
public class AutoConfigurationA {
#Bean
public MyService mockService() {
return new MyMockService();
}
}
This configuration is in my-package-real.jar:
package org.packages.package.packageB;
#Configuration
#ConditionalOnMissingBean(MyService.class)
public class AutoConfigurationB {
#Bean
public MyService realService() {
return new MyRealService();
}
}
Now the idea is that if my-package-mock.jar is included then AutoConfigurationB will not be processed as A is ordered to be before and by the time it gets to B MyService is already defined.
However, it does not work when used in a third project that includes these jars. It looks like the AutoConfigureOrder annotation is skipped when loading these jars from the classpath and these configurations are processed in the order the jvm loads these classes. In my particular case it does B first and at that point MyService is not yet defined and thus will instantiate the RealService bean. How can I get this to work?
Obviously this is a small example where a #Primary annotation on the mock will do the job, but that is not what I'm looking for.
Edit: it seems if the #SpringBootApplication annotated main is not a part of the package where these configurations are then things do work. E.g. the annotation is not in "org.packages.package" but "org.somewhereelse" then things work.
package org.packages.package;
#SpringBootApplication
public class TestApplication {
public static void main(String[] args) throws Exception {
SpringApplication.run(Collections.singletonList(TestApplication.class).toArray(), args);
}
}
#AutoConfigureBefore and #AutoConfigureAfter only apply when a configuration class is loaded as a result of auto-configuration being enabled and it being listed in spring.factories. When your auto-configuration classes are in org.packages.package (or a sub-package) and your main application class is in the same package, they're being found by Spring Framework's standard component scanning. This happens because #SpringBootApplication enables component scanning for the package of the class that it's annotating. As a result of this the auto-configuration-specific ordering doesn't apply.
To avoid the problem, you should places your auto-configuration classes in a package that isn't used by any application code.
Right now i have a inherited project that is using annotation based spring dependency injection. So all classes are simply marked with #Component (or specific stereoTypes like #service, #Repository,#RestController, etc). This makes it a little hard to find where the dependency is located and i was thinking to change it so that each package has its own dependency configuration and then add each package to the #ComponentScan afterwards.
So for example if i had a package called com.mycoolpackage.login and mycoolpackage.networking
then i'd have a Spring configuration like this in first package:
#Configuration
public class LoginDIConfig {
#Bean
public LoginServiceImpl loginServiceImpl() {
return new LoginServiceImpl();
}
}
and in the second package i'd have the following:
#Configuration
public class NetworkDIConfig {
#Bean
public NetworkServiceImpl networkServiceImpl() {
return new NetworkServiceImpl();
}
}
and my#ComponentScan would look like this:
#ComponentScan(basePackages = {"com.mycoolpackage.login","com.mycoolpackage.network"})
So i have two questions about this approach.
How can i use a #Service annotation instead of bean here
Do you think this design is more easier as it tells you what your package dependencies are very easily instead of hunting them
down.
If you want to configure some been properties manually then you should go for above configuration else you should stick with exiting one.
This makes it a little hard to find where the dependency is located
#Autowire Or #Inject annotation will always lead you to dependency class.