When configuring multiple datasources in Spring Boot and Mybatis project, following Exception occurs:
org.springframework.beans.factory.NoUniqueBeanDefinitionException: No
qualifying bean of type
'org.springframework.transaction.PlatformTransactionManager'
available: expected single matching bean but found 2:
primaryTx,secondTx at
org.springframework.beans.factory.support.DefaultListableBeanFactory.resolveNamedBean(DefaultListableBeanFactory.java:1041)
~[spring-beans-4.3.13.RELEASE.jar:4.3.13.RELEASE] at
org.springframework.beans.factory.support.DefaultListableBeanFactory.getBean(DefaultListableBeanFactory.java:345)
~[spring-beans-4.3.13.RELEASE.jar:4.3.13.RELEASE] at
org.springframework.beans.factory.support.DefaultListableBeanFactory.getBean(DefaultListableBeanFactory.java:340)
~[spring-beans-4.3.13.RELEASE.jar:4.3.13.RELEASE] at
org.springframework.transaction.interceptor.TransactionAspectSupport.determineTransactionManager(TransactionAspectSupport.java:384)
~[spring-tx-4.3.13.RELEASE.jar:4.3.13.RELEASE] at
org.springframework.transaction.interceptor.TransactionAspectSupport.invokeWithinTransaction(TransactionAspectSupport.java:272)
~[spring-tx-4.3.13.RELEASE.jar:4.3.13.RELEASE] at
org.springframework.transaction.interceptor.TransactionInterceptor.invoke(TransactionInterceptor.java:96)
~[spring-tx-4.3.13.RELEASE.jar:4.3.13.RELEASE] at
org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:179)
~[spring-aop-4.3.13.RELEASE.jar:4.3.13.RELEASE] at
org.springframework.aop.framework.JdkDynamicAopProxy.invoke(JdkDynamicAopProxy.java:213)
~[spring-aop-4.3.13.RELEASE.jar:4.3.13.RELEASE] at
com.sun.proxy.$Proxy86.findByDomain(Unknown Source) ~[na:na]
start of project
#SpringBootApplication( exclude = {
DataSourceAutoConfiguration.class,
DataSourceTransactionManagerAutoConfiguration.class
})
#EnableTransactionManagement
public class BookSystemApplication {
}
datasource configuration
#Configuration
public class DataSourceConfig {
#Bean(name = "primaryDataSource")
#ConfigurationProperties(prefix = "spring.datasource.primary")
public DataSource primaryDataSource() {
return DataSourceBuilder.create().build();
}
#Bean(name = "secondDataSource")
#ConfigurationProperties(prefix = "spring.datasource.second")
public DataSource secondDataSource() {
return DataSourceBuilder.create().build();
}
}
Transaction
#Configuration
public class TransactionConfig {
#Autowired
#Qualifier("primaryDataSource")
private DataSource primary;
#Autowired
#Qualifier("secondDataSource")
private DataSource second;
#Bean(name="primaryTx")
public PlatformTransactionManager primaryTransaction() {
return new DataSourceTransactionManager(primary);
}
#Bean(name="secondTx")
public PlatformTransactionManager secondTransaction() {
return new DataSourceTransactionManager(second);
}
}
The problem here is that you are defining two beans as datasource and two beans as TransactionManager but you didn't specify which one of them is the primary one, this won't work because Spring needs one datasource bean and one TransactionManager bean to be defined as primary if more than one are defined.
What you should do here is to define, one of your datasources beans and one of your TransactionManager beans as Primary, so that Spring can run correctly, to do so you will need to use #Primary annotation.
#Bean(name = "primaryDataSource")
#Primary
#ConfigurationProperties(prefix = "spring.datasource.primary")
public DataSource primaryDataSource() {
return DataSourceBuilder.create().build();
}
Please refer to the Spring's Configure two datasources section from the Documentation.
First,thanks √ answer,it solved my problem perfectly.
And I can offer another way of thinking.Unnecessary #Primary to someone,you can define like #Transactional("primaryTx") in your service.
like this:
#Override
#Transactional("primaryTx")
public Test update() {
Test entity = new Test();
entity.setId(1L);
entity.setPhone("19900000050");
return testRepository.save(entity);
}
Related
I have a Spring Framework 5.3.7 application, NOT Spring Boot (I'll move to Spring Boot later). I am using Java Configuration and it is working well so far. I have a multi-maven module project, and the first module is "myapp-entity". There is a config directory with the following file:
#Configuration
#PropertySource(value = "file:/opt/myapp/myapp-ws.properties")
#EnableTransactionManagement
public class AppEntityConfiguration
{
#Value("${hibernate.connection.driver.class}")
private String driverClassName;
#Value("${hibernate.connection.url}")
private String connectionUrl;
#Value("${hibernate.connection.username}")
private String username;
#Value("${hibernate.connection.password}")
private String password;
#Bean
public LocalSessionFactoryBean sessionFactory()
{
LocalSessionFactoryBean sessionFactory = new LocalSessionFactoryBean();
sessionFactory.setDataSource(dataSource());
sessionFactory.setPackagesToScan("com.app.model");
sessionFactory.setHibernateProperties(hibernateProperties());
return sessionFactory;
}
#Bean
public DataSource dataSource()
{
DriverManagerDataSource dataSource = new DriverManagerDataSource();
dataSource.setDriverClassName(driverClassName);
dataSource.setUrl(connectionUrl);
dataSource.setUsername(username);
dataSource.setPassword(password);
return dataSource;
}
#Bean
public PlatformTransactionManager hibernateTransactionManager()
{
HibernateTransactionManager transactionManager = new HibernateTransactionManager();
transactionManager.setSessionFactory(sessionFactory().getObject());
return transactionManager;
}
private final Properties hibernateProperties()
{
Properties hibernateProperties = new Properties();
hibernateProperties.setProperty("hibernate.hbm2ddl.auto", "none");
hibernateProperties.setProperty("hibernate.dialect", "org.hibernate.dialect.H2Dialect");
return hibernateProperties;
}
}
The myapp-entity.jar compiles fine with Java 11, and this is fine. The next maven module which is myapp-dao has a config directory and a configuration class.
#Configuration
#Import(AppEntityConfiguration.class)
#ComponentScan(basePackages = "com.app.dao")
public class RepositoryContextConfiguration {
}
I have an TableOneEntity defined with an #Entity annotation in the app-entity.jar and that is fine.
I have a TableOneDao and TableOneDaoImpl defined with a very basic list of functions.
public interface TableOneDao
{ ... list of functions }
#Repositories("tableOneDao")
public class TableOneDaoImpl implements TableOneDao
{ ... iplementation of functions }
And the test for this works perfectly:
#RunWith(SpringJUnit4ClassRunner.class)
#Transactional
#PropertySource(value = "file:/opt/myapp/myapp-ws.properties")
#ContextConfiguration(classes = RepositoryContextConfiguration.class)
public class OrganizationDaoTest extends BaseDaoTests
{
#Autowired
private TableOneDao tableOneDao;
}
This is very much the old way I used to do things, and it wall worked well. NOW, I want to get rid of this old way of doing things and go with a new way of doing things. Maybe I can't do them in the same project, that could be the issue.
I have a second Entity (TableTwoEntity) and a second Repository (TableTwoDao):
#Repository("clothesDryerDao")
public interface ClothesDryerDao extends JpaRepository<ClothesDryer, Long>
{
}
This is now a JPA repository and when I do a simple on this Dao, it cannot find THIS Dao.
#RunWith(SpringJUnit4ClassRunner.class)
#Transactional
#PropertySource(value = "file:/opt/ekotrope/ekotrope-ws.properties")
#ContextConfiguration(classes = RepositoryContextConfiguration.class)
public class ClothesDryerDaoTest extends TestCase
{
#Autowired
private ClothesDryerDao clothesDryerDao;
}
And I get the error as follows:
Caused by: org.springframework.beans.factory.NoSuchBeanDefinitionException: No qualifying bean of type 'com.ekotrope.dao.ClothesDryerDao' available: expected at least 1 bean which qualifies as autowire candidate. Dependency annotations: {#org.springframework.beans.factory.annotation.Autowired(required=true)}
at org.springframework.beans.factory.support.DefaultListableBeanFactory.raiseNoMatchingBeanFound(DefaultListableBeanFactory.java:1790)
at org.springframework.beans.factory.support.DefaultListableBeanFactory.doResolveDependency(DefaultListableBeanFactory.java:1346)
at org.springframework.beans.factory.support.DefaultListableBeanFactory.resolveDependency(DefaultListableBeanFactory.java:1300)
at org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor$AutowiredFieldElement.resolveFieldValue(AutowiredAnnotationBeanPostProcessor.java:657)
... 30 more
I thought I just need to modify the RepositoryContextConfiguration as follows:
#Configuration
#Import(EkotropeEntityConfiguration.class)
#ComponentScan(basePackages = "com.ekotrope.dao")
#EnableJpaRepositories(basePackages = "com.ekotrope.dao")
public class RepositoryContextConfiguration
{
}
But this did not work. As a matter of fact I think it broke the other working tests. So, the question is ... can I use both these methods (Dao and DaoImpl) and (JPA Dao)? Or, should I use only one, and if that was the case I would go with the JPA Repositories. I just wanted to be able to demonstrate both methods to my co-workers who are not familiar with Spring.
So, if I can get both to work at the same time, that would be great, but if not, then I can create one more Maven Module and then I will have a myapp-dao-old.jar and myapp-dao-new.jar.
Thanks for the help!
I think your jpaRepositories config is wrong but yout dao is good.
And I think this 2 solutions can work together.
Firtsly you don't need to put an #Repository on the ClothesDryerDao interface. (I understand you want to use a qualifier but i don't think this will work will not be necessary. (Type is enough for spring injection if you don't have multiple instance of a same class)
Secondly I think you need to change your Jpa configuration.
In a project i've done something like this
#Configuration
#EnableJpaRepositories(
basePackageClasses = ClothesDryerDao.class,
entityManagerFactoryRef = "configEntityManager",
)
public class RepositoryContextConfiguration{ /
//be careful this bean can maybe be in conflict with your sessionFactory
#Bean
public LocalContainerEntityManagerFactoryBean configEntityManager(DataSource dataSource) {
log.info("Start Parametrage Arcole Entity Manager");
LocalContainerEntityManagerFactoryBean em = new LocalContainerEntityManagerFactoryBean();
em.setDataSource(dataSource);
em.setPackagesToScan("com.app.model");
HibernateJpaVendorAdapter vendorAdapter = new HibernateJpaVendorAdapter();
em.setJpaVendorAdapter(vendorAdapter);
return em;
}
}
Thirdly i think (this one is juste my opinion) you should maybe not mix classical Dao and Repository in the same package. For a better visibility :)
Even if the usage is the same
This question already has answers here:
implement dynamically datasource in spring data jpa
(2 answers)
Closed 2 years ago.
I'm writing a Microservice using SpringBoot and I have a requirement to select the datasource dynamically. I will select the datasource based on the parameter. Each datasources will point to Oracle Database that has the same schema (same tables, triggers, stored procedures and etc). How can I implement this requirement?
Configure all datasources at startup, then:
either:
a. Have a different repo class that implements each datasource, check the parameter before calling the corresponding repo.
b. Have one repo class that checks the parameter and uses the corresponding datasource for its queries.
I did implement something similar...
I used application.properties to store datasource connection
Datasource drive name
spring.datasource.driver-class-name=
spring.datasource.url=
spring.datasource.username=
spring.datasource.password=
there is some code example on how to read and write to properties
https://github.com/evandbrown/amediamanager/blob/d42228a924cfbf14832e774a77c03eb0e9c2dba1/src/main/java/com/amediamanager/config/ConfigurationProviderChain.java
create an endpoint to update your properties
#PutMapping("/update")
public ResponseEntity<?> updateConnection(#RequestBody final List<ConfigurationProperty> properies) {
LOGGER.trace("Updating data source properties ");
for (final ConfigurationProperty configurationProperty : properies) {
config.getConfigurationProvider().persistDatabaseProperty(configurationProperty.getPropertyName(),
configurationProperty.getPropertyValue());
}
}
}
return new ResponseEntity<> (HttpStatus.OK);
}
One more tip from experience, ping the connection before executing update endpoint. :)
The only conclusion with my implementation, the user will need to restart the server for making changes to pickup.
You must implement two configuration datasource in your application.properties and after that configure two EntityManagerFactory and TransactionManager. You can switch between both datasources using diferent Repositories for every one.
application.properties
first.datasource.url=jdbc:oracle:thin:#//host:1521/firstdb
first.datasource.username=first
first.datasource.password=first
first.datasource.driver-class-name=oracle.jdbc.OracleDriver
second.datasource.url=jdbc:oracle:thin:#//host:1521/firstdb
second.datasource.username=second
second.datasource.password=second
second.datasource.driver-class-name=oracle.jdbc.OracleDriver
spring.jpa.database=default
Two packages to every Entity and repository and two configuration to setup.
First:
#Configuration
#EnableTransactionManagement
#EnableJpaRepositories(
entityManagerFactoryRef = "entityManagerFactory",
basePackages = { "com.system.first.repo" }
)
public class FirstDbConfig {
#Primary
#Bean(name = "dataSource")
#ConfigurationProperties(prefix = "first.datasource")
public DataSource dataSource() {
return DataSourceBuilder.create().build();
}
#Primary
#Bean(name = "entityManagerFactory")
public LocalContainerEntityManagerFactoryBean
entityManagerFactory(
EntityManagerFactoryBuilder builder,
#Qualifier("dataSource") DataSource dataSource
) {
return builder
.dataSource(dataSource)
.packages("com.system.first.domain")
.persistenceUnit("first")
.build();
}
#Primary
#Bean(name = "transactionManager")
public PlatformTransactionManager transactionManager(
#Qualifier("entityManagerFactory") EntityManagerFactory
entityManagerFactory
) {
return new JpaTransactionManager(entityManagerFactory);
}
}
Second:
#Configuration
#EnableTransactionManagement
#EnableJpaRepositories(
entityManagerFactoryRef = "secondEntityManagerFactory",
transactionManagerRef = "secondTransactionManager",
basePackages = { "com.system.second.repo" }
)
public class SecondDbConfig {
#Bean(name = "secondDataSource")
#ConfigurationProperties(prefix = "second.datasource")
public DataSource dataSource() {
return DataSourceBuilder.create().build();
}
#Bean(name = "secondEntityManagerFactory")
public LocalContainerEntityManagerFactoryBean
secondEntityManagerFactory(
EntityManagerFactoryBuilder builder,
#Qualifier("secondDataSource") DataSource dataSource
) {
return
builder
.dataSource(dataSource)
.packages("com.system.second.domain")
.persistenceUnit("second")
.build();
}
#Bean(name = "secondTransactionManager")
public PlatformTransactionManager secondTransactionManager(
#Qualifier("secondEntityManagerFactory") EntityManagerFactory
secondEntityManagerFactory
) {
return new JpaTransactionManager(secondEntityManagerFactory);
}
}
try this AbstractRoutingDataSource in springboot.
https://spring.io/blog/2007/01/23/dynamic-datasource-routing/
I want to initialize two DataSource in my app, as follows:
#Configuration
public class DataSourceConfig {
#Bean
#Primary
#ConfigurationProperties(prefix="spring.datasource")
public DataSource primaryDataSource() {
return DataSourceBuilder.create().build();
}
#Bean
#ConfigurationProperties(prefix="spring.datasource2")
public DataSource secondaryDataSource() {
return DataSourceBuilder.create().build();
}
}
Now I want to use the secondary datasource explicit as follows:
public class SecondaryDbService {
#Autowired
private EntityManager em;
#Autowired
private SecondaryCrudRepository dao;
}
interface SecondaryCrudRepository implements CrudRepository<SecondaryEntity, Long> {
}
If configured as above, the service would use the primary datasource.
Question: how can I tell the CrudRepository to rely on the "secondaryDataSource"? And likewise, how can I inject the EntityManager from the "secondaryDataSource"?
If you want to use multiple datasources, the key is to have the configurations for each Datasource in different packages. You will need to separate your entities between these packages according to which datasource they should access.
You will also have to implement both entity and transaction managers for each datasource in these packages.
To much theory ? in practical it would look something like this:
com.package1
- com.package1.entities
- EntityClass1.java (annotated with #Entity)
- ConfigForDataSource1.java
com.package2
- com.package2.entities
- EntityClass2.java (annotated with #Entity)
- ConfigForDataSource2.java
Here's how ConfigForDataSource1 would look like:
#Configuration
#EnableJpaRepositories(entityManagerFactoryRef = "entityManagerDataSource1",
basePackages = "com.package1",
transactionManagerRef = "TransactionManagerDataSource1")
public class MasterDBConfig {
#Bean(name="DataSource1")
#ConfigurationProperties(prefix = "datasource1.datasource")
public DataSource dataSource() {
return DataSourceBuilder.create().build();
}
#Bean(name="entityManagerDataSource1")
public LocalContainerEntityManagerFactoryBean entityManagerDataSource1(EntityManagerFactoryBuilder builder,#Qualifier("DataSource1") DataSource dataSource) {
return builder.dataSource(dataSource).packages("com.package1").persistenceUnit("DataSource1").build();
}
#Bean(name = "TransactionManagerDataSource1")
public PlatformTransactionManager TransactionManagerDataSource1(#Qualifier("entityManagerDataSource1") EntityManagerFactory entityManagerFactory) {
return new JpaTransactionManager(entityManagerFactory);
}
}
Then do the same for package 2 and enjoy.
Good luck !
I have my multi-module project structured like this:
project-example (parent)
-- project-example-domain: contains DAOs and Services
-- project-example-service: contains Controllers
Service module has domain as dependency, so service knows domain, but not the other way around.
So, for testing ClientDAO integrity (in this case), i have this test:
#RunWith(SpringJUnit4ClassRunner.class)
#SpringApplicationConfiguration(classes = DAOConfiguration.class)
public class ClientDAOTests {
#Autowired
private ClientDAO dao;
#Test
public void testFindClient() {
...
}
}
When i run it, i get this exception:
Caused by: org.springframework.beans.factory.NoSuchBeanDefinitionException: No qualifying bean of type [javax.sql.DataSource] 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.support.DefaultListableBeanFactory.raiseNoSuchBeanDefinitionException(DefaultListableBeanFactory.java:1301)
at org.springframework.beans.factory.support.DefaultListableBeanFactory.doResolveDependency(DefaultListableBeanFactory.java:1047)
at org.springframework.beans.factory.support.DefaultListableBeanFactory.resolveDependency(DefaultListableBeanFactory.java:942)
at org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor$AutowiredFieldElement.inject(AutowiredAnnotationBeanPostProcessor.java:533)
... 51 more
And finally my DAOConfiguration
#Configuration
#PropertySource("classpath:persistence.properties")
#EnableJpaRepositories(basePackages = {"com.example.movies.domain.feature"})
#EnableTransactionManagement
public class DAOConfiguration {
#Autowired
private Environment env;
#Autowired
private DataSource dataSource;
#Bean
public LocalContainerEntityManagerFactoryBean entityManagerFactory(EntityManagerFactoryBuilder builder) {
LocalContainerEntityManagerFactoryBean entityManager = new LocalContainerEntityManagerFactoryBean();
entityManager.setDataSource(this.dataSource);
entityManager.setPackagesToScan(new String[] {"com.example.movies.domain"});
JpaVendorAdapter vendorAdapter = new HibernateJpaVendorAdapter();
entityManager.setJpaVendorAdapter(vendorAdapter);
return entityManager;
}
#Bean
public PlatformTransactionManager transactionManager(EntityManagerFactory emf) {
JpaTransactionManager transactionManager = new JpaTransactionManager();
transactionManager.setEntityManagerFactory(emf);
return transactionManager;
}
#Bean
public PersistenceExceptionTranslationPostProcessor exceptionTranslation() {
return new PersistenceExceptionTranslationPostProcessor();
}
}
When i run the server from the service it works fine, Spring makes the DataSource bean. But when i run my test in domain, i get the exception mentioned before.
So i think that my problem is that i am passing incorrect argument to #SpringApplicationConfiguration. I don't understand what do i have to pass... should i pass it the Application class with the #SpringBootApplication annotattion? In this case, this is inside Service module so domain doesn't know this class. Can you explain what does the SpringApplicationConfiguration should take as parameter that is making my test fail? Thanks in advance!
EDIT
This is my ApplicationRunner class located in service layer:
#SpringBootApplication
#Import({ServicesConfiguration.class, CustomDataBindingConfiguration.class, CustomMappingConfiguration.class})
#PropertySource(value = {"application.properties"})
public class ApplicationRunner {
public static void main(String[] args) {
SpringApplication.run(ApplicationRunner.class, args);
}
}
I resolved the very same problem.
The problem comes from #SpringApplicationConfiguration(classes = DAOConfiguration.class).
You have to reference a class annotated with #EnableAutoConfiguration for datasource to be automatically injected by Spring Boot. (this annotation is generally inherited from #SpringBootApplication)
You have several options :
Reference your application class annotated with #SpringBootApplication from #SpringApplicationConfiguration
Add #EnableAutoConfiguration to DAOConfiguration class
... (you understand the point)
I am trying to configure a couple of datasources within Spring Batch. On startup, Spring Batch is throwing the following exception:
To use the default BatchConfigurer the context must contain no more thanone DataSource, found 2
Snippet from Batch Configuration
#Configuration
#EnableBatchProcessing
public class BatchJobConfiguration {
#Primary
#Bean(name = "baseDatasource")
public DataSource dataSource() {
// first datasource definition here
}
#Bean(name = "secondaryDataSource")
public DataSource dataSource2() {
// second datasource definition here
}
...
}
Not sure why I am seeing this exception, because I have seen some xml based configuration for Spring batch that declare multiple datasources. I am using Spring Batch core version 3.0.1.RELEASE with Spring Boot version 1.1.5.RELEASE. Any help would be greatly appreciated.
You must provide your own BatchConfigurer. Spring does not want to make that decision for you
#Configuration
#EnableBatchProcessing
public class BatchConfig {
#Bean
BatchConfigurer configurer(#Qualifier("batchDataSource") DataSource dataSource){
return new DefaultBatchConfigurer(dataSource);
}
...
AbstractBatchConfiguration tries to lookup BatchConfigurer in container first, if it is not found then tries to create it itself - this is where IllegalStateException is thrown where there is more than one DataSource bean in container.
The approach to solving the problem is to prevent from creation the DefaultBatchConfigurer bean in AbstractBatchConfiguration.
To do it we hint to create DefaultBatchConfigurer by Spring container using #Component annotation:
The configuration class where #EnableBatchProcessing is placed we can annotate with #ComponentScan that scan the package that contains the empty class that is derived from DefaultBatchConfigurer:
package batch_config;
...
#EnableBatchProcessing
#ComponentScan(basePackageClasses = MyBatchConfigurer.class)
public class MyBatchConfig {
...
}
the full code of that empty derived class is here:
package batch_config.components;
import org.springframework.batch.core.configuration.annotation.DefaultBatchConfigurer;
import org.springframework.stereotype.Component;
#Component
public class MyBatchConfigurer extends DefaultBatchConfigurer {
}
In this configuration the #Primary annotation works for DataSource bean as in the example below:
#Configuration
public class BatchTestDatabaseConfig {
#Bean
#Primary
public DataSource dataSource()
{
return .........;
}
}
This works for the Spring Batch version 3.0.3.RELEASE
The simplest solution to make #Primary annotation on DataSource work might be just adding #ComponentScan(basePackageClasses = DefaultBatchConfigurer.class) along with #EnableBatchProcessing annotation:
#Configuration
#EnableBatchProcessing
#ComponentScan(basePackageClasses = DefaultBatchConfigurer.class)
public class MyBatchConfig {
I would like to provide a solution here, which is very similar to the one answered by #vanarchi, but I managed to put all the necessary configurations into one class.
For the sake of completeness, the solution here assumes that primary datasource is hsql.
#Configuration
#EnableBatchProcessing
public class BatchConfiguration extends DefaultBatchConfigurer {
#Bean
#Primary
public DataSource batchDataSource() {
// no need shutdown, EmbeddedDatabaseFactoryBean will take care of this
EmbeddedDatabaseBuilder builder = new EmbeddedDatabaseBuilder();
EmbeddedDatabase embeddedDatabase = builder
.addScript("classpath:org/springframework/batch/core/schema-drop-hsqldb.sql")
.addScript("classpath:org/springframework/batch/core/schema-hsqldb.sql")
.setType(EmbeddedDatabaseType.HSQL) //.H2 or .DERBY
.build();
return embeddedDatabase;
}
#Override
protected JobRepository createJobRepository() throws Exception {
JobRepositoryFactoryBean factory = new JobRepositoryFactoryBean();
factory.setDataSource(batchDataSource());
factory.setTransactionManager(transactionManager());
factory.afterPropertiesSet();
return (JobRepository) factory.getObject();
}
private ResourcelessTransactionManager transactionManager() {
return new ResourcelessTransactionManager();
}
//NOTE: the code below is just to provide developer an easy way to access the in-momery hsql datasource, as we configured it to the primary datasource to store batch job related data. Default username : sa, password : ''
#PostConstruct
public void getDbManager(){
DatabaseManagerSwing.main(
new String[] { "--url", "jdbc:hsqldb:mem:testdb", "--user", "sa", "--password", ""});
}
}
THREE key points in this solution:
This class is annotated with #EnableBatchProcessing and #Configuration, as well as extended from DefaultBatchConfigurer. By doing this, we instruct spring-batch to use our customized batch configurer when AbstractBatchConfiguration tries to lookup BatchConfigurer;
Annotate batchDataSource bean as #Primary, which instruct spring-batch to use this datasource as its datasource of storing the 9 job related tables.
Override protected JobRepository createJobRepository() throws Exception method, which makes the jobRepository bean to use the primary datasource, as well as use a different transactionManager instance from the other datasource(s).
The simplest solution is to extend the DefaultBatchConfigurer and autowire your datasource via a qualifier:
#Component
public class MyBatchConfigurer extends DefaultBatchConfigurer {
/**
* Initialize the BatchConfigurer to use the datasource of your choosing
* #param firstDataSource
*/
#Autowired
public MyBatchConfigurer(#Qualifier("firstDataSource") DataSource firstDataSource) {
super(firstDataSource);
}
}
Side Note (as this also deals with the use of multiple data sources): If you use autoconfig to run data initialization scripts, you may notice that it's not initializing on the datasource you'd expect. For that issue, take a look at this: https://github.com/spring-projects/spring-boot/issues/9528
You can define below beans and make sure you application.properties file has entries needed for
#Configuration
#PropertySource("classpath:application.properties")
public class DataSourceConfig {
#Primary
#Bean(name = "abcDataSource")
#ConfigurationProperties(prefix = "abc.datasource")
public DataSource dataSource() {
return DataSourceBuilder.create().type(HikariDataSource.class).build();
}
#Bean(name = "xyzDataSource")
#ConfigurationProperties(prefix = "xyz.datasource")
public DataSource xyzDataSource() {
return DataSourceBuilder.create().type(HikariDataSource.class).build();
}
}
application.properties
abc.datasource.jdbc-url=XXXXX
abc.datasource.username=XXXXX
abc.datasource.password=xxxxx
abc.datasource.driver-class-name=org.postgresql.Driver
...........
...........
...........
...........
Here you can refer: Spring Boot Configure and Use Two DataSources
First, create a custom BatchConfigurer
#Configuration
#Component
public class TwoDataSourcesBatchConfigurer implements BatchConfigurer {
#Autowired
#Qualifier("dataSource1")
DataSource dataSource;
#Override
public JobExplorer getJobExplorer() throws Exception {
...
}
#Override
public JobLauncher getJobLauncher() throws Exception {
...
}
#Override
public JobRepository getJobRepository() throws Exception {
JobRepositoryFactoryBean factory = new JobRepositoryFactoryBean();
// use the autowired data source
factory.setDataSource(dataSource);
factory.setTransactionManager(getTransactionManager());
factory.afterPropertiesSet();
return factory.getObject();
}
#Override
public PlatformTransactionManager getTransactionManager() throws Exception {
...
}
}
Then,
#Configuration
#EnableBatchProcessing
#ComponentScan("package")
public class JobConfig {
// define job, step, ...
}