Spring: Autowired or "Plain" Call by using the #Configuration annotations? - spring

i have the following JAVA supported in Spring 4.0.3 configuration, that have two DataSource und JdbcTemplate beans:
#PropertySource("classpath:db.properties")
#Configuration
public class DBConfiguration {
.....
#Autowired
Environment env;
#Bean
public DataSource internalDataSource() {
DriverManagerDataSource dataSource = new DriverManagerDataSource();
// init dataSource setters for DATABASE_1
return dataSource;
}
#Bean
public DataSource publicDataSource() {
DriverManagerDataSource dataSource = new DriverManagerDataSource();
// init dataSource setters for DATABASE_2
return dataSource;
}
}
...
#Bean
public JdbcTemplate internalJDBCTemplate() {
return new JdbcTemplate(internalDataSource());
}
#Bean
public JdbcTemplate publicJDBCTemplate() {
return new JdbcTemplate(publicDataSource());
}
___
I have other configuration bean class, that autowires the first configuration and calls internalDataSource() method:
#Import(DBConfiguration.class)
#Configuration
public class AuthConfiguration {
#Autowired
private DBConfiguration dbConfiguration;
#Autowired
private TokenStore tokenStore;
#Bean
public TokenStore tokenStore() {
return new JdbcTokenStore(dbConfiguration.securityDataSource());
}
...}
I suppose, that even DBConfiguration was imported and autowired into AuthConfiguration class, the each call of dbConfiguration.securityDataSource() will be cause the new DriverManagerDataSource() with each time intializing of data source.
Is it correkt or not?

The default bean scope in Spring is singleton so the data source will be initialized only once.
When a DI container creates bean TokenStore it gets a bean defined in DBConfiguration by the securityDataSource method. But it doesn't call the method directly, it takes a bean instance from the DI container. An initialization of all beans is done by Spring transparently to a developer.
Note that classes annotated with #Configuration are just a definition for the framework and they aren't executed directly.

Related

Why are the data sources interfering in Spring Batch when using a RepositoryItemReader?

I am trying to migrate some data between a Postgres database and MongoDB using Spring Batch. I have a very simple ItemReader, ItemProcessor, and ItemWriter configured, and it everything works as intended. However, if I switch to a RepositoryItemReader, I'm getting the following error:
java.lang.IllegalStateException: Already value [org.springframework.jdbc.datasource.ConnectionHolder#684430c1] for key [HikariDataSource (HikariPool-1)] bound to thread
If I understand correctly, there is something wrong with the EntityManager or TransactionManager, but I cannot figure out what, and why it's working with a simple ItemReader that doesn't work with a repository, but it uses the same data source.
I would be very grateful for any help.
Here is my source db configuration:
package com.example.batch.primary;
#Configuration
#EnableTransactionManagement
#EnableJpaRepositories(
entityManagerFactoryRef = "primaryEntityManagerFactory",
transactionManagerRef = "primaryTransactionManager",
basePackages = {"com.example.batch.primary"}
)
public class PrimaryDBConfig {
#Bean(name = "primaryDataSource")
#Primary
public DataSource primaryDatasource(){
DataSourceBuilder dataSourceBuilder = DataSourceBuilder.create()
.driverClassName("org.postgresql.Driver")
.url("jdbc:postgresql://localhost:5432/postgres")
.username("test")
.password("test");
return dataSourceBuilder.build();
}
#Bean(name = "primaryEntityManagerFactory")
#Primary
public LocalContainerEntityManagerFactoryBean primaryEntityManagerFactory(EntityManagerFactoryBuilder builder,
#Qualifier("primaryDataSource")
DataSource primaryDataSource){
return builder.dataSource(primaryDataSource)
.packages("com.example.batch.primary")
.build();
}
#Bean(name = "primaryTransactionManager")
public PlatformTransactionManager primaryTransactionManager(
#Qualifier("primaryEntityManagerFactory") EntityManagerFactory primaryEntityManagerFactory)
{
return new JpaTransactionManager(primaryEntityManagerFactory);
}
}
Here is the configuration of MongoDB:
package com.example.batch.secondary;
#EnableMongoRepositories(basePackages = "com.example.batch.secondary")
#Configuration
public class MongoDBConfig {
#Bean
public MongoClient mongo() {
ConnectionString connectionString = new ConnectionString("mongodb+srv://mongoadmin:blablabla.mongodb.net/?retryWrites=true&w=majority");
MongoClientSettings mongoClientSettings = MongoClientSettings.builder()
.applyConnectionString(connectionString)
.build();
return MongoClients.create(mongoClientSettings);
}
#Bean
public MongoTemplate mongoTemplate() throws Exception {
return new MongoTemplate(mongo(), "test");
}
}
Here is the RepositoryItemReader:
package com.example.batch.stepcomponents;
#Component
public class RepositoryReader extends RepositoryItemReader<Partner> {
public RepositoryReader(#Autowired PartnerRepository partnerRepository){
setRepository(partnerRepository);
setPageSize(1);
setSort(Map.of("id", Sort.Direction.ASC));
setMethodName("findAll");
}
}
Batch Config:
#Configuration
#EnableBatchProcessing
public class BatchConfig {
#Autowired
public JobBuilderFactory jobBuilderFactory;
#Autowired
public StepBuilderFactory stepBuilderFactory;
#Autowired
RepositoryReader repositoryReader;
#Autowired
CustomWriter customWriter;
#Autowired
CustomProcessor customProcessor;
#Bean
public Job createJob() {
return jobBuilderFactory.get("MyJob")
.incrementer(new RunIdIncrementer())
.flow(createStep())
.end()
.build();
}
#Bean
public Step createStep() {
return stepBuilderFactory.get("MyStep")
.<Partner, Student> chunk(1)
.reader(repositoryReader)
.processor(customProcessor)
.writer(customWriter)
.build();
}
}
So I tried taking out the EntityManagerFactory and the TransactionManager, and now it works. I guess they are already initialized automatically when starting up the server..
Yes, by default, if you provide a DataSource bean, Spring Batch will use a DataSourceTransactionManager, not the JPA one as you expect. This is explained in the Javadoc of EnableBatchProcessing:
The transaction manager provided by this annotation will be of type:
* ResourcelessTransactionManager if no DataSource is provided within the context
* DataSourceTransactionManager if a DataSource is provided within the context
In order to use the JPA transaction manager, you need to configure a custom a BatchConfigurer and override getTransactionManager, something like:
#Bean
public BatchConfigurer batchConfigurer(DataSource dataSource, EntityManagerFactory entityManagerFactory) {
return new DefaultBatchConfigurer(dataSource) {
#Override
public PlatformTransactionManager getTransactionManager() {
return new JpaTransactionManager(entityManagerFactory);
}
};
}
Note this will not be required anymore starting from v5, see:
Revisit the configuration of infrastructure beans with #EnableBatchProcessing
Spring Batch 5.0.0-M6 and 4.3.7 are out!
You can also set the JPA transaction manager on your step:
#Bean
public Step createStep(JpaTransactionManager jpaTransactionManager) {
return stepBuilderFactory.get("MyStep")
.<Partner, Student> chunk(1)
.reader(repositoryReader)
.processor(customProcessor)
.writer(customWriter)
.transactionManager(jpaTransactionManager)
.build();
}
Adding 'spring-data-jpa' as a dependency will automatically configure aJpaTransactionManager if no other TransactionManager is defined

two beans of same type collide even when given two different names and using #Qaulifier annotation, why?

here is the following code for configuring several datasources in a spring batch project
#Configuration
#RequiredArgsConstructor
public class DatasourceConfiguration {
private final Environment env;
#Bean(name = "batchDataSource")
#ConfigurationProperties(prefix = "spring.batch-datasource")
public DataSource primaryDataSource() {
return DataSourceBuilder.create().build();
}
#Bean(name = "customersDataSource")
#ConfigurationProperties(prefix = "spring.customers-datasource")
public DataSource secondaryDataSource() {
return DataSourceBuilder.create().build();
}
#Bean
BatchConfigurer configurer(#Qualifier("batchDataSource") DataSource dataSource) {
return new DefaultBatchConfigurer(dataSource);
}
}
when loading the app I get the following error:
Field dataSource in org.springframework.batch.core.configuration.annotation.AbstractBatchConfiguration required a single bean, but 2 were found:
- batchDataSource: defined by method 'primaryDataSource' in class path resource [com/comp/lr/snapshot/customers/job/config/DatasourceConfiguration.class]
- customersDataSource: defined by method 'secondaryDataSource' in class path resource [com/comp/lr/snapshot/customers/job/config/DatasourceConfiguration.class]
But why is it happening if I'm using #Qaulifier annotation?
If you want this to work create your own class AbstractBatchConfiguration and mark the datasource injection with a #qualifier (for your desired datasource bean)
Also exclude AbstractBatchConfiguration so that it does not get autoconfigured.
The reason is spring batch:
#Configuration(proxyBeanMethods = false)
#Import(ScopeConfiguration.class)
public abstract class AbstractBatchConfiguration implements ImportAware, InitializingBean {
#Autowired
private DataSource dataSource;
private BatchConfigurer configurer;
private JobRegistry jobRegistry = new MapJobRegistry();
private JobBuilderFactory jobBuilderFactory;
private StepBuilderFactory stepBuilderFactory;
class has #Autowired on the Datasource field.
So Spring does not know which one of the 2 you defined to inject.
You could also annotate the batch datasource with #primary but then where ever you don't specify a datasource qualifier the batch datasource will be injected. So if another spring class has #Autowired Datasource you still have a problem.

How to use Multiple JdbcOperations and Multiple JdbcTemplates in Spring

I have 2 different datasrouces from which I want to use in the same file and query each of them using JdbcOperations implementation. Is this possible?
#Repository
public class TestRepository {
private JdbcOperations jdbcOperations;
#Inject
#Qualifier("dataSource1")
private DataSource dataSource1;
#Inject
#Qualifier("dataSource2")
private DataSource dataSource2;
#Bean
#Qualifier("jdbcTemplate1")
public JdbcTemplate jdbcTemplate1(#Qualifier("dataSource1") DataSource dataSource) {
return new JdbcTemplate(dataSource);
}
#Bean
#Qualifier("jdbcTemplate2")
public JdbcTemplate jdbcTemplate1(#Qualifier("dataSource2") DataSource dataSource) {
return new JdbcTemplate(dataSource);
}
#Inject
public TestRepository(JdbcOperations jdbcOperations) {
this.jdbcOperations = jdbcOperations; //HOW DO I SPECIFY WHICH JDBCTEMPLATE SHOULD BE USED FOR INITIALIZING THIS JDBCOPERATIONS
}
}
Above is my code, note that JdbcOperations is initialized in the constructor. But no way to specify which jdbcTemplate should the jdbcOperations use.
The qualifier should actually be put at the parameter level:
public TestRepository(#Qualifier("jdbcTemplate2")JdbcOperations jdbcOperations) {
this.jdbcOperations = jdbcOperations;
}
Uses the bean named jdbcTemplate2

create an instance of DataSource and use inside constructor

In my current spring-boot project, I have this class:
#Component
public class OauthTokenStore extends JdbcTokenStore {
public OauthTokenStore() {
super(...);
}
}
the atribute for super(...) should be a valid datasource. I have this configuration in my application.properties:
# jdbc.X
spring.datasource.driverClassName=com.mysql.jdbc.Driver
spring.datasource.url=jdbc:mysql://localhost/lojacms
spring.datasource.username=root
spring.datasource.password=
# hibernate.X
spring.jpa.database-platform=org.hibernate.dialect.MySQL5Dialect
spring.jpa.dialect=org.hibernate.dialect.MySQL5Dialect
spring.jpa.show-sql=false
spring.jpa.hibernate.ddl-auto=validate
Is there any way to create an instance of a datasource and use in this constructor?
You need to inject a DataSource bean which you can then pass it to your OauthTokenStore constructor using #Inject.
#Component
public class OauthTokenStore extends JdbcTokenStore {
#Inject
public OauthTokenStore(DataSource ds) {
super(ds);
}
}
If you don't yet have a bean of type DataSource defined in your application you'll need one:
#Configuration
public class Config {
#Bean
public DriverManagerDataSource dataSource() {
DriverManagerDataSource dataSource = new DriverManagerDataSource();
dataSource.setDriverClassName("com.mysql.jdbc.Driver");
dataSource.setUrl("jdbc:mysql://localhost/lojacms");
dataSource.setUsername("root");
dataSource.setPassword("");
return dataSource;
}
}

Multiple transaction managers annotation configuration

I have two transaction managers configured in annotation-based configuration class:
#Configuration
#EnableTransactionManagement
public class DBConfig implements TransactionManagementConfigurer {
//...
#Override
public PlatformTransactionManager annotationDrivenTransactionManager() {
return defTransactionManager();
}
#Bean
#Qualifier("defSessionFactory")
public LocalSessionFactoryBean defSessionFactory() {
LocalSessionFactoryBean sfb = new LocalSessionFactoryBean();
sfb.setDataSource(defDataSource());
Properties props = new Properties();
//...
sfb.setHibernateProperties(props);
sfb.setPackagesToScan("my.package");
return sfb;
}
#Bean
#Qualifier("defTransactionManager")
public PlatformTransactionManager defTransactionManager() {
return new HibernateTransactionManager(defSessionFactory().getObject());
}
#Bean
#Qualifier("secondSessionFactory")
public LocalSessionFactoryBean secondSessionFactory() {
LocalSessionFactoryBean sfb = new LocalSessionFactoryBean();
sfb.setDataSource(secondDataSource());
Properties props = new Properties();
//...
sfb.setHibernateProperties(props);
sfb.setPackagesToScan("my.package.subpackage");
return sfb;
}
#Bean
#Qualifier("secondTM")
public PlatformTransactionManager secondTransactionManager() {
return new HibernateTransactionManager(secondSessionFactory().getObject());
}
}
My intention is use annotation transactions with two transaction managers.
Methonds annotated like this
#Transactional
public void method() {}
should be handled by defTransactionManager
And methods annotated like this
#Transactional("secondTM")
public void anotherMethod() {}
by secondTransactionManager
defTransactionManager works fine but when it comes to anotherMethod() I get:
org.hibernate.HibernateException: No Session found for current thread
When I use programmatic transaction management for anotherMethod (autowire secondSessionFactory, use TransactionTemplate) everything works fine.
In case of #EnableTranscationManagement Spring will use by-type lookup, you can provide your own lookup method to a single transaction manager, but it will not work for two tx managers
If you want to check how Spring determines the transaction to execute, you can try to debug the TransactionAspectSupport class. The key methods are setTransactionManagerBeanName and determineTransactionManager.
Just in case anyone runs into this problem, I found a solution:
#Configuration
#EnableTransactionManagement
#DependsOn("myTxManager")
#ImportResource("classpath:applicationContext.xml")
public class AppConfig implements TransactionManagementConfigurer {
#Autowired
private PlatformTransactionManager myTxManager;
...
#Override
public PlatformTransactionManager annotationDrivenTransactionManager() {
return this.myTxManager;
}
In this way, you can use a specific txManager defined in an xml configuration.
In case you want to define the txManager used on service-level, you shall remove the #EnableTransactionManagement annotation from the #Configuration class and specify the txManager in the #Transactional annotations, e.g.
#Service
#Transactional(value="myTxManager", readOnly = true)
public class MyServiceImpl implements MyService { ... }

Resources