How to connect multiple databases in Spring Boot - java

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.

Related

Cyclic dependency when there are multiple DataSource defined

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

I am using multiple databases in Spring Boot, but creating domains in primary Database

I am trying to connect with two databases in Spring Boot. But two domains are creating in the primary Database. I uploaded my code. If anyone knows. I am not getting any console errors.But two domains creating in primary DB.
May I know what is region behind this?
(First Databse)
#EnableTransactionManagement
#EnableJpaRepositories(entityManagerFactoryRef = "dbEntityManagerFactory", transactionManagerRef = "dbTransactionManager", basePackages = {
"com.multipledatasources.db.domain", "com.multipledatasources.db.repository" })
public class DBConfigure {
#Autowired
private DatabaseSettings databaseSettings;
#Bean
#Primary
#ConfigurationProperties("spring.datasource1")
public DataSourceProperties productProperties() {
return new DataSourceProperties();
}
#Primary
#Bean(name = "DB")
public DataSource dataSource(DataSourceProperties dataSourceProperties) {
return dataSourceProperties.initializeDataSourceBuilder().build();
}
#Primary
#Bean(name = "dbEntityManagerFactory")
public LocalContainerEntityManagerFactoryBean dbEntityManagerFactory(EntityManagerFactoryBuilder builder,
#Qualifier("DB") DataSource DBDataSource) {
Map<String, String> map = databaseSettings.setting();
return builder.dataSource(DBDataSource)
.packages("com.multipledatasources.db.domain", "com.multipledatasources.db.repository").properties(map)
.persistenceUnit("DB").build();
}
#Primary
#Bean(name = "dbTransactionManager")
public PlatformTransactionManager dbTransactionManager(
#Qualifier("dbEntityManagerFactory") EntityManagerFactory dbEntityManagerFactory) {
return new JpaTransactionManager(dbEntityManagerFactory);
}
(Second Database)
#EnableTransactionManagement
#EnableJpaRepositories(transactionManagerRef = "sysTransactionManager", entityManagerFactoryRef = "sysEntityManagerFactory", basePackages = {
"com.multipledatasources.sys.domain", "com.multipledatasources.sys.repository" })
public class SysConfigure {
#Autowired
private DatabaseSettings databaseSettings;
#Bean
#ConfigurationProperties("spring.datasource2")
public DataSourceProperties sysProperties() {
return new DataSourceProperties();
}
#Bean(name = "sys")
public DataSource sysDataSource(DataSourceProperties dataSourceProperties) {
return dataSourceProperties.initializeDataSourceBuilder().build();
}
#Bean(name = "sysEntityManagerFactory")
public LocalContainerEntityManagerFactoryBean sysEntityManagerFactory(
EntityManagerFactoryBuilder entityManagerFactory, #Qualifier("sys") DataSource dataSource) {
Map<String, String> map = databaseSettings.setting();
return entityManagerFactory.dataSource(dataSource).properties(map)
.packages("com.multipledatasources.sys.domain", "com.multipledatasources.sys.repository")
.persistenceUnit("sys").build();
}
#Bean(name = "sysTransactionManager")
public PlatformTransactionManager sysTransactionManager(
#Qualifier("sysEntityManagerFactory") EntityManagerFactory sysEntityManagerFactory) {
return new JpaTransactionManager(sysEntityManagerFactory);
}
(Setting class)
Map<String, String> map = new HashMap<>();
map.put(AvailableSettings.SHOW_SQL, "true");
map.put(AvailableSettings.HBM2DDL_AUTO, "update");
map.put(AvailableSettings.DIALECT, "org.hibernate.dialect.MySQL5InnoDBDialect");
return map;
}
(Application.properties File)
server.port=4456
# Hibernate ddl auto (create, create-drop, validate, update)
spring.jpa.hibernate.ddl-auto=create-drop
spring.jpa.generate-ddl=true
## Spring DATASOURCE (DataSourceAutoConfiguration & DataSourceProperties)
spring.datasource1.url=jdbc:mysql://localhost:3306/DB?useSSL=false
spring.datasource1.username=root
spring.datasource1.password=sarat
## Spring DATASOURCE (DataSourceAutoConfiguration & DataSourceProperties)
spring.datasource2.url=jdbc:mysql://localhost:3306/sys?userSSL=false
spring.datasource2.username=root
spring.datasource2.password=sarat
Project Strectures

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?

How can I read object from one datasource and write to another with spring data?

I have a config class:
#SpringBootConfiguration
#ComponentScan(basePackages = "vap")
public class AppConfig {
Logger logger = LoggerFactory.getLogger(this.getClass());
public AppConfig() {
}
#Bean
public ServerRuntime runtime() {
ServerRuntime runtime = ServerRuntime.builder().addConfig("cayenne-project.xml").build();
return runtime;
}
#Bean
public ObjectContext getContext(#Autowired ServerRuntime serverRuntime) {
return serverRuntime.newContext();
}
#Bean(name = "pgDataSource")
public DataSource getDataSource() {
Properties props = new Properties();
props.setProperty("user", "postgres");
props.setProperty("password", "");
PoolConfiguration configuration = new PoolProperties();
configuration.setDbProperties(props);
configuration.setUrl("jdbc:postgresql://localhost/mikro00");
configuration.setDriverClassName("org.postgresql.Driver");
DataSource dataSource = new org.apache.tomcat.jdbc.pool.DataSource(configuration);
return dataSource;
}
#Bean(name = "hsqldbDataSource")
public DataSource getHSQLDataSource() {
Properties props = new Properties();
props.setProperty("user", "sa");
props.setProperty("password", "");
PoolConfiguration configuration = new PoolProperties();
configuration.setDbProperties(props);
configuration.setUrl("jdbc:h2:file:./outbase");
configuration.setDriverClassName("org.h2.Driver");
DataSource dataSource = new org.apache.tomcat.jdbc.pool.DataSource(configuration);
return dataSource;
}
}
my PGConfig.java
#Configuration
#EnableTransactionManagement
public class PGConfig {
#Primary
#Bean(name = "entityManagerFactoryPG")
public LocalContainerEntityManagerFactoryBean entityManagerFactory(EntityManagerFactoryBuilder builder, #Qualifier(value = "pgDataSource") DataSource dataSource) {
LocalContainerEntityManagerFactoryBean vap = builder.dataSource(dataSource)
.packages("vap")
.build();
JpaVendorAdapter jpaVendorAdapter = new HibernateJpaVendorAdapter();
vap.setJpaVendorAdapter(jpaVendorAdapter);
return vap;
}
#Primary
#Bean(name = "transactionManagerPG")
public PlatformTransactionManager transactionManager(
#Qualifier("entityManagerFactory") EntityManagerFactory
entityManagerFactory
) {
return new JpaTransactionManager(entityManagerFactory);
}
}
My H2Config.java
#Configuration
#EnableTransactionManagement
public class H2Config {
#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(value = "hsqldbDataSource") DataSource dataSource){
LocalContainerEntityManagerFactoryBean vap = builder.dataSource(dataSource)
.packages("vap")
.build();
JpaVendorAdapter jpaVendorAdapter = new HibernateJpaVendorAdapter();
vap.setJpaVendorAdapter(jpaVendorAdapter);
return vap;
}
#Primary
#Bean(name = "transactionManagerH2")
public PlatformTransactionManager transactionManager(
#Qualifier("entityManagerFactory") EntityManagerFactory
entityManagerFactory
) {
return new JpaTransactionManager(entityManagerFactory);
}
}
KlientRepository
#Repository
public interface KlientRepository extends CrudRepository<Klient,Integer> {
}
How can I read Klient from one repository and write to another. I need to read from PG, work with data and save to h2. I can't find how two object of repository with different datasource, or simply create repository object with concrete datasource
You have pretty much everything out there in your code, you only need to do a bit of fine tuning thats all
Create two configuration classes with bean declaration for Datasource, EntityManagerFactory and TransactionManager
Mark one of the two as primary
Create two model classes(one for each database model)
Create two Repository classes in two different package**(very Important)**
In your service class Autowire both Repositories, read from one DB, manipulate and save to other.
Only thing missing in your code is you need to tell Spring which Repository class should use which EntityManager /Datasource(Since you have two). This can be done by Annotation #EnableJpaRepositories(basepackages=...). Use this annotation on each configuration classes, with basePackages indicating your repository classes

Categories

Resources