Cyclic dependency when there are multiple DataSource defined - java

I wanted to declare two DataSource beans and use one of them dynamically using AbstractRoutingDataSource, which is declared as #Primary bean. Surprisingly, I was not able to run my application because of cyclic dependency:
org.springframework.boot.autoconfigure.orm.jpa.HibernateJpaConfiguration
┌─────┐
| dataSource defined in <myclass>
↑ ↓
| readOnlyDataSource defined in <myclass>
↑ ↓
| org.springframework.boot.autoconfigure.jdbc.DataSourceInitializerInvoker
└─────┘
It is cause because of this implementation:
#SpringBootApplication
public class DemoApplication {
public static void main(String[] args) {
SpringApplication.run(DemoApplication.class, args);
}
#Bean
#Primary
DataSource dataSource(#Qualifier("firstDS") DataSource firstDS,
#Qualifier("secondDS") DataSource secondDS) {
MyRoutingDataSource ds = new MyRoutingDataSource();
ds.setCurrentDS(firstDS);
return ds;
}
#Bean("firstDS")
public DataSource firstDS(DataSourceProperties properties) {
return properties.initializeDataSourceBuilder().build();
}
#Bean("secondDS")
public DataSource secondDs(DataSourceProperties properties) {
return properties.initializeDataSourceBuilder().build();
}
class MyRoutingDataSource extends AbstractRoutingDataSource {
private DataSource currentDS;
public void setCurrentDS(DataSource currentDS) {
this.currentDS = currentDS;
}
#Override
protected Object determineCurrentLookupKey() {
return currentDS;
}
}
}
Please note that I don't want to exclude DataSourceAutoConfiguration - it provides some additional functionally that I want to use in my project (e.g. DataSourceInitializer).
Could you please explain to me why it does not work? I feel that this error message is misleading. There is no cyclic dependency between HibernateJpaConfiguration and DataSourceInitializerInvoker. Both of them uses DataSource which primary definition I provide.
There is full project with that issue: https://github.com/kozub/spring-dependency-management-bug

I ran into the same problem you have, with the difference that I am not using DataSourceAutoConfiguration.
I'm not a Spring expert, so I can't tell you the root cause. But I was able to get my code to work by going from something like this, which is similar to what you posted:
#Bean
#Primary
DataSource dataSource(#Qualifier("firstDS") DataSource firstDS,
#Qualifier("secondDS") DataSource secondDS) {
MyRoutingDataSource ds = new MyRoutingDataSource();
ds.setFirstDS(firstDS);
ds.setSecondDs(secondDS);
return ds;
}
#Bean("firstDS")
public DataSource firstDS() {
return /*create first DS*/
}
#Bean("secondDS")
public DataSource secondDs(DataSourceProperties properties) {
return /*create second DS*/
}
To this:
#Bean
DataSource dataSource() {
DataSource first = /*create first DS*/
DataSource second = /*create second DS*/
MyRoutingDataSource ds = new MyRoutingDataSource();
ds.setFirstDS(first);
ds.setSecondDs(second);
return ds;
}
As you can see, I was able to solve the problem by only having one Spring bean of type DataSource. I created the two "first" and "second" DataSources inside the method which creates the routing datasource so that they don't have to be Spring beans. Having only one bean of type DataSource got rid of my circular dependency error.
This solved my problem, but you also want to use DataSourceAutoConfiguration.
I think you may be able to achieve that with something like this:
#Bean
DataSource dataSource(#Qualifier("firstDSproperties") DataSourceProperties firstDSprops,
#Qualifier("secondDSproperties") DataSourceProperties secondDSprops) {
DataSource first = firstDSprops.initializeDataSourceBuilder().build();
DataSource second = secondDSprops.initializeDataSourceBuilder().build();
MyRoutingDataSource ds = new MyRoutingDataSource();
ds.setCurrentDS(firstDS);
return ds;
}
#Bean("firstDSproperties")
#ConfigurationProperties("datasource.first")
public DataSourceProperties firstDataSourceProperties() {
return new DataSourceProperties();
}
#Bean("secondDSproperties")
#ConfigurationProperties("datasource.second")
public DataSourceProperties secondDataSourceProperties() {
return new DataSourceProperties();
}
What this code does is to make two beans of type DataSourceProperties rather than type DataSource. With DataSourceProperties beans you can still let Spring autowire your config without (hopefully) having the cyclical dependency problem caused by having multiple beans of type DataSource depending on each other.
I haven't tested doing this with DataSourceProperties since I am not using DataSourceAutoConfiguration in my code. But based on your code I think it might work.

There is slight a mistake here. Let me explain you with foo and bar DB example along with git repo reference.
Here is how your application.properties look like
# Oracle DB - "foo"
spring.datasource.url=jdbc:oracle:thin:#//db-server-foo:1521/FOO
spring.datasource.username=fooadmin
spring.datasource.password=foo123
spring.datasource.driver-class-name=oracle.jdbc.OracleDriver
# PostgreSQL DB - "bar"
bar.datasource.url=jdbc:postgresql://db-server-bar:5432/bar
bar.datasource.username=baradmin
bar.datasource.password=bar123
bar.datasource.driver-class-name=org.postgresql.Driver
Set the SQL Dialect to “default” in your application.properties to let Spring autodetect the different SQL Dialects of each datasource
spring.jpa.database=default
Package should look something like
src/main/java
- com.foobar
- foo
- domain
- repo
- bar
- domain
- repo
Here is the main part. Configuration classes
Foo Configuration class (Oracle)
package com.foobar;
#Configuration
#EnableTransactionManagement
#EnableJpaRepositories(
entityManagerFactoryRef = "entityManagerFactory",
basePackages = { "com.foobar.foo.repo" }
)
public class FooDbConfig {
#Primary
#Bean(name = "dataSource")
#ConfigurationProperties(prefix = "spring.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.foobar.foo.domain")
.persistenceUnit("foo")
.build();
}
#Primary
#Bean(name = "transactionManager")
public PlatformTransactionManager transactionManager(
#Qualifier("entityManagerFactory") EntityManagerFactory
entityManagerFactory
) {
return new JpaTransactionManager(entityManagerFactory);
}
}
Bar Configuration class (postgres)
package com.foobar;
#Configuration
#EnableTransactionManagement
#EnableJpaRepositories(
entityManagerFactoryRef = "barEntityManagerFactory",
transactionManagerRef = "barTransactionManager",
basePackages = { "com.foobar.bar.repo" }
)
public class BarDbConfig {
#Bean(name = "barDataSource")
#ConfigurationProperties(prefix = "bar.datasource")
public DataSource dataSource() {
return DataSourceBuilder.create().build();
}
#Bean(name = "barEntityManagerFactory")
public LocalContainerEntityManagerFactoryBean
barEntityManagerFactory(
EntityManagerFactoryBuilder builder,
#Qualifier("barDataSource") DataSource dataSource
) {
return
builder
.dataSource(dataSource)
.packages("com.foobar.bar.domain")
.persistenceUnit("bar")
.build();
}
#Bean(name = "barTransactionManager")
public PlatformTransactionManager barTransactionManager(
#Qualifier("barEntityManagerFactory") EntityManagerFactory
barEntityManagerFactory
) {
return new JpaTransactionManager(barEntityManagerFactory);
}
}
Your repositories would look something like
package com.foobar.bar.repo;
#Repository
public interface BarRepository extends JpaRepository<Bar, Long> {
Bar findById(Long id);
}
package com.foobar.foo.repo;
#Repository
public interface FooRepository extends JpaRepository<Foo, Long> {
Foo findById(Long id);
}
And you are done here.
You can refer code on github here

Related

Spring Boot/Spring Batch: Using and configuring two DataSource via app properties

I'm working with Spring Boot/Spring Batch, and need to provide two jdbc Data Sources.
I can't find a way to automatically load the config parameters from my application.properties.
//BatchConfiguration which uses both Data Sources:
#Configuration
#EnableBatchProcessing
public class BatchConfiguration {
#Bean
public JdbcBatchItemWriter<Customer> writer(#Qualifier("dataSourceOne") DataSource dataSource) {
return new JdbcBatchItemWriterBuilder<Customer>()
.itemSqlParameterSourceProvider(new BeanPropertyItemSqlParameterSourceProvider<>())
.sql("..."))
.dataSource(dataSource)
.build();
}
#Bean
public JdbcCursorItemReader<Customer> reader(#Qualifier("dataSourceTwo") DataSource dataSource) {
return new JdbcCursorItemReaderBuilder<Customer>()
.dataSource(dataSource)
.name("myItemReader")
.sql("...")
.rowMapper(new CustomerRowMapper())
.build();
}
Above code works with the following Configuration, which has the DataSource provided directly:
//WORKING SOLUTION
#Configuration(proxyBeanMethods = false)
public class DataSourcesConfiguration {
#Bean("dataSourceOne")
#Primary
public DataSource dataSource() {
DataSourceBuilder dataSourceBuilder = DataSourceBuilder.create();
dataSourceBuilder.url("jdbc:h2:mem:AAA-h2");
dataSourceBuilder.username("AAA");
dataSourceBuilder.password("AAA");
dataSourceBuilder.driverClassName("org.h2.Driver");
return dataSourceBuilder.build();
}
#Bean("dataSourceTwo")
public DataSource dataSourceTwo() {
DataSourceBuilder dataSourceBuilder = DataSourceBuilder.create();
dataSourceBuilder.url("jdbc:oracle:thin:#AAA");
dataSourceBuilder.username("AAA");
dataSourceBuilder.password("AAA");
dataSourceBuilder.driverClassName("oracle.jdbc.driver.OracleDriver");
return dataSourceBuilder.build();
}
}
Now if I try to create the DataSource from my application properties via annotation #ConfigurationProperties, it will not work:
//NOT WORKING
#Configuration(proxyBeanMethods = false)
public class DataSourcesConfiguration {
#Bean("dataSourceOne")
#Primary
#ConfigurationProperties(prefix="data1.datasource")
public DataSource dataSource() {
return DataSourceBuilder.create().build();
}
#Bean("dataSourceTwo")
#ConfigurationProperties(prefix="data2.datasource")
public DataSource dataSourceTwo() {
return DataSourceBuilder.create().build();
}
}
application.properties:
data1.datasource.url=jdbc:h2:mem:AAA-h2
data1.datasource.username=AAA
data1.datasource.password=AAA
data1.datasource.driverClassName=org.h2.Driver
data2.datasource.url=jdbc:oracle:thin:#AAA
data2.datasource.username=AAA
data2.datasource.password=AAA
data2.datasource.driverClassName=oracle.jdbc.driver.OracleDriver
I receive the error Unable to detect database type in the non working version.
How can I correctly configure and provide a DataSource through my properties in application.properties?
The values to use in creating a DataSource are actually part of another class DataSourceProperties
which you can also use to create a DataSource.
Following is a snippet from TaskDbConfig (this class also contains information if you need more functions).
import org.springframework.boot.autoconfigure.jdbc.DataSourceProperties;
import com.zaxxer.hikari.HikariDataSource;
#Bean
#ConfigurationProperties("data1.datasource")
public DataSourceProperties data1DataSourceProperties() {
return new DataSourceProperties();
}
#Bean
#ConfigurationProperties("data1.datasource.hikari")
public HikariDataSource data1DataSource() {
// Different types are documented at
// https://docs.spring.io/spring-boot/docs/current/api/org/springframework/boot/jdbc/DataSourceBuilder.html
return data1DataSourceProperties().initializeDataSourceBuilder()
.type(HikariDataSource.class).build();
}
application.yml
data1.datasource.platform: h2
data1.datasource.url: 'jdbc:h2:mem:test'
# Hikari config properties: https://github.com/brettwooldridge/HikariCP#configuration-knobs-baby
data1.datasource.hikari.minimumIdle: 1
data1.datasource.hikari.maximumPoolSize: 4

How to connect multiple databases in Spring Boot

I want to develop an application to connect to multiple oracle databases and fetch data from same table from each database.
I have tried using the multiple datasource for every database and fetch the data from the tables.
Resource: https://medium.com/#joeclever/using-multiple-datasources-with-spring-boot-and-spring-data-6430b00c02e7
DB1 Config:
#Configuration
#EnableTransactionManagement
#EnableJpaRepositories(entityManagerFactoryRef = "CU1EntityManagerFactory", basePackages = {
"com.javatechie.multiple.ds.api.cu1.repository" })
public class CU1Config {
#Primary
#Bean(name = "CU1DataSource")
#ConfigurationProperties(prefix = "spring.cu1.datasource")
public DataSource dataSource() {
return DataSourceBuilder.create().build();
}
#Primary
#Bean(name = "CU1EntityManagerFactory")
public LocalContainerEntityManagerFactoryBean CU1EntityManagerFactory(EntityManagerFactoryBuilder builder,
#Qualifier("CU1DataSource") DataSource dataSource) {
HashMap<String, Object> properties = new HashMap<>();
properties.put("hibernate.hbm2ddl.auto", "update");
properties.put("hibernate.dialect", "org.hibernate.dialect.Oracle12cDialect");
return builder.dataSource(dataSource).properties(properties)
.packages("com.javatechie.multiple.ds.api.model").persistenceUnit("Unit").build();
}
}
DB2 Config:
#Configuration
#EnableTransactionManagement
#EnableJpaRepositories(entityManagerFactoryRef = "CU2EntityManagerFactory", basePackages = {
"com.javatechie.multiple.ds.api.cu2.repository" })
public class CU2Config {
#Bean(name = "CU2DataSource")
#ConfigurationProperties(prefix = "spring.cu2.datasource")
public DataSource dataSource() {
return DataSourceBuilder.create().build();
}
#Bean(name = "CU2EntityManagerFactory")
public LocalContainerEntityManagerFactoryBean CU2EntityManagerFactory(EntityManagerFactoryBuilder builder,
#Qualifier("CU2DataSource") DataSource dataSource) {
HashMap<String, Object> properties = new HashMap<>();
properties.put("hibernate.hbm2ddl.auto", "update");
properties.put("hibernate.dialect", "org.hibernate.dialect.Oracle12cDialect");
return builder.dataSource(dataSource).properties(properties)
.packages("com.javatechie.multiple.ds.api.model").persistenceUnit("Unit").build();
}
}
But the issue that I am facing is for every datasource I have to create a separate config class and also a dedicated repository and service, which is code repeatability though the work from each DB is same to fetch data from a particular table.
And also in future if I want to add any new database I have to repeat the same above process again.
Any help regarding better approach for code reusability is highly appreciated.
You can do it directly through the properties file
spring.datasource.jdbcUrl = [url]
spring.datasource.username = [username]
spring.datasource.password = [password]
spring.second-datasource.jdbcUrl = [url]
spring.second-datasource.username = [username]
spring.second-datasource.password = [password]
Then you need to create the configurations for the datasources:
#Configuration
#PropertySource({"classpath:persistence-multiple-db-boot.properties"})
#EnableJpaRepositories(
basePackages = "com.baeldung.multipledb.dao.user",
entityManagerFactoryRef = "userEntityManager",
transactionManagerRef = "userTransactionManager")
public class PersistenceUserAutoConfiguration {
#Primary
#Bean
#ConfigurationProperties(prefix="spring.datasource")
public DataSource userDataSource() {
return DataSourceBuilder.create().build();
}
// userEntityManager bean
// userTransactionManager bean
}
The config for the second one:
#Configuration
#PropertySource({"classpath:persistence-multiple-db-boot.properties"})
#EnableJpaRepositories(
basePackages = "com.baeldung.multipledb.dao.product",
entityManagerFactoryRef = "productEntityManager",
transactionManagerRef = "productTransactionManager")
public class PersistenceProductAutoConfiguration {
#Bean
#ConfigurationProperties(prefix="spring.second-datasource")
public DataSource productDataSource() {
return DataSourceBuilder.create().build();
}
// productEntityManager bean
// productTransactionManager bean
}
You can find more about this implementation here
If you are going to create a serious service, you should know only how to config them, how to use them, and how to control transaction for all of databases via Spring.
You can see the runnable example and some explanation in https://github.com/surasint/surasint-examples/tree/master/spring-boot-jdbi/10_spring-boot-two-databases (see what you can try in README.txt)
I copied some code here.
First you have to set application.properties like this
#Database
database1.datasource.url=jdbc:mysql://localhost/testdb
database1.datasource.username=root
database1.datasource.password=root
database1.datasource.driver-class-name=com.mysql.jdbc.Driver
database2.datasource.url=jdbc:mysql://localhost/testdb2
database2.datasource.username=root
database2.datasource.password=root
database2.datasource.driver-class-name=com.mysql.jdbc.Driver
Then define them as providers (#Bean) like this:
#Bean(name = "datasource1")
#ConfigurationProperties("database1.datasource")
#Primary
public DataSource dataSource(){
return DataSourceBuilder.create().build();
}
#Bean(name = "datasource2")
#ConfigurationProperties("database2.datasource")
public DataSource dataSource2(){
return DataSourceBuilder.create().build();
}
Note that I have #Bean(name="datasource1") and #Bean(name="datasource2"), then you can use it when we need datasource as #Qualifier("datasource1") and #Qualifier("datasource2") , for example
#Qualifier("datasource1")
#Autowired
private DataSource dataSource;
If you do care about transaction, you have to define DataSourceTransactionManager for both of them, like this:
#Bean(name="tm1")
#Autowired
#Primary
DataSourceTransactionManager tm1(#Qualifier ("datasource1") DataSource datasource) {
DataSourceTransactionManager txm = new DataSourceTransactionManager(datasource);
return txm;
}
#Bean(name="tm2")
#Autowired
DataSourceTransactionManager tm2(#Qualifier ("datasource2") DataSource datasource) {
DataSourceTransactionManager txm = new DataSourceTransactionManager(datasource);
return txm;
}
Then you can use it like
#Transactional //this will use the first datasource because it is #primary
or
#Transactional("tm2")
The most important part, which you will hardly find an example in anywhere: if you want a method to commit/rollback transactions of both databases, you need ChainedTransactionManager for tm1 and tm2 , like this:
#Bean(name = "chainedTransactionManager")
public ChainedTransactionManager getChainedTransactionManager(#Qualifier ("tm1") DataSourceTransactionManager tm1, #Qualifier ("tm2") DataSourceTransactionManager tm2){
return new ChainedTransactionManager(tm1, tm2);
}
To use it, add this annotation in a method #Transactional(value="chainedTransactionManager") for example
#Transactional(value="chainedTransactionManager")
public void insertAll() {
UserBean test = new UserBean();
test.setUsername("username" + new Date().getTime());
userDao.insert(test);
userDao2.insert(test);
}
This should be enough. See example and detail in the link above.

Spring boot multiple external datasource stored in internal database

I have a spring boot project and I have one internal database with the configuration on the application.properties. In this database I have a Company table which contains the connection informations to external databases (all the external databases have same structure).
I created a class which create datasource when we need :
public class PgDataSource {
private static Map<Long, DataSource> dataSourceMap = new HashMap<>();
private static void createDataSource(Company company) {
HikariConfig hikariConfig = new HikariConfig();
hikariConfig.setMaximumPoolSize(10);
hikariConfig.setMinimumIdle(1);
hikariConfig.setJdbcUrl("jdbc:postgresql://"+company.getUrl()+"/"+company.getIdClient());
hikariConfig.setUsername(company.getUsername());
hikariConfig.setPassword(company.getPassword());
dataSourceMap.put(company.getId(), new HikariDataSource(hikariConfig));
}
public static DataSource getDataSource(Company company) {
if (!dataSourceMap.containsKey(company.getId()))
createDataSource(company);
return dataSourceMap.get(company.getId());
}
}
Could you tell me if this solution is the best and if I can use JPA with this solution ?
Thanks
The difficulty in your setup is not multiple datasources, but the fact that they are dynamic, i.e. determined at run time.
In addition to a DataSource JPA uses EntityManagerFactory and TransactionManager, which are determined statically i.e. at compile time. So it's not easy to use JPA with dynamic data sources.
In Spring Boot 2 you can try the AbstractRoutingDataSource, which allows to route JPA calls to a different datasource based on some (thread-bound) context. Here's an example of how it can be used and a demo application.
Alternatively you can turn your setup into a static setup, then use the regular multiple datasource approach. The downside is that the list of "companies" will be fixed at compile time, and so is probably not what you want.
Thanks it's work fine !
My solution :
I create a first datasource for my local database with #Primary annotation.
#Configuration
#EnableTransactionManagement
#EnableJpaRepositories(
entityManagerFactoryRef = "localEntityManagerFactory",
basePackages = {"fr.axygest.akostaxi.local"}
)
public class LocalConfig {
#Primary
#Bean(name = "dataSource")
#ConfigurationProperties(prefix = "spring.datasource")
public DataSource dataSource() {
return DataSourceBuilder.create().build();
}
#Primary
#Bean(name = "localEntityManagerFactory")
public LocalContainerEntityManagerFactoryBean entityManagerFactory(
EntityManagerFactoryBuilder builder,
#Qualifier("dataSource") DataSource dataSource) {
return builder
.dataSource(dataSource)
.packages("fr.axygest.akostaxi.local.model")
.persistenceUnit("local")
.build();
}
#Primary
#Bean(name = "transactionManager")
public PlatformTransactionManager transactionManager(
#Qualifier("localEntityManagerFactory") EntityManagerFactory
entityManagerFactory
) {
return new JpaTransactionManager(entityManagerFactory);
}
}
Next, for the x external databases save in the company table of the local database, I use AbstractRoutingDataSource
I store a current context as a ThreadLocal :
public class ThreadPostgresqlStorage {
private static ThreadLocal<Long> context = new ThreadLocal<>();
public static void setContext(Long companyId) {
context.set(companyId);
}
public static Long getContext() {
return context.get();
}
}
I defined the RoutingSource to extend the AbstractRoutingDataSource :
public class RoutingSource extends AbstractRoutingDataSource
{
#Override
protected Object determineCurrentLookupKey() {
return ThreadPostgresqlStorage.getContext();
}
}
And the config class create all the databases connection saved in company table :
#Configuration
#EnableJpaRepositories(
basePackages = {"fr.axygest.akostaxi.postgresql"},
entityManagerFactoryRef = "pgEntityManager"
)
#EnableTransactionManagement
public class PgConfig {
private final CompanyRepository companyRepository;
#Autowired
public PgConfig(CompanyRepository companyRepository) {
this.companyRepository = companyRepository;
}
#Bean(name = "pgDataSource")
public DataSource pgDataSource() {
RoutingSource routingSource = new RoutingSource();
List<Company> companies = companyRepository.findAll();
HashMap<Object, Object> map = new HashMap<>(companies.size());
companies.forEach(company -> {
map.put(company.getId(), createDataSource(company));
});
routingSource.setTargetDataSources(map);
routingSource.afterPropertiesSet();
return routingSource;
}
#Bean(name = "pgEntityManager")
public LocalContainerEntityManagerFactoryBean pgEntityManager(
EntityManagerFactoryBuilder builder,
#Qualifier("pgDataSource") DataSource dataSource) {
return builder
.dataSource(dataSource)
.packages("fr.axygest.akostaxi.postgresql.model")
.persistenceUnit("pg")
.properties(jpaProperties())
.build();
}
private DataSource createDataSource(Company company) {
HikariConfig hikariConfig = new HikariConfig();
hikariConfig.setMaximumPoolSize(10);
hikariConfig.setMinimumIdle(1);
hikariConfig.setJdbcUrl("jdbc:postgresql://" + company.getUrl() + "/" + company.getIdClient());
hikariConfig.setUsername(company.getUsername());
hikariConfig.setPassword(company.getPassword());
return new HikariDataSource(hikariConfig);
}
private Map<String, Object> jpaProperties() {
Map<String, Object> props = new HashMap<String, Object>();
props.put("hibernate.dialect", "org.hibernate.dialect.MySQL5Dialect");
return props;
}
}

IllegalTransactionStateException Pre-bound JDBC Connection found when configuring multiple datasources

I have a spring batch project wherein I read data from a datasource , process the data and write into another primary data source. I am extending CrudRepository for dao operations.
I am trying to configure multiple datasources for my springbatch + spring boot application below is the package structure :
myproject
---com
---batch
---config
---firstDsConfig.java
---secondDsConfig.java
---firstrepository
---firstCrudRepository.java
---secondRepository
---SecondCrudRepository.java
---firstEntity
---firstDBEntity.java
---secondEntity
---secondDBEntity.java
----main
---MyMainClass.java
Code for firstDsConfig.java:
#Configuration
#EnableJpaRepositories(
entityManagerFactoryRef = "firstEntityManagerFactory",
transactionManagerRef = "firstTransactionManager",
basePackages = "com.batch.firstrepository"
)
#EnableTransactionManagement
public class FirstDbConfig {
#Primary
#Bean(name = "firstEntityManagerFactory")
public LocalContainerEntityManagerFactoryBean firstEntityManagerFactory(final EntityManagerFactoryBuilder builder,
final #Qualifier("firstDs") DataSource dataSource) {
return builder
.dataSource(dataSource)
.packages("com.batch.firstEntity")
.persistenceUnit("abc")
.build();
}
#Primary
#Bean(name = "firstTransactionManager")
public PlatformTransactionManager firstTransactionManager(#Qualifier("firstEntityManagerFactory")
EntityManagerFactory firstEntityManagerFactory) {
return new JpaTransactionManager(firstEntityManagerFactory);
}
#Primary
#Bean(name = "firstDs")
#ConfigurationProperties(prefix = "spring.datasource.first")
public DataSource firstDataSource() {
return DataSourceBuilder.create().build();
}
}
Code for secondDsConfig:
#Configuration
#EnableJpaRepositories(
entityManagerFactoryRef = "secondEntityManagerFactory",
transactionManagerRef = "secondTransactionManager",
basePackages = "com.batch.secondrepository"
)
#EnableTransactionManagement
public class FirstDbConfig {
#Primary
#Bean(name = "secondEntityManagerFactory")
public LocalContainerEntityManagerFactoryBean secondEntityManagerFactory(final EntityManagerFactoryBuilder builder,
final #Qualifier("secondDs") DataSource dataSource) {
return builder
.dataSource(dataSource)
.packages("com.batch.secondEntity")
.persistenceUnit("xyz")
.build();
}
#Primary
#Bean(name = "secondTransactionManager")
public PlatformTransactionManager secondTransactionManager(#Qualifier("secondEntityManagerFactory")
EntityManagerFactory firstEntityManagerFactory) {
return new JpaTransactionManager(secondEntityManagerFactory);
}
#Primary
#Bean(name = "secondDs")
#ConfigurationProperties(prefix = "spring.datasource.second")
public DataSource secondDataSource() {
return DataSourceBuilder.create().build();
}
}
Here is my main class
#EnableScheduling
#EnableBatchProcessing
#SpringBootApplication(scanBasePackages = { "com.batch" })
public class MyMainClass {
#Autowired
private JobLauncher jobLauncher;
#Autowired
private JobRepository jobRepository;
#Autowired
#Qualifier(firstDs)
private DataSource dataSource;
#Autowired
#Qualifier("myJob")
private Job job;
public static void main(String[] args) throws Exception {
SpringApplication.run(MyMainClass.class, args);
}
#EventListener(ApplicationReadyEvent.class)
private void start() throws Exception {
jobLauncher.run(job, new JobParameters());
}
#Bean(name="jobService")
public JobService jobService() throws Exception {
SimpleJobServiceFactoryBean factoryBean = new SimpleJobServiceFactoryBean();
factoryBean.setDataSource(dataSource);
factoryBean.setJobRepository(jobRepository);
factoryBean.setJobLocator(new MapJobRegistry());
factoryBean.setJobLauncher(jobLauncher);
factoryBean.afterPropertiesSet();
return factoryBean.getObject();
}
}
Here are the crud repo:
First curd repo
public interface FirstCrudRepository extends CrudRepository<FirstDbEntity, Integer> {
List<FirstDbEntity> findByOId(String oId);
}
Second curd repo
public interface SecondCrudRepository extends CrudRepository<SecondDbEntity, Integer> {
List<SecondDbEntity> findByPid(String pid);
}
When I run my application I see following error while saving record using FirstCrudRepository:
org.springframework.transaction.IllegalTransactionStateException: Pre-bound JDBC Connection found! JpaTransactionManager does not support running within DataSourceTransactionManager if told to manage the DataSource itself. It is recommended to use a single JpaTransactionManager for all transactions on a single DataSource, no matter whether JPA or JDBC access.
Note: I am able to fetch details successfully from SecondCrudRepository
By default, if you provide a DataSource, Spring Batch will use a DataSourceTransactionManager which knows nothing about your JPA configuration. You need to tell Spring Batch to use your JpaTransactionManager. This is explained in the:
reference documentation: https://docs.spring.io/spring-batch/4.1.x/reference/html/index-single.html#javaConfig.
Javadoc of #EnableBatchProcessing: https://docs.spring.io/spring-batch/4.1.x/api/org/springframework/batch/core/configuration/annotation/EnableBatchProcessing.html
I suspect there are 2 different transaction managers present which would be causing issue. Annotate with #Transactional and specifying the transaction manager would help
Source:
Spring - Is it possible to use multiple transaction managers in the same application?

One Repository for different datasources

I want to have just one implementation of repository class for multiple datasources.
Imagine that there is 1 application for multiple countries (with their dbs) and all repositories are the same for all of those countries..
Basic structure looks like this:
org.company.name
.config.{country-shortcut}Config
.repository.{country-shortcut}.SameRepository{country-shortcut}
.. other parts ..
Repository class could look like this:
package org.company.name.repository.{country-shortcut}.SomethingRepository
public class SomethingRepository extends CrudRepository<Something, Long> {
... some methods ...
}
Let's now look at some implementation.. We take UK as a our country now..
Country repository class for some country..
package org.company.name.repository.uk.SomethingRepositoryUk
public class SomethingRepositoryUk extends SomethingRepository {}
Basic config for each datasource.. I have multiple configs (one for each country)..
package org.company.name.config.UkConfig
#Configuration
#EnableTransactionManagement
#EnableJpaRepositories(entityManagerFactoryRef = "entityManagerFactoryUK",
transactionManagerRef = "transactionManagerUK",
basePackages = {"org.company.name.repository.uk"})
public class UkConfig {
#Autowired
private Environment env;
#Bean(name = "dataSourceUk")
public DataSource dataSource() {
DriverManagerDataSource dataSource = new DriverManagerDataSource();
... datasource settings (url, user, pass, etc.) ...
return dataSource;
}
#Bean(name = "entityManagerFactoryUk")
public LocalContainerEntityManagerFactoryBean entityManagerFactory(
EntityManagerFactoryBuilder builder,
#Qualifier("dataSourceUk") DataSource dataSource) {
return builder
.dataSource(dataSource)
.packages("org.company.name.repository.model")
.persistenceUnit("uk")
.build();
}
#Bean(name = "transactionManagerUk")
public PlatformTransactionManager transactionManager(
#Qualifier("entityManagerFactoryUk") EntityManagerFactory entityManagerFactory) {
return new JpaTransactionManager(entityManagerFactory);
}
}
Config is fine by me, but is it possible to do not have multiple repositories (each for country, everything the same..)? It's bothering me because it seems like an unclean implementation..

Categories

Resources