Spring Data JPA - NoSuchBeanDefinitionException - spring

I am using CURDRepository, when calling save menthod below exception got:
org.springframework.beans.factory.NoSuchBeanDefinitionException: No
bean named 'transactionManager' available: No matching
TransactionManager bean found for qualifier 'transactionManager' -
neither qualifier match nor bean name match!
whereas, when calling other function from repo like findALL and findByID they are working fine, below is code for config, repo and calling fucntion.
// config
#Primary
#Bean(name = "emfRequest")
public LocalContainerEntityManagerFactoryBean entityManagerFactory(EntityManagerFactoryBuilder builder,
#Qualifier("dsRequest") DataSource dataSource) {
return builder.dataSource(dataSource).packages("com.Digit4G.domain").build();
}
#Primary
#Bean(name = "tmRequest")
public PlatformTransactionManager transactionManager(
#Qualifier("emfRequest") EntityManagerFactory entityManagerFactory) {
return new JpaTransactionManager(entityManagerFactory);
}
// Repository
#Repository
public interface Digit4GRepo<P> extends CrudRepository<Digit4GRequest, Long> {
public List<Digit4GRequest> findBySUBNOAndIMEI(String subno, String IMEI);
public Digit4GRequest save(Digit4GRequest request);
}
// calling function
Digit4GRequest obj = new Digit4GRequest();
obj.setSUBNO(subno);
obj.setIMEI(IMEI);
obj.setIMSI(IMSI);
obj.setPACKAGENAME(packageName);
digit4GRepo.save(obj);

Related

Spring Batch test case with multiple data sources

I have a Spring Batch Classifier to test for which I've defined this test class:
#RunWith(SpringRunner.class)
#SpringBatchTest
#ContextConfiguration(classes = { BatchConfiguration.class })
class CsvOutputClassifierTest {
#Autowired
private FlatFileItemWriter<CsvData> createRequestForProposalWriter;
#Autowired
private FlatFileItemWriter<CsvData> createRequestForQuotationWriter;
private final CsvOutputClassifier csvOutputClassifier = new CsvOutputClassifier(
createRequestForProposalWriter,
createRequestForQuotationWriter);
#Test
void shouldReturnProposalWriter() {
...
}
The batch configuration class has this constructor:
public BatchConfiguration(
final JobBuilderFactory jobBuilderFactory,
final StepBuilderFactory stepBuilderFactory,
#Qualifier("oerationalDataSource") final DataSource oerationalDataSource,
final DwhFileManager dwhFileManager,
final OperationalRepository operationalRepository)
And these beans:
#StepScope
#Bean
public FlatFileItemWriter<CsvData> createRequestForProposalWriter(
#Value("#{jobParameters['startDate']}") String startDate) {
FlatFileItemWriter<CsvData> writer = new FlatFileItemWriter<CsvData>();
...
return writer;
}
#StepScope
#Bean
public FlatFileItemWriter<CsvData> createRequestForQuotationWriter(
#Value("#{jobParameters['startDate']}") String startDate) {
FlatFileItemWriter<CsvData> writer = new FlatFileItemWriter<CsvData>();
...
return writer;
}
Running the test class I'm not able to trigger the first test method as I'm getting:
Error creating bean with name 'batchConfiguration': Unsatisfied dependency expressed through constructor parameter 2; nested exception is org.springframework.beans.factory.NoSuchBeanDefinitionException: No qualifying bean of type 'javax.sql.DataSource' available: expected at least 1 bean which qualifies as autowire candidate. Dependency annotations: {#org.springframework.beans.factory.annotation.Qualifier(value="oerationalDataSource")}
In fact, I defined two different data sources, one for the 'operational' data and the 'app' for Spring Batch persistency:
#Configuration(proxyBeanMethods = false)
public class DataSourceConfiguration {
#Bean
#Primary
#ConfigurationProperties("app.datasource")
public DataSourceProperties defaultDataSourceProperties() {
return new DataSourceProperties();
}
#Bean
#Primary
#ConfigurationProperties("app.datasource.configuration")
public HikariDataSource defaultDataSource(DataSourceProperties properties) {
return properties.initializeDataSourceBuilder().type(HikariDataSource.class)
.build();
}
#Bean
#ConfigurationProperties("aggr.datasource")
public DataSourceProperties oerationalDataSourceProperties() {
return new DataSourceProperties();
}
#Bean
#ConfigurationProperties("aggr.datasource.configuration")
public HikariDataSource oerationalDataSource(
#Qualifier("oerationalDataSourceProperties") DataSourceProperties oerationalDataSourceProperties) {
return oerationalDataSourceProperties.initializeDataSourceBuilder().type(HikariDataSource.class).build();
}
#Bean
public JdbcTemplate operationalJdbcTemplate(#Qualifier("oerationalDataSource") DataSource dataSource) {
return new JdbcTemplate(dataSource);
}
}
In #SpringBatchTest documentation it is reported that just one DataSource should be found or it should be marked as Primary:
It should be noted that JobLauncherTestUtils requires a org.springframework.batch.core.Job bean and that JobRepositoryTestUtils requires a javax.sql.DataSource bean. Since this annotation registers a JobLauncherTestUtils and a JobRepositoryTestUtils in the test context, it is expected that the test context contains a single autowire candidate for a org.springframework.batch.core.Job and a javax.sql.DataSource (either a single bean definition or one that is annotated with org.springframework.context.annotation.Primary).
But I have it. So how to fix it?
Update #1
Thanks to #Henning's tip, I've changed the annotations as follows:
#RunWith(SpringRunner.class)
#SpringBatchTest
#SpringBootTest(args={"--mode=custom", "--startDate=2022-05-31T01:00:00.000Z", "--endDate=2022-05-31T23:59:59.999Z"})
#ContextConfiguration(classes = { BatchConfiguration.class, DataSourceConfiguration.class, LocalFileManager.class, AggregatorRepository.class })
#ActiveProfiles({"integration"})
#EnableAutoConfiguration(exclude={DataSourceAutoConfiguration.class})
Where:
#SpringBootTest is needed to avoid 'Failed to determine a suitable driver class exception'
args is needed to provide the required parameters to the batch
But still having this exception:
Error creating bean with name 'scopedTarget.createRequestForProposalWriter' defined in BatchConfiguration: Bean instantiation via factory method failed; nested exception is org.springframework.beans.BeanInstantiationException: Failed to instantiate [org.springframework.batch.item.file.FlatFileItemWriter]: Factory method 'createRequestForProposalWriter' threw exception; nested exception is java.lang.NullPointerException: text
raised in the implementation of:
#StepScope
#Bean
public FlatFileItemWriter<CsvData> createRequestForProposalWriter(
#Value("#{jobParameters['startDate']}") String startDate)
as the parameter 'startDate' is null.
In my naivety I assumed that I could test in isolation the classifier with something like that:
#Test
void shouldReturnProposalWriter() {
CsvData csvData = create-some-fake-data
CsvOutputClassifier csvOutputClassifier = new CsvOutputClassifier(
createRequestForProposalWriter,
createRequestForQuotationWriter);
ItemWriter itemWriter = csvOutputClassifier.classify(csvData);
some-assert-about-itemWriter-properties
}
So now the question is: how to correctly test the classifier?
You need to list DataSourceConfiguration as argument of #ContextConfiguration, i.e. your test class should start like this
#RunWith(SpringRunner.class)
#SpringBatchTest
#ContextConfiguration(classes = { BatchConfiguration.class, DataSourceConfiguration.class })
class CsvOutputClassifierTest {
...
}
The DataSourceConfiguration is currently not known within the test as you didn't declare it as part of the context or enabled classpath scanning in any form.

How to use Transactional annotation when config transactionManager conditionally in spring?

I use multiple datasources in my spring project.
I enable/disable them by manual config in project startup time.
At a time all of them may be active
So a transactionManager bean maybe active or not.
I implement it by #Conditional annotation in spring configuration class.
When I use a disable transactional annotation on methods I have NoSuchBeanDefinitionException.
When I define transactionManager bean conditionally how to use the transactional annotioan on methods?
The archiveTransactionManager bean doesn't create by #Conditional annotation and I want spring skip checking for bean validation of conditional transaction manager.
For conditional sessionFactory beans I set 'required' parameter in Autowired annotation to false for prevent spring to throw NoSuchBeanDefinitionException but what do I do for #Transactional ?
Configuration class
#Bean("archiveTransactionManager")
#Conditional(ArchiveCondition.class)
public HibernateTransactionManager archiveTransactionManager() {
HibernateTransactionManager transactionManager = new HibernateTransactionManager();
transactionManager.setSessionFactory(archiveSessionFactory());
return transactionManager;
}
Transactional method
#Transactional(value = "archiveTransactionManager", readOnly = true)
private List<DocumentItem> loadArchivedDocumentItem() {...}
Usage
if(GeneralSetting.isArchive)
documentService.loadArchivedDocumentItem();
Current result:
org.springframework.beans.factory.NoSuchBeanDefinitionException: No bean named 'archiveTransactionManager' available: No matching PlatformTransactionManager bean found for qualifier 'archiveTransactionManager' - neither qualifier match nor bean name match!
I want spring skip bean validation of conditional transactionManager beans on some situations that they don't create by conditions.
I implement a no-op SessionFactory and instantiate it in spring configuration class whenever archive_mode is disable :
SpringConfig.java
#Bean("archiveSessionFactory")
public SessionFactory archiveSessionFactory() {
return ServerConf.ARCHIVE_MODE ? createNormalSessionFactory() : new DummySessionFactory();
}
#Bean("archiveTransactionManager")
public HibernateTransactionManager archiveTransactionManager() {
HibernateTransactionManager transactionManager = new HibernateTransactionManager();
transactionManager.setSessionFactory(archiveSessionFactory());
return transactionManager;
}
DummySessionFactory.java
public class DummySessionFactory implements SessionFactory {
...
// These two methods call in spring initializing and we have to implement them
#Override
public Metamodel getMetamodel() {
return new MetamodelImpl(null);
}
#Override
public Map<String, Object> getProperties() {
return null;
}
//Throw suitable exception in other methods
#Override
public Session getCurrentSession() throws HibernateException {
throw new HibernateException("Desired mode is Disabled");
}
...
}
I would treat it as a fallback scenario, where you have to recover after a failure. In this case, have a method calling method annotated with
#Transactional(value = "archiveTransactionManager", readOnly = true)
in case of failure (or exception), call method annotated with
#Transactional(value = "alternativeTransactionManager", readOnly = true)
public void doSomething() {
try {
tryFirst();
} catch (Exception e) {
tryAlternative();
}
}
#Transactional(value = "archiveTransactionManager", readOnly = true)
public void tryFirst() {
}
#Transactional(value = "alternativeTransactionManager", readOnly = true)
public void tryAlternative() {
}
Keep in mind, you must have both of them declared though.
You used the wrong annotation to name the bean. #Qualifier is used on the injection point to specify the name. You need to give the bean a name with #Bean. So your bean definition needs to look like that:
#Bean("archiveTransactionManager")
#Conditional(ArchiveCondition.class)
public HibernateTransactionManager archiveTransactionManager() {
HibernateTransactionManager transactionManager = new HibernateTransactionManager();
transactionManager.setSessionFactory(archiveSessionFactory());
return transactionManager;
}

SpringBoot app. accessing 2 DataSources using JdbcTemplate

I have a SpringBoot app. that has to access to different datasources to export data from 1 to another (1 local datasource and another remote datasource)
This is how my persistenceConfig looks like
public class PersistenceConfig {
#Bean
public JdbcTemplate localJdbcTemplate() {
return new JdbcTemplate(localDataSource());
}
#Bean
public JdbcTemplate remoteJdbcTemplate() {
return new JdbcTemplate(remoteDataSource());
}
#Bean
public DataSource localDataSource(){
HikariConfig config = new HikariConfig();
config.setMaximumPoolSize(getLocalDbPoolSize());
config.setMinimumIdle(5);
config.setDriverClassName(getLocalDbDriverClassName());
config.setJdbcUrl(getLocalDbJdbcUrl());
config.addDataSourceProperty("user", getLocalDbUser());
config.addDataSourceProperty("password", getLocalDbPwd());
return new HikariDataSource(config);
}
#Bean
public DataSource remoteDataSource(){
HikariConfig config = new HikariConfig();
config.setMaximumPoolSize(getRemoteDbPoolSize());
config.setMinimumIdle(5);
config.setDriverClassName(getRemoteDbDriverClassName());
config.setJdbcUrl(getRemoteDbJdbcUrl());
config.addDataSourceProperty("user", getRemoteDbUser());
config.addDataSourceProperty("password", getRemoteDbPwd());
return new HikariDataSource(config);
}
}
But when I init my app I got this error:
Caused by: org.springframework.beans.factory.NoUniqueBeanDefinitionException: No qualifying bean of type 'javax.sql.DataSource' available: expected single matching bean but found 2: localDataSource,remoteDataSource
I also tried to user qualified beans, as follows:
#Bean(name = "localJdbcTemplate")
public JdbcTemplate localJdbcTemplate() {
return new JdbcTemplate(localDataSource());
}
#Bean(name = "remoteJdbcTemplate")
public JdbcTemplate remoteJdbcTemplate() {
return new JdbcTemplate(remoteDataSource());
}
#Bean(name = "localDataSource")
public DataSource localDataSource(){
HikariConfig config = new HikariConfig();
config.setMaximumPoolSize(getLocalDbPoolSize());
config.setMinimumIdle(5);
config.setDriverClassName(getLocalDbDriverClassName());
config.setJdbcUrl(getLocalDbJdbcUrl());
config.addDataSourceProperty("user", getLocalDbUser());
config.addDataSourceProperty("password", getLocalDbPwd());
return new HikariDataSource(config);
}
#Bean(name = "remoteDataSource")
public DataSource remoteDataSource(){
HikariConfig config = new HikariConfig();
config.setMaximumPoolSize(getRemoteDbPoolSize());
config.setMinimumIdle(5);
config.setDriverClassName(getRemoteDbDriverClassName());
config.setJdbcUrl(getRemoteDbJdbcUrl());
config.addDataSourceProperty("user", getRemoteDbUser());
config.addDataSourceProperty("password", getRemoteDbPwd());
return new HikariDataSource(config);
}
but then I got this other error:
A component required a bean of type 'org.springframework.transaction.PlatformTransactionManager' that could not be found.
- Bean method 'transactionManager' not loaded because #ConditionalOnSingleCandidate (types: javax.sql.DataSource; SearchStrategy: all) did not find a primary bean from beans 'remoteDataSource', 'localDataSource'
I also tried
#SpringBootApplication(exclude = {
DataSourceAutoConfiguration.class,
DataSourceTransactionManagerAutoConfiguration.class})
#EnableAutoConfiguration(exclude = {
DataSourceAutoConfiguration.class,
DataSourceTransactionManagerAutoConfiguration.class})
but then I got
A component required a bean of type 'org.springframework.transaction.PlatformTransactionManager' that could not be found.
You can use bean names to qualify them:
#Bean(name = "localDataSource")
public DataSource localDataSource() {
...
}
#Bean(name = "remoteDataSource")
public DataSource remoteDataSource() {
...
}
Please note: You have to do the same for your JdbcTemplate beans - just give them a name and it will work.
See the Spring JavaDoc for more Information: Bean
#Bean(name = "localJdbcTemplate")
public JdbcTemplate localJdbcTemplate() {
return new JdbcTemplate(localDataSource());
}
When you use your JdbcTemplate beans within your export service implementation via autowiring (#Autowired), you need to use #Qualifier to qualify them:
#Autowired
#Qualifier("localJdbcTemplate")
private JdbcTemplate jdbcTemplate;
#Autowired
#Qualifier("remoteJdbcTemplate")
private JdbcTemplate jdbcTemplate;
Bean gets its name from the method name, providing name attribute just makes it explicit (keeping the name the same as the method name). Overall suggestion about #Bean(name="...") and #Qualifier didn't fix the error for me.
I set up sample project with two embedded databases and got the same error as the aothor. Spring suggestion was to annotate one of the DataSource beans as #Primary and, in fact, this fixes the error. Usually it happens, when some other application parts want to see only one or one primary DataSource, if several present.
What seems to be a better solution is to disable not needed autoconfiguration beans keeping rest of the code as it is:
#SpringBootApplication(exclude = {
DataSourceAutoConfiguration.class,
DataSourceTransactionManagerAutoConfiguration.class})
or:
#EnableAutoConfiguration(exclude = {
DataSourceAutoConfiguration.class,
DataSourceTransactionManagerAutoConfiguration.class})
depending on which annotations are in use.
If author doesn't use any JPA provider and operates directly with JdbcTemplate it may be a suitable solution.

Error: java.lang.IllegalArgumentException: Property 'transactionManager' is required even after it is declared

I have already declared the transactionManager in my Config file as below.
#Bean
public HibernateTransactionManager transactionManager() {
return new HibernateTransactionManager(sessionFactory().getObject());
}
Even after the declaration, I see the error that Property 'transactionManager' is not found.
Trying to configure hibernate using Spring and Hibernate Contextual sessions.
Configured SessionFactory using LocalSessionFactoryBean.
Try to use interface instead of concrete class
#Bean
public TransactionManager transactionManager() {
return new HibernateTransactionManager(sessionFactory().getObject());
}

Spring Data: inject 2 repositories with same name but in 2 different packages

Context
I want to use in the same Spring context two different databases that have entities that share the same name, but not the same structure. I rely on Spring Data MongoDB and JPA/JDBC. I have two packages, containing among others the following files:
com.bar.entity
Car.class
com.bar.repository
CarRepository.class
RepoBarMarker.class
com.bar.config
MongoConfiguration.class
com.foo.entity
Car.class
com.foo.repository
CarRepository.class
RepoFooMarker.class
com.foo.config
JPAConfiguration.class
SpecEntityManagerFactory.class
The content of each Car.class is different, I cannot reuse them. bar uses Spring-Mongo and foo uses Spring-JPA, and repositories are initialised via #EnableMongoRepositories and #EnableJpaRepositories annotations. When in one of my application component I try to access the foo version of the repository:
#Resource
private com.foo.repository.CarRepository carRepository;
I have the following exception when the class containing the #Resource field is created:
Caused by: org.springframework.beans.factory.BeanNotOfRequiredTypeException: Bean named 'carRepository' must be of type [com.foo.repository.CarRepository], but was actually of type [com.sun.proxy.$Proxy31]
at org.springframework.beans.factory.support.AbstractBeanFactory.doGetBean(AbstractBeanFactory.java:374)
at org.springframework.beans.factory.support.AbstractBeanFactory.getBean(AbstractBeanFactory.java:198)
at org.springframework.context.annotation.CommonAnnotationBeanPostProcessor.autowireResource(CommonAnnotationBeanPostProcessor.java:446)
at org.springframework.context.annotation.CommonAnnotationBeanPostProcessor.getResource(CommonAnnotationBeanPostProcessor.java:420)
at org.springframework.context.annotation.CommonAnnotationBeanPostProcessor$ResourceElement.getResourceToInject(CommonAnnotationBeanPostProcessor.java:545)
at org.springframework.beans.factory.annotation.InjectionMetadata$InjectedElement.inject(InjectionMetadata.java:155)
at org.springframework.beans.factory.annotation.InjectionMetadata.inject(InjectionMetadata.java:87)
at org.springframework.context.annotation.CommonAnnotationBeanPostProcessor.postProcessPropertyValues(CommonAnnotationBeanPostProcessor.java:305)
... 26 more
It appears that Spring tries to convert a bar repository to a foo repository, instead of creating a new bean, as in the same stack I also have the following exception:
Caused by: java.lang.IllegalStateException: Cannot convert value of type [com.sun.proxy.$Proxy31 implementing com.bar.repository.CarRepository,org.springframework.data.repository.Repository,org.springframework.aop.SpringProxy,org.springframework.aop.framework.Advised] to required type [com.foo.repository.CarRepository]: no matching editors or conversion strategy found
at org.springframework.beans.TypeConverterDelegate.convertIfNecessary(TypeConverterDelegate.java:267)
at org.springframework.beans.TypeConverterDelegate.convertIfNecessary(TypeConverterDelegate.java:93)
at org.springframework.beans.TypeConverterSupport.doConvert(TypeConverterSupport.java:64)
... 35 more
If I try instead to autowire the repository:
#Autowire
private com.foo.repository.CarRepository carRepository;
I get the following exception:
Caused by: org.springframework.beans.factory.BeanCreationException: Could not autowire field: private com.foo.CarRepository com.shell.ShellApp.carRepository; nested exception is org.springframework.beans.factory.NoSuchBeanDefinitionException: No qualifying bean of type [com.foo.CarRepository] found for dependency: expected at least 1 bean which qualifies as autowire candidate for this dependency. Dependency annotations: {#org.springframework.beans.factory.annotation.Autowired(required=true)}
at org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor$AutowiredFieldElement.inject(AutowiredAnnotationBeanPostProcessor.java:509)
at org.springframework.beans.factory.annotation.InjectionMetadata.inject(InjectionMetadata.java:87)
at org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor.postProcessPropertyValues(AutowiredAnnotationBeanPostProcessor.java:290)
... 26 more
Spring-data configuration
In foo (JPA) package, JPAConfigration.class:
#Configuration
#EnableJpaRepositories(basePackageClasses = RepoFooMarker.class)
public class JPAConfiguration {
#Autowired
public DataSource dataSource;
#Autowired
public EntityManagerFactory entityManagerFactory;
#Bean
public EntityManager entityManager(final EntityManagerFactory entityManagerFactory) {
return entityManagerFactory.createEntityManager();
}
#Bean
public Session session(final EntityManager entityManager)
{
return entityManager.unwrap(Session.class);
}
#Bean
public PlatformTransactionManager transactionManager() throws SQLException {
final JpaTransactionManager txManager = new JpaTransactionManager();
txManager.setEntityManagerFactory(entityManagerFactory);
return txManager;
}
#Bean
public HibernateExceptionTranslator hibernateExceptionTranslator() {
return new HibernateExceptionTranslator();
}
}
SpecEntityManagerFactory.class:
#Configuration
public class SpecEntityManagerFactory {
#Bean
public EntityManagerFactory entityManagerFactory(final DataSource dataSource) throws SQLException {
final HibernateJpaVendorAdapter vendorAdapter = new HibernateJpaVendorAdapter();
vendorAdapter.setGenerateDdl(false);
vendorAdapter.setDatabase(Database.POSTGRESQL);
final LocalContainerEntityManagerFactoryBean factory = new LocalContainerEntityManagerFactoryBean();
factory.setJpaVendorAdapter(vendorAdapter);
factory.setPackagesToScan("com.foo.entity");
factory.setJpaProperties(getHibernateProperties());
factory.setDataSource(dataSource);
factory.afterPropertiesSet();
return factory.getObject();
}
private Properties getHibernateProperties()
{
final Properties hibernateProperties = new Properties();
hibernateProperties.setProperty("hibernate.temp.use_jdbc_metadata_defaults", "false");
return hibernateProperties;
}
}
In bar (MongoDB) package, MongoConfiguration.class:
#Configuration
#EnableMongoRepositories(basePackageClasses = RepoBarMarker.class)
public class MongoConfiguration extends AbstractRepoConfig {
#Override
#Bean
public MongoOperations mongoTemplate() {
final MongoClient mongo = this.getMongoClient();
final MongoClientURI mongoUri = this.getMongoClientUri();
final MongoTemplate mongoTemplate = new MongoTemplate(mongo, mongoUri.getDatabase());
mongoTemplate.setReadPreference(ReadPreference.secondaryPreferred());
mongoTemplate.setWriteConcern(WriteConcern.UNACKNOWLEDGED);
return mongoTemplate;
}
}
Question
If I change in foo repository the entity name to CarFoo.class and the repository to CarFooRepository.class, then everything works. But is there away to avoid renaming them and still have a real wiring per type, instead of name (as it is what seems to be done here), for Spring Data repositories?
In your case, you can use
#Repository("fooCarRepository")
on the interface declaration of
com.foo.repository.CarRepository
Although when using Spring Data #Repository is not generally needed on the interface, however in your case you need to supply it. That's because you need to make Spring register the implementation of the bean with a custom name (in this case fooCarRepository) in order to avoid the name collision.

Resources