SpringBoot 3 native compilation not generating bean definition for second JpaRepository and failing to start with -Dspring.aot.enabled=true - spring-boot

I am facing an issue with Spring Boot 3 native compilation where the project contains two JpaRepository connecting to two different datasources. The creation of the second datasource configuration depends on the first datasource and JpaRepository as it contains the details about the databases to connect.
The problem is that the Spring Boot Maven plugin process-aot goal does not generate bean definition for repositories which are processed later on. As a result, the application fails to start with the -Dspring.aot.enabled=true property enabled.
I have tried several solutions, including:
Adding the #DependsOn annotation to the second datasource configuration class, but it didn't work.
Adding the #DependsOn annotation to the second JpaRepository, but it also didn't work.
Adding a #Configuration class that contains both datasource configurations, but it also didn't work.
Here is a simplified version of my code:
package com.company.multidatabases.config
#Configuration
public class DataSource1Config {
// datasource1 configuration
#Autowired
private MyEntity1Repository repo;
private Map<Object,Object> dataSources;
}
package com.company.config
#Configuration
public class DataSource2Config {
#Autowired
private DataSource1Config dataSource1Config;
#Bean
public DataSource dataSource(){
return // AbstractDataSourceRouting with datasources map from DataSource1Config
}
// datasource2 configuration that depends on dataSource1Config
}
package com.company.multidatabases.repository
#Repository
public interface MyEntity1Repository extends JpaRepository<MyEntity1, Long> {
// MyEntity1Repository definition
}
package com.company.repository
#Repository
public interface MyEntity2Repository extends JpaRepository<MyEntity2, Long> {
// MyEntity2Repository definition that depends on DataSource2Config
}
And here is the error message I get:
Caused by: org.springframework.beans.factory.NoSuchBeanDefinitionException: No bean named 'myEntity1Repository' available
Any help or suggestion is highly appreciated. Thank you in advance.

Related

Why spring data jpa unnecessary #Repository?

I've looked through various documents and questions about it, but I haven't been able to find a clear answer.
I don't know how an interface that extends JpaRepository can be registered as a bean, and why it doesn't need the #Repository annotation.
In Spring, an interface cannot register a bean with that type without an implementation.
So I tried experimenting like JpaRepository myself, but it didn't work.
// JpaRepository
#NoRepositoryBean
public interface WackRepository {
}
// SimpleJpaRepository
#Repository
public class WackRepositoryImpl implements WackRepository {
}
public interface HelloRepository extends WackRepository {
}
#RestController
#RequiredArgsConstructor
public class HelloController {
private final HelloRepository helloRepository;
}
Parameter 0 of constructor in com.example.demo.HelloController required a bean of type 'com.example.demo.HelloRepository' that could not be found
HelloController, of course, has no implementation, so it is not registered as a bean and throws an exception.
Spring Data detects extensions of the Repository Interface.
In Spring Boot the interfaces extending Repository are found automatically without Spring Boot or if the interfaces are not below the SpringBootApplication in the package hierarchy you have to configure the packages:
#EnableJpaRepositories("com.acme.repositories")
Source: https://docs.spring.io/spring-data/jpa/docs/current/reference/html/#repositories.create-instances

Spring Boot 2.5 and Spring Data: #NoRepositoryBean unexpected behaviour in multi-module project

I'm facing the following issue in a legacy code that I can't change. I have a multi module project which defines in the commons module a Spring Data interface as below:
package commons;
...
#NoRepositoryBean
public interface MyCustomRepository<P, I extends Number> extends JpaRepository<MyEntity, Integer>
{
MyEntity getOneAndCheck();
}
In another module I extend this interface as follows:
package data;
...
#Repository
public interface MyRepository extends MyCustomRepository<MyEntity, Integer>
{
...
}
So, the idea is that I don't want that Spring Data generates any implementation for the MyEntity getOneAndCheck() method 'cause it is implemented like this:
package data;
...
public class MyCustomRepositoryImpl implements MyCustomRepository
{
...
#Override
public MyEntity getOneAndCheck()
{
...
}
...
}
However, when I'm starting the application, I get the following exception:
...
Caused by: java.lang.IllegalArgumentException: Failed to create query for method public abstract MyEntity commons.MyCustomRepository.getOneAndCheck()! No property getOne found for type MyEntity!
...
So what it seems to happen is that Spring Data tries to generate a Query for the MyEntity getOneAndCheck() method, despite the #NoRepositoryBean annotation. This works as expected in the application I'm gonna migrate from Spring 3 with Spring Data to Spring Boot 2.5.
Not sure if the described behavior has anything to do with the fact that there are multiple Maven modules and that the repositories, the entities and the DTOs are in different modules. Not sure neither if there should be any difference between the way it runs currently with Spring and the one with Spring Boot. But the result is that all of the dozens of repositories in this legacy application are failing with the mentioned exception.
It might be important to mention that the main class needs to use annotations in order to tune the scanning:
#SpringBootApplication(scanBasePackages = "...")
#EnableJpaRepositories(basePackages={"...", "..."})
#EntityScan(basePackages= {"...", "..."})
public class MyApp
{
public static void main(String[] args)
{
SpringApplication.run(MyApp.class, args);
}
}
Not sure whether these annotations are supposed to change anything from the point of view of #NoRepositoryBean but the issue appeared as soon as I added this Spring Boot main class. It worked okay previously without Spring Boot.
Any suggestion please ?
Many thanks in advance.
Kind regards,
Seymour
There are two things that play together:
Spring Data's default custom implementation
Repository fragments
None of these apply because:
The default custom implementation follows the name of the actual repository. In your case, the implementation is named MyCustomRepositoryImpl whereas the repository name is MyRepository. Renaming the implementation to MyRepositoryImpl would address the issue
Since Spring Data 2.0, the repository detection considers interfaces defined at the repository level as fragment candidates where each interface can contribute a fragment implementation. While the implementation name follows the fragment interface name (MyCustomRepository -> MyCustomRepositoryImpl), only interfaces without #NoRepositoryBean are considered.
You have three options:
extracting your custom method into its own fragment interface and providing an implementation class that follows the fragment name:
interface MyCustomFragement {
MyEntity getOneAndCheck();
}
class MyCustomFragementImpl implements MyCustomFragement {
public MyEntity getOneAndCheck() {…}
}
public interface MyRepository extends MyCustomRepository<MyEntity, Integer>, MyCustomFragment {…}
Set the repositoryBaseClass via #EnableJpaRepositories(repositoryBaseClass = …) to a class that implements the custom method.
If you cannot change the existing code, you could implement a BeanPostProcessor to inspect and update the bean definition for the JpaRepositoryFactoryBean by updating repositoryFragments and adding the implementation yourself. This path is rather complex and requires the use of reflection since bean factory internals aren't exposed.

How to test a configuration class in Spring Boot

I have the following Spring Boot configuration class so that an index is created in our MongoDB:
#Configuration
#DependsOn("mongoTemplate")
#Profile({ "!test" })
public class CollectionsConfig {
private final MongoTemplate mongoTemplate;
#Autowired
CollectionsConfig(MongoTemplate mongoTemplate) {
this.mongoTemplate = mongoTemplate;
}
#PostConstruct
public void initIndexes() {
mongoTemplate.indexOps("db-name"); // collection name string or .class
.ensureIndex(
new Index().on("fechaAlta", Sort.Direction.ASC).expire(15552000)
);
}
}
How could I test this since this class doesn't create any bean or anything like that? I have seen examples of configuration classes where they use the application context to test if a bean is created, but I am not creating any bean here so I don't know what to do to run this code.
Help please.
I'd assume a meaningful way to test this configuration would be to verify the side effects of the #PostConstruct-annotated method.
For example, you could have a #SpringBootTest loading this configuration, then have your test verify that on your embedded version of Mongo DB the index on fechaAlta has been successfully created.
An alternative would be writing a test where you manually instantiate CollectionsConfig, pass it a mocked MongoTemplate instance, manually invoke the #PostConstruct-annotated method and ultimately verify the expected interactions with the MongoTemplate are satisfied.

Can't Find A Reponsitory

I have a repository interface as
#Repository
public interface WordRepository extends ReactiveCrudRepository<Word, Long> {}
And in the #SpringApplication class, I have
#Bean
ApplicationListener<applicationReadyEvent> ready(WordRepository rep) {
...
}
to populate some data to the database. It won't be compiled. After the message "APPLICATION FAILED TO START", it says
Action:
Consider defining a bean of type 'com.example.reactive.wordservice.WordRepository' in your configuration.
With or without the annotation #Repository won't yield a different outcome. I change to another approach with a new class instead.
#Component
class WordDataInitializer {
private static Logger log = LoggerFactory.getLogger(WordDataInitializer.class);
private WordRepository wordRepository;
public WordDataInitializer(WordRepository wordRepository) {
this.wordRepository = wordRepository;
}
#EventListener(ApplicationReadyEvent.class)
public void initializeDB() throws URISyntaxException, IOException {
...
}
}
The outcome is still the same. I have done that many times and don't know why it doesn't work this time with Reactor. The Spring Boot is the latest version, 2.3.0 release.
What is missing?
After waking up this morning, I recognized that a dependency I added might cause the problem. I added the Spring Boot starter data JPA to get the #Entity annotation. Removing the dependency solves the problem.
The Reactive DB works differently. The Entity is one case. Also, a schema.sql file won't get picked up automatically as the JPA approach does. I need to write some code to pick up the schema file.

Spring Boot Application with dependency having multiple datasources

I am trying to create a Spring Boot Application, with a dependency jar which has got context.xml configured with multiple datasources.
In My spring boot application, I added #ImportResource("context.xml") to the #Configuration class and now, I get an exception that
"No qualifying bean of type [javax.sql.DataSource] is defined: expected single matching bean but found 4: XXXDataSource,YYYDataSource,ZZZDataSource,aaaaDataSource".
I read the documentation on multiple datasources in Spring Boot, but unable to fix this issue. Not sure, how I can configure my class, as I cannot change the dependency jar to change the way datasources are configured.
Please help!
You can use the "Primary" attribute on your datasource bean to make your autowiring choose it by default.
<bean primary="true|false"/>
If you are using Java configuration, use the #Primary annotation instead.
http://docs.spring.io/spring-framework/docs/4.0.4.RELEASE/javadoc-api/org/springframework/context/annotation/Primary.html
#Component
public class FooService {
private FooRepository fooRepository;
#Autowired
public FooService(FooRepository fooRepository) {
this.fooRepository = fooRepository;
}
}
#Component
public class JdbcFooRepository {
public JdbcFooService(DataSource dataSource) {
// ...
}
}
#Primary
#Component
public class HibernateFooRepository {
public HibernateFooService(SessionFactory sessionFactory) {
// ...
}
}
If this still doesn't resolve the issue, you can name the bean, and use the #Qualifier annotation in your java classes, or use the "ref" attribute in your Spring XML configuration.
https://spring.io/blog/2014/11/04/a-quality-qualifier
#Autowired
#Qualifier( "ios") // the use is unique to Spring. It's darned convenient, too!
private MarketPlace marketPlace ;
If you require one of the datasources in the jar and are unable to modify the configuration, rather than importing the xml from the jar, copy the configurations you need into your own local spring context configuration.

Resources