Dynamic Datasource using SpringBoot [duplicate] - java

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/

Related

Dynamic Bean configuration & loading in spring boot

I am following this link for understanding hexagonal architecture with spring boot. The infrastructure section contains the configuration for the service bean and the repository is passed as a parameter as a below method.
Configuration
#Configuration
#ComponentScan(basePackageClasses = HexagonalApplication.class)
public class BeanConfiguration {
#Bean
BankAccountService bankAccountService(BankAccountRepository repository) {
return new BankAccountService(repository, repository);
}
}
I am not using JPA instead using Spring JDBC for interacting to DB. Linked tutorial is using JPA.
Lets say I have different database implementations i.e.. postgresql(BankAccountRepository) and db2(BankAccountDB2Rep) . I want to change the beans without touching the code. something like with yml configuration or something which I can maintain separately instead of touching the code.
BankAccountRepository.java
#Component
public class BankAccountRepository implements LoadAccountPort, SaveAccountPort {
private SpringDataBankAccountRepository repository;
// Constructor
#Override
public Optional<BankAccount> load(Long id) {
return repository.findById(id);
}
#Override
public void save(BankAccount bankAccount) {
repository.save(bankAccount);
}
}
How can I achieve the same in spring boot? Any help is appreciated..
You can refer to
Spring Boot Configure and Use Two DataSources for creating multiple datasources and do something like following.
#Configuration
#EnableTransactionManagement
#EnableJpaRepositories(
entityManagerFactoryRef = "entityManagerFactory",
transactionManagerRef = "transactionManager",
basePackages = {
"com.example"
}
)
public class JPAConfig {
#Primary
#Bean(name = "postgresDataSource")
#ConfigurationProperties(prefix = "postgres.datasource")
public DataSource postgresDataSource() {
return DataSourceBuilder.create().build();
}
#Primary
#Bean(name = "db2DataSource")
#ConfigurationProperties(prefix = "db2.datasource")
public DataSource db2DataSource() {
return DataSourceBuilder.create().build();
}
#Primary
#Bean(name = "entityManagerFactory")
public LocalContainerEntityManagerFactoryBean entityManagerFactory(
EntityManagerFactoryBuilder builder,
#Qualifier("postgresDataSource") DataSource postgresdataSource,
#Qualifier("db2DataSource") DataSource db2dataSource,
#Value("${useDb2}") Boolean useDb2
) {
return builder
.dataSource(useDb2? db2dataSource : postgresdataSource)
.packages("com.example")
.persistenceUnit("db1")
.build();
}
#Primary
#Bean(name = "transactionManager")
public PlatformTransactionManager transactionManager(
#Qualifier("entityManagerFactory") EntityManagerFactory entityManagerFactory
) {
return new JpaTransactionManager(entityManagerFactory);
}
}
As mentioned by #M.Deinum in comments, the issue can be resolved by using the spring conditional beans, as below
#Configuration
#ConditionalOnProperty(
value="module.enabled",
havingValue = "true",
matchIfMissing = true)
class CrossCuttingConcernModule {
...
}
More information can be found here

Unwanted empty table created in datasource

When I am trying to use multiple datasources in my Spring Boot Application, every entityManager is creating unwanted tables from other datasources. How can I fix this?
Here's related snippet of my code:
dataSourceConfig.java
#Configuration
#EnableTransactionManagement
#EnableJpaRepositories(entityManagerFactoryRef= "entityManagerFactory",
basePackages= {"com.wnp.customerinfo.customerinfo.customerrepository"},transactionManagerRef="transactionManager")
public class CustomerConfig {
#Bean(name="datasource")
#ConfigurationProperties(prefix="spring.customer.datasource")
public DataSource dataSource() {
return DataSourceBuilder.create().build();
}
#Bean(name="entityManagerFactory")
public LocalContainerEntityManagerFactoryBean entityManagerFactoryBean(EntityManagerFactoryBuilder builder,
#Qualifier("datasource") DataSource dataSource) {
Map<String,Object> properties = new HashMap<>();
properties.put("hibernate.dialect", "org.hibernate.dialect.MySQL5Dialect");
properties.put("hibernate.hbm2ddl.auto", "update");
return builder.dataSource(dataSource).properties(properties)
.packages("com.ecom.customerinfo.customerinfo.model").persistenceUnit("CustomerInfo").build();
}
#Bean(name="transactionManager")
public PlatformTransactionManager transactionManager(#Qualifier("entityManagerFactory") EntityManagerFactory entityManagerFactory) {
return new JpaTransactionManager(entityManagerFactory);
}
}
application.properties
spring.customer.datasource.jdbcUrl=jdbc:mysql://localhost:3306/customer_info_db_temp
spring.customer.datasource.username=root
spring.customer.datasource.password=
spring.prod.datasource.jdbcUrl=jdbc:mysql://localhost:3307/prod_info_db_temp
spring.prod.datasource.username=root
spring.prod.datasource.password=
The configuration you used in the class is perfect. But if you have all the model classes in the same package and giving the same base-package in all the configuration classes it will always create the empty tables in all the databases. Try to keep them in different packages.

How to inject CrudRepository and EntityManager from secondary DataSource?

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 !

Spring-jpa-data: more than one DataSource and one single Spring config

I am trying to use Crud Repository from Spring-jpa-data:
My config with data access beans looks like:
#Configuration
#EnableTransactionManagement
#ComponentScan (basePackages = {"com.comp.olme"})
#PropertySource("classpath:OlmeSmb-${env}.properties")
#EnableJpaRepositories(basePackages="com.comp.olme", entityManagerFactoryRef ="emGapSort", transactionManagerRef = "txManagerGapSort")
#EnableScheduling
public class OlmeSmbConfig {
#Bean
public BasicDataSource olmeDataSource() {
BasicDataSource olmeDataSource = new BasicDataSource();
...
return olmeDataSource;
}
#Bean
public BasicDataSource gapSortDataSource() {
BasicDataSource gapSortDataSource = new BasicDataSource();
...
return gapSortDataSource;
}
#Bean
public LocalContainerEntityManagerFactoryBean emFactoryOLME(#Qualifier("olmeDataSource") BasicDataSource olmeDataSource) {
LocalContainerEntityManagerFactoryBean localConnectionFactoryBean = new LocalContainerEntityManagerFactoryBean();
...
return localConnectionFactoryBean;
}
#Bean
public LocalContainerEntityManagerFactoryBean emFactoryGapSort(#Qualifier("gapSortDataSource") BasicDataSource gapSortDataSource) {
LocalContainerEntityManagerFactoryBean localConnectionFactoryBeanGapSort = new LocalContainerEntityManagerFactoryBean();
...
return localConnectionFactoryBeanGapSort;
}
#Bean
public EntityManager emOLME(#Qualifier("emFactoryOLME") EntityManagerFactory entityManagerFactory) {
return entityManagerFactory.createEntityManager();
}
#Bean
public EntityManager emGapSort(#Qualifier("emFactoryGapSort") EntityManagerFactory entityManagerFactory) {
return entityManagerFactory.createEntityManager();
}
#Bean
public JpaTransactionManager txManagerOLME(#Qualifier("emFactoryOLME") EntityManagerFactory entityManagerFactoryOLME) {
JpaTransactionManager txManagerOLME = new JpaTransactionManager();
...
return txManagerOLME;
}
#Bean
public JpaTransactionManager txManagerGapSort(#Qualifier("emFactoryGapSort") EntityManagerFactory entityManagerFactoryGapSort) {
JpaTransactionManager txManagerGapSort = new JpaTransactionManager();
....
return txManagerGapSort;
}
}
So, as you can see, i have two datasources, two EntityManagerFactories, two TransactionManagers and others...
But i pass only one EntityManagerFactory into #EnableJpaRepositories annotation (entityManagerFactoryRef ="emGapSort").
The question is: how two use more than one DataSources (entityManagerFactory) with Spring-jpa-data?
I read one example where splitting config described as a solution, but i would like to use one single Spring Config. Is it possibe?
Thank you.
From spring boot - #5401
After a careful consideration we've decided not to implement this. That annotation proposal of yours is a mix of configuration key prefix, bean prefix and other bean-related settings. While it sounds appealing on paper, it would be quite hard to implement and probably even harder to keep it consistent with user's customizations.
So, you may try put it on two #Configuration classes (one #EnableJpaRepositories per #Configuration).

Two data sources in a Spring Boot application

I have a Spring Boot app for which I have configured two data sources. So far I've configured the data sources in my Application class (annotated with #EnableAutoConfiguration):
#Bean
#Primary
#ConfigurationProperties(prefix="datasource.db1")
public DataSource dataSource() {
return DataSourceBuilder.create().build();
}
#Bean
#ConfigurationProperties(prefix="datasource.db2")
public DataSource secondaryDataSource() {
return DataSourceBuilder.create().build();
}
I also added the configuration values to application.properties:
datasource.db1.url=...
...
datasource.db2.url=...
...
Since db1 is the #Primary data source, it is chosen by default. How do I tell an interface extending JpaRepository that it should use db2 instead?
UPDATE: mentioning that my repository is an interface.
You can get the bean associated to the secondary data source from the application context.
For example in Application.java (I'm also using Spring Boot) you define:
#Bean
#ConfigurationProperties(prefix="datasource.secondary")
public DataSource secondaryDataSource() {
return DataSourceBuilder.create().build();
}
and in a service (here for calling a stored procedure) you have:
#Service
public class EngineImpl implements EngineDao {
private SetScartiProcedure setScarti;
#Autowired
public void init(ApplicationContext ctx) {
DataSource dataSource = (DataSource) ctx.getBean("secondaryDataSource");
this.setScarti = new SetScartiProcedure(dataSource);
}
public class SetScartiProcedure extends StoredProcedure {
...
}
based on this you can define several DataSourcethis way
#Bean
public LocalContainerEntityManagerFactoryBean customerEntityManagerFactory(
EntityManagerFactoryBuilder builder) {
return builder
.dataSource(customerDataSource())
.packages(Customer.class)
.persistenceUnit("customers")
.build();
}
#Bean
public LocalContainerEntityManagerFactoryBean orderEntityManagerFactory(
EntityManagerFactoryBuilder builder) {
return builder
.dataSource(orderDataSource())
.packages(Order.class)
.persistenceUnit("orders")
.build();
}
and then bind each one of them to different classes that each one of them manages
#Configuration
#EnableJpaRepositories(basePackageClasses = Customer.class,
entityManagerFactoryRef = "customerEntityManagerFactory")
public class CustomerConfiguration {
...
}
#Configuration
#EnableJpaRepositories(basePackageClasses = Order.class,
entityManagerFactoryRef = "orderEntityManagerFactory")
public class OrderConfiguration {
...
}
the repositories should know which database to use by the DataSource that was bidden to the class

Categories