I have configured two databases (Same schema one is QA and another one is PROD DB) in my Springboot application, The idea here is I don't want to deploy two instances of application to talk to each DB instead on login screen user will select the Database to work with and then application will connect to the database and pull the required data. The question is how can I select database dynamically in my DAO layer using #Qualifier("firstDB") or #Qualifier("secondDB"). Please note that I have user selected database value in Session object so obviously I can't or should not access Session data in my DAO layer as I want to keep DAO separately. Please help me out to choose a DB connection dynamically.
DB Configuration:
#Bean(name = "jdbcTemplate")
#Autowired
public JdbcTemplate jdbcTemplate(#Qualifier("dataSourceOne") DataSource dsCustom) {
return new JdbcTemplate(dsCustom);
}
DAO:
#Autowired
private JdbcTemplate jdbcTemplate;
public TidalAlertsDAOImpl(JdbcTemplate template) {
this.jdbcTemplate = template;
}
Solution:
#Configuration
public class DataSourceConfig {
#Bean(name = "dataSourceOne")
#Primary
public DataSource dataSource() {
return DataSourceBuilder
.create()
.username("USERNAME")
.password("Password")
.url("jdbc:oracle:thin:#localhost.com:1521:DV")
.driverClassName("oracle.jdbc.driver.OracleDriver")
.build();
}
#Bean(name = "dataSourceTwo")
public DataSource dataSourceTwo() {
return DataSourceBuilder
.create()
.username("USERNAME")
.password("Password")
.url("jdbc:oracle:thin:#localhost.com:1521:QA")
.driverClassName("oracle.jdbc.driver.OracleDriver")
.build();
}
#Bean(name = "jdbcTemplate")
#Autowired
public JdbcTemplate jdbcTemplate(#Qualifier("dataSourceOne") DataSource dsCustom) {
return new JdbcTemplate(dsCustom);
}
public JdbcTemplate getJDBCTemplate(){
String environmnet = null;
JdbcTemplate jdbcTemplate = null;
ServletRequestAttributes requestAttributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
HttpSession session = requestAttributes.getRequest().getSession();
if(session.getAttribute("databaseName") != null){
environmnet = (String) session.getAttribute("databaseName");
if(environmnet.equals("Development - QA")){
System.out.println("Sected Database is:" + environmnet);
jdbcTemplate = new JdbcTemplate(dataSourceTwo());
}else if(environmnet.equals("Sandbox - DV")){
System.out.println("Sected Database is:" + environmnet);
jdbcTemplate = new JdbcTemplate(dataSource());
}
}
return jdbcTemplate;
}
This is a tricky one. Will there be multiple users logging into the application? In that case, you will have to map the user with the selected connection. It would be difficult to inject a connection in the DAO using annotations since database connections are at an application level. You will have to retrieve the connection to be used for that user and then use the corresponding connection.
Related
I use MsSQL and Spring Batch. I need to interact with two schemas in the same database. However, the driver for some reason ignores the installation of the scheme and uses one of the standard schemes. How can I fix this?
#Bean
public DataSource targetDataSource() {
SimpleDriverDataSource dataSource = getSimpleDriverDataSource();
dataSource.setSchema("target");
return dataSource;
}
#Bean
public DataSource sourceDataSource() {
SimpleDriverDataSource dataSource = getSimpleDriverDataSource();
dataSource.setSchema("source");
return dataSource;
}
private SimpleDriverDataSource getSimpleDriverDataSource() {
SimpleDriverDataSource dataSource = new SimpleDriverDataSource();
dataSource.setDriverClass(com.microsoft.sqlserver.jdbc.SQLServerDriver.class);
dataSource.setUsername("sa");
dataSource.setUrl("jdbc:sqlserver://localhost:1433;database=myDB");
dataSource.setPassword("myPassword");
return dataSource;
}
Code for writhing:
#Bean
public JdbcBatchItemWriter<Person> writer() {
return new JdbcBatchItemWriterBuilder<Person>()
.itemSqlParameterSourceProvider(new BeanPropertyItemSqlParameterSourceProvider<>())
.sql("INSERT INTO target.people (first_name, last_name) VALUES (:firstName, :lastName)") //TODO убрать схему
.dataSource(targetDataSource())
.build();
}
When remove target. part, there will be a PreparedStatement Callback exception; bad SQL grammar [INSERT INTO people (first_name, last_name) VALUES (?, ?)]; nested exception is java.sql.BatchUpdateException: Invalid object name 'people'. The same thing happens with reading from the schema. As soon as I remove the schema name from the request, I get an exception
I have two DBs in my Spring Boot app, one configured with appliation.properties:
spring.secondDatasource.url=jdbc:mysql://10.10.10:3306/db1
spring.secondDatasource.username=user
spring.secondDatasource.password=pass
spring.jpa.database-platform = org.hibernate.dialect.MySQL5Dialect
spring.datasource.driverClassName=com.mysql.jdbc.Driver
spring.jpa.hibernate.ddl-auto = update
and another via DataSource:
#Configuration
public class SecondDbConnectionConfig {
#Bean
public DriverManagerDataSource dataSource() {
DriverManagerDataSource dataSource = new DriverManagerDataSource();
dataSource.setUrl("jdbc:mysql://20.20.20:3306/db2");
dataSource.setDriverClassName("com.mysql.jdbc.Driver");
dataSource.setUsername("user");
dataSource.setPassword("pass");
return dataSource;
}
}
When I run the app, both schemas are updated with my domain model.
I want to update a domain model only for app.prop configured DB.
As to DataSource configured DB, I don't want to make any changes, just read.
How to fix this config?
Don't use DriverManagerDataSource for production. It's just suitable for your testing environment. (It does not have connection pool, just create new connection on the fly)
Your datasource uses your JpaProperties config from your spring.jpa.* properties. So you need to override it:
#Bean(name = "your-entity-manager-factory")
public LocalContainerEntityManagerFactoryBean getEntityManagerFactory(EntityManagerFactoryBuilder builder,
#Qualifier("testdatasource") DataSource dataSource, // Here you must annotate your DriverManagerDataSource bean with #Bean("testdatasource")
JpaProperties jpaProperties) {
// Here you clone and modify JpaProperties
Map<String, String> hibernateConfig = jpaProperties.getHibernateProperties(dataSource);
hibernateConfig.remove("hibernate.hbm2ddl.auto");
return builder
.dataSource(dataSource)
.persistenceUnit("testPu")
.properties(hibernateConfig)
.build();
}
You may config additional transaction manager.
I use spring-boot with DATAJPA repository and two datasource. One datasource connect to a MySQL DB and the 2nd to a file based embedded H2 DB.
On the H2 DB i want change the URL during my application ist running. The URL changing work and after change the URL to an not existing DB spring create the DB. But the tables will not create and spring give me an error like Table "SYMBOL" not found; SQL statement:.
When i restart my app now then spring create the tables with the new URL and everything work fine.
How i can create the tables after change the URL?
To change the URL i write my own datasource and build it new on each connection.
public class ProjectDataSource extends AbstractDataSource {
#Autowired ToolService toolService;
#Override
public Connection getConnection() throws SQLException {
return determineTargetDataSource().getConnection();
}
#Override
public Connection getConnection(String username, String password) throws SQLException {
return determineTargetDataSource().getConnection(username, password);
}
#ConfigurationProperties(prefix = "project.datasource")
private DataSource determineTargetDataSource() {
String projectName = toolService.get().getProjectName();
String url = "jdbc:h2:file:../db/" + projectName;
return DataSourceBuilder
.create()
.url(url)
.build();
}
}
Here the configuration
#Configuration
#EnableTransactionManagement
#EnableJpaRepositories(
entityManagerFactoryRef = "projectEntityManagerFactory",
transactionManagerRef = "projectTransactionManager",
basePackages = {"at.ltw.test.bz.model.project"})
public class ProjectDbConfiguration {
#Bean(name = "projectDataSource")
public DataSource dataSource() {
return new ProjectDataSource();
}
#Bean(name = "projectEntityManagerFactory")
public LocalContainerEntityManagerFactoryBean
barEntityManagerFactory(
EntityManagerFactoryBuilder builder,
#Qualifier("projectDataSource") DataSource dataSource
) {
HibernateJpaVendorAdapter jpaVendorAdapter = new HibernateJpaVendorAdapter();
jpaVendorAdapter.setGenerateDdl(true);
return builder
.dataSource(dataSource)
.packages("at.ltw.test.bz.model.project")
.persistenceUnit("bzProject")
.properties(jpaVendorAdapter.getJpaPropertyMap())
.build();
}
#Bean(name = "projectTransactionManager")
public PlatformTransactionManager barTransactionManager(
#Qualifier("projectEntityManagerFactory") EntityManagerFactory projectEntityManagerFactory) {
return new JpaTransactionManager(projectEntityManagerFactory);
}
}
Here my application.properties
#jpa
spring.jpa.hibernate.ddl-auto=update
spring.jpa.generate-ddl=true
#tool database
spring.datasource.driver-class-name=com.mysql.jdbc.Driver
spring.datasource.url=jdbc:mysql://localhost/ltw?useSSL=false
spring.datasource.username=ltw
spring.datasource.password=password
#project database
project.datasource.driver-class-name=org.h2.Driver
#logging
logging.level.at.ltw = trace
Hope somebody can help me and sorry for my bad English ...
JPA Will not help you because it is only at init state that DDL are checked.
You need to use something like flywayDB or liquibase or simple SQL file to generate the new DB.
Anyway, you code is wrong, and I'm surprise you don't have error.
#Bean(name = "projectDataSource")
public DataSource dataSource() {
return new ProjectDataSource();
}
There is no injection but a simple new object and ...
public class ProjectDataSource extends AbstractDataSource {
#Autowired ToolService toolService;
#ConfigurationProperties(prefix = "project.datasource")
private DataSource determineTargetDataSource() {
String projectName = toolService.get().getProjectName();
...
}
toolService will be null, so determineTargetDataSouce will fail with a null pointer exception.
I am writring a batch using the following thecnologies:
Spring Boot to run the application : V1.5.3.RELEASE
Spring Batch with Spring Batch Config: spring-batch-infrastructure V3.0.7.RELEASE
Spring Data for my generic DAO to the business database: >spring-data-jpa V1.11.3.RELEASE
My datasource to oracle datbase is HikariDataSource :
#Qualifier("dataSource")
#Bean(destroyMethod = "close")
#Primary
public HikariDataSource dataSource() throws SQLException {
return buildDataSource();
}
public HikariDataSource buildDataSource() throws SQLException {
HikariDataSource ds = new HikariDataSource();
ds.setMaximumPoolSize(poolSize);
ds.setDriverClassName(driverClassName);
ds.setJdbcUrl(jdbcUrl);
ds.setUsername(userName);
ds.setPassword(password);
ds.setConnectionTestQuery("SELECT 1 from DUAL");
ds.addDataSourceProperty("hibernate.show_sql", showSQL);
ds.addDataSourceProperty("hibernate.use_sql_comments", useSQLComment);
ds.addDataSourceProperty("hibernate.format_sql", formatSQL);
ds.addDataSourceProperty("hibernate.ddl-auto", "none");
return ds;
}
I want to write my meta data in another database (in memory HSQL or H2 for example) but i can't find a way because the context is writing the meta data in the same database.
The only way is to define a TransactionManager and an EntityManager and enable them to my DAO :
#Bean
PlatformTransactionManager businessTransactionManager() throws SQLException {
return new JpaTransactionManager(businessEntityManagerFactory().getObject());
}
#Bean
LocalContainerEntityManagerFactoryBean businessEntityManagerFactory() throws SQLException {
HibernateJpaVendorAdapter jpaVendorAdapter = new HibernateJpaVendorAdapter();
jpaVendorAdapter.setGenerateDdl(true);
LocalContainerEntityManagerFactoryBean factoryBean = new LocalContainerEntityManagerFactoryBean();
factoryBean.setDataSource(dataSource());
factoryBean.setJpaVendorAdapter(jpaVendorAdapter);
factoryBean.setPackagesToScan("package.of.business.model", "package.of.business.data");
return factoryBean;
}
and in my batch configuration i add :
#EnableJpaRepositories(entityManagerFactoryRef = "businessEntityManagerFactory",
transactionManagerRef = "businessTransactionManager", basePackages = {"package.of.business.model",
"package.of.business.data"})
This way it works after i define the spring default dataSource in my app.properties:
spring.datasource.url=jdbc:h2:mem:test
spring.datasource.driverClassName=org.h2.Driver
spring.datasource.username=sa
spring.datasource.password=
spring.jpa.hibernate.ddl-auto=update
What i really want to do is the exact opposite of this, i want that the default database is the business one and i want to override the datasource that writes the meta data but i can't find a way. I even tried to make a custom BatchConfigurer:
CustomBatchConfigurer extends DefaultBatchConfigurer
It works only for my meta data after i disable the initialization of my spring data for the default datasource but it doesn't write anything in my oracle business database :
batch.data.source.init=false
spring.batch.initializer.enabled=false
spring.batch.initialize.enabled=false
spring.datasource.initialize=false
spring.datasource.continue-on-error=true
Does any one have any idea how this could be done?
You'll need to create a custom implementation of the BatchConfigurer (typically by extending DefaultbatchConfigurer. That will allow you to configure the batch DataSource explicitly.
You can read more about the BatchConfigurer in the documentation here: http://docs.spring.io/spring-batch/apidocs/org/springframework/batch/core/configuration/annotation/BatchConfigurer.html
I am trying to use datasource with spring boot.
In dev the application will run as spring bootrun. But in Test / Prod it will be external tomcat.
So in dev property file I am using Datasource details:
spring.community_ds.url=xxx
spring.community_ds.username=xxx
spring.community_ds.password=xxx
spring.community_ds.driver-class-name=oracle.jdbc.OracleDriver
spring.community_ds.driverClassName=oracle.jdbc.OracleDriver
And in TEST/PROD want to use JNDI
spring.community_ds.jndi-name=xxxx
Now in code I am trying to get the DataSource to create JdbcTemplate
#ConfigurationProperties(prefix = "spring.community_ds")
#Bean(name = "communityDb")
public DataSource communityDbDataSource() {
DataSource ds = DataSourceBuilder.create().build();
//Getting error db url is Null here
return ds;
}
#Bean(name = "communityDbTemplate")
public JdbcTemplate communityDbTemplate(#Qualifier("communityDb") DataSource communityDb) {
return new JdbcTemplate(communityDb);
}
I also tried :
#Configuration
public class DatabaseConfig {
#Autowired
Environment env;
#ConfigurationProperties(prefix = "spring.datasource")
#Bean
#Primary
public DataSource getDataSource() {
DataSource dsp = DataSourceBuilder
.create().build();
System.out.println(env.getProperty("spring.community_ds.url"));
System.out.println(env.getProperty("spring.datasource.url"));
return dsp;
}
}
Here also I saw env.getProperty is printing the data properly, but when I debugged inside DataSourceBuilder the url/username etc are null.
In gradle first I used:
compile("org.springframework:spring-jdbc")
Which was giving some error hence used:
compile("org.springframework.boot:spring-boot-starter-jdbc")