DataSourceBuilder does not pickup ConfigurationProperties(prefix="...") - spring

My application.properties
spring.datasource.url=jdbc:h2:mem:testdb
spring.datasource.username=${env.H2_USER}
spring.datasource.password=${env.H2_PASS}
spring.token-datasource.url=jdbc:mysql://${env.MYSQL_HOST}/${env.MYSQL_DB}
spring.token-datasource.username=${env.MYSQL_USER}
spring.token-datasource.password=${env.MYSQL_PASS}
My Configuration.java code snippet
#Configuration
public class DataSourceConfiguration {
#Bean
#Primary
#ConfigurationProperties(prefix = "spring.datasource")
public DataSource regularDataSource() {
DataSource dataSource = DataSourceBuilder.create().build();
return dataSource;
}
#Bean
#ConfigurationProperties(prefix = "spring.token-datasource")
public DataSource tokenDataSource() {
DataSource dataSource = DataSourceBuilder.create().build();
return dataSource;
}
}
However, if I put a break point at each of return dataSource;, I see the DataSource object not populated with the correponding property values, e.g. jdbcUrl would be null.
Any help?
Thanks!

Followed https://docs.spring.io/spring-boot/docs/2.4.2/reference/html/howto.html#howto-two-datasources and crafted following code which works:
#Configuration
public class DataSourceConfiguration {
#Bean
#Primary
#ConfigurationProperties("spring.datasource")
public DataSourceProperties regularDataSourceProperties() {
return new DataSourceProperties();
}
#Bean
#Primary
#ConfigurationProperties("spring.datasource.configuration")
public HikariDataSource regularDataSource() {
HikariDataSource dataSource = regularDataSourceProperties().initializeDataSourceBuilder()
.type(HikariDataSource.class).build();
return dataSource;
}
#Bean
#ConfigurationProperties("spring.token-datasource")
public DataSourceProperties tokenDataSourceProperties() {
return new DataSourceProperties();
}
#Bean
#ConfigurationProperties("spring.token-datasource.configuration")
public DataSource tokenDataSource() {
DataSource dataSource = tokenDataSourceProperties().initializeDataSourceBuilder().build();
return dataSource;
}
}
It's interesting that the original code snippet does NOT work, while it's still the approach recommened by many Java tutorials and stackoverflow posts. For example,
https://www.baeldung.com/spring-data-jpa-multiple-databases
Spring Boot Configure and Use Two DataSources

Related

How to set multi-datasource by spring data JDBC? MySQL

I try to set multiple datasource, but just always only can get primary datasource.
source Primary:
#Configuration
#EnableJdbcRepositories("com.xxx.repository.abc")
public class AbcDataSourceConfig {
#Bean
#Primary
#ConfigurationProperties(prefix="spring.abc")
public DataSourceProperties abcDataSourceProperties() {
return new DataSourceProperties();
}
#Bean
#Primary
public DataSource abcDataSource(#Qualifier("abcDataSourceProperties") DataSourcePropertiesproperties) {
log.debug("abc properties : {}", properties.getDriverClassName());
return properties.initializeDataSourceBuilder().build();
}
#Bean
#Primary
public NamedParameterJdbcOperations abcJdbcOperations(#Qualifier("abcDataSource") DataSource abcDs) {
return new NamedParameterJdbcTemplate(abcDs);
}
}
source 2:
#Configuration
#EnableJdbcRepositories(jdbcOperationsRef = "cbaJdbcOperations", basePackages = "com.xxx.repository.cba")
public class CbaDataSourceConfig {
#Bean
#ConfigurationProperties(prefix="spring.cba")
public DataSourceProperties cbaDataSourceProperties() {
return new DataSourceProperties();
}
#Bean
public DataSource cbaDataSource(#Qualifier("cbaDataSourceProperties") DataSourceProperties properties) {
return properties.initializeDataSourceBuilder().build();
}
#Bean
public NamedParameterJdbcOperations cbaJdbcOperations(#Qualifier("cbaDataSource") DataSource cbaDs) {
return new NamedParameterJdbcTemplate(cbaDs);
}
}
when I try to select cba's data, it show is error: "Caused by: java.sql.SQLSyntaxErrorException: Table 'abc.cba_data' doesn't exist".
but cba_data is in the cba datasource.
how to setting is correct?

Spring boot 2 IllegalArgumentException: Property 'driverClassName' must not be empty

I am trying to establish 2 database connections in spring boot 2 app. I am getting below error:
java.lang.IllegalArgumentException: Property 'driverClassName' must not be empty
Here is my configuration on Primary db
#Configuration
#EnableTransactionManagement
#EnableJpaRepositories(
entityManagerFactoryRef = "myEntityManagerFactory",
basePackages = {"com.cmp.myapp.repository"})
public class PrimDBConfig {
#Primary
#Bean(name = "primaryDataSource")
#ConfigurationProperties(prefix = "spring.datasource")
public DataSource dataSource() {
return DataSourceBuilder.create().build();
}
#PersistenceContext(unitName = "primdb")
#Primary
#Bean(name = "entityManagerFactory")
public LocalContainerEntityManagerFactoryBean entityManagerFactory(
EntityManagerFactoryBuilder builder,
#Qualifier("primaryDataSource") DataSource dataSource) {
return builder
.dataSource(dataSource)
.packages("com.cmp.myapp.dto")
.persistenceUnit("primdb")
.build();
}
#Primary
#Bean(name = "transactionManager")
public PlatformTransactionManager transactionManager(
#Qualifier("myEntityManagerFactory") EntityManagerFactory entityManagerFactory) {
return new JpaTransactionManager(entityManagerFactory);
}
}
Here is the secondary db connection
#Configuration
public class SecDBConfig {
#Autowired
Environment env;
#Bean(name = "secDataSource")
#ConfigurationProperties(prefix = "secondary.datasource")
public DataSource wiseDataSource() {
DriverManagerDataSource ds = new DriverManagerDataSource();
ds.setUrl(env.getProperty("secondary.datasource.jdbc-url"));
ds.setDriverClassName(env.getProperty("secondary.datasource.driverClassName"));
ds.setUsername(env.getProperty("secondary.datasource.username"));
ds.setPassword(env.getProperty("secondary.datasource.password"));
return ds;
}
}
My application.properties file as below:
spring.datasource.jdbc-url=jdbc:sqlserver://xxx
spring.datasource.driverClassName=com.microsoft.sqlserver.jdbc.SQLServerDriver
spring.datasource.username=xxxx
spring.datasource.password=xxxx
spring.datasource.validation-query=select 1
spring.datasource.testOnBorrow=true
secondary.datasource.jdbc-url=jdbc:as400://xxxxxx;
secondary.datasource.driverClassName=com.ibm.as400.access.AS400JDBCDriver
secondary.datasource.username=xxxxx
secondary.datasource.password=xxxxxx
secondary.datasource.testOnBorrow=true
secondary.datasource.validation-query=select 1 from sysibm.sysdummy1
May I know what I'm doing wrong here, please advice. Thanks in advance
One of the reasons I can think of is, you have added this prefix
#ConfigurationProperties(prefix = "secondary.datasource")
Then accessing driverClassName using complete path
ds.setDriverClassName(env.getProperty("secondary.datasource.driverClassName"));
I think you should remove the prefix and try accessing.
This blog may also help you. https://howtodoinjava.com/spring-boot2/datasource-configuration/

Spring multiple datasource without primary

I have a problem, I need to create two DataSource but I cannot use #Primary because in other module I also have two DataSource and then in third module I include both modules, so there are two primary modules.
I wanted to use #Qualifier but it does not work.
#Bean(name = "secondDataSourceProperties")
#ConfigurationProperties("second")
public DataSourceProperties secondDataSourceProperties() {
return new DataSourceProperties();
}
#Bean(name = "secondDataSource")
#ConfigurationProperties("second.configuration")
public DataSource secondDataSource(#Qualifier("secondDataSourceProperties") DataSourceProperties dataSourceProperties) {
HikariDataSource ds = dataSourceProperties.initializeDataSourceBuilder().type(HikariDataSource.class).build();
ds.setConnectionTestQuery("SELECT 1");
ds.setConnectionInitSql("SELECT 1");
ds.setPoolName("jdbc/second");
return ds;
}
#Bean(name = "secondTransactionManager")
public DataSourceTransactionManager secondDataSourceTransactionManager(#Qualifier("secondDataSource") DataSource dataSource) {
return new DataSourceTransactionManager(dataSource);
}
#Bean
#ConfigurationProperties("first")
public DataSourceProperties firstDataSourceProperties() {
return new DataSourceProperties();
}
#Bean(name = "firstDataSource")
#ConfigurationProperties("first.configuration")
public DataSource firstDataSource(#Qualifier("firstDataSourceProperties") DataSourceProperties dataSourceProperties) {
HikariDataSource ds = dataSourceProperties.initializeDataSourceBuilder().type(HikariDataSource.class).build();
ds.setConnectionTestQuery("SELECT 1");
ds.setConnectionInitSql("SELECT 1");
ds.setPoolName("jdbc/first");
return ds;
}
#Bean(name = "firstTransactionManager")
public DataSourceTransactionManager firstDataSourceTransactionManager(#Qualifier("firstDataSource") DataSource dataSource) {
return new DataSourceTransactionManager(dataSource);
}
Error I'm getting is:
No qualifying bean of type
'org.springframework.boot.autoconfigure.jdbc.DataSourceProperties'
available: expected single matching bean but found 5:
secondDataSourceProperties,firstDataSourceProperties,3DataSourceProperties,4DataSourceProperties,spring.datasource-org.springframework.boot.autoconfigure.jdbc.DataSourceProperties
I use DataSource like that in code:
#Autowired
#Qualifier("first")
private DataSource dataSource;
and
#Transactional(value = "firstTransactionManager")
How about just doing the following? Without #Qualifier mess? The name of the method for #Bean is by default used as the "qualifier" of the bean.
#Bean
#ConfigurationProperties("first")
public DataSourceProperties firstDataSourceProperties() {
return new DataSourceProperties();
}
#Bean
#ConfigurationProperties("second")
public DataSourceProperties secondDataSourceProperties() {
return new DataSourceProperties();
}
#Bean
public DataSource firstDataSource(DataSourceProperties firstDataSourceProperties) {
HikariDataSource ds = firstDataSourceProperties.initializeDataSourceBuilder().type(HikariDataSource.class).build();
ds.setConnectionTestQuery("SELECT 1");
ds.setConnectionInitSql("SELECT 1");
ds.setPoolName("jdbc/first");
return ds;
}
#Bean
public DataSource secondDataSource(DataSourceProperties secondDataSourceProperties) {
HikariDataSource ds = secondDataSourceProperties.initializeDataSourceBuilder().type(HikariDataSource.class).build();
ds.setConnectionTestQuery("SELECT 1");
ds.setConnectionInitSql("SELECT 1");
ds.setPoolName("jdbc/second");
return ds;
}
#Bean
public DataSourceTransactionManager firstTransactionManager(DataSource firstDataSource) {
return new DataSourceTransactionManager(firstDataSource);
}
#Bean
public DataSourceTransactionManager secondTransactionManager(DataSource secondDataSource) {
return new DataSourceTransactionManager(secondDataSource);
}
You can just autowire with this name only;
#Autowire
private DataSource firstDateSource;
and
#Transactional("secondTransactionManager")
Helped adding
#EnableAutoConfiguration(exclude={DataSourceAutoConfiguration.class})
Or if you use #SpringBootApplication
#SpringBootApplication(scanBasePackages={"a.b.c"}, exclude = {DataSourceAutoConfiguration.class})

problem with writing to 2 databases from Spring boot

I was trying this code from github:https://github.com/kodinor/spring-data-many-dbs
It's an example of how to use multiple db's from a Spring-boot application. it worked fine but I added the Spring-boot-starter-web dependency and now I'm getting an error:
>Method requestMappingHandlerMapping in >org.springframework.boot.autoconfigure.web.servlet.WebMvcAutoConfiguration$En>ableWebMvcConfiguration required a single bean, but 2 were found:
> - productDSEmFactory: defined by method 'productDSEmFactory' in class >path resource [com/kodinor/configuration/ProductDBConfiguration.class]
> - userDSEmFactory: defined by method 'userDSEmFactory' in class path >resource [com/kodinor/configuration/UserDBConfiguration.class]
>
>
>Action:
>
>Consider marking one of the beans as #Primary, updating the consumer to >accept multiple beans, or using #Qualifier to identify the bean that should >be consumed<br>
I have two config files:
#Configuration
#EnableTransactionManagement
#EnableJpaRepositories(basePackageClasses = UserRepository.class, entityManagerFactoryRef = "userDSEmFactory", transactionManagerRef = "userDSTransactionManager")
public class UserDBConfiguration {
#Primary
#Bean
#ConfigurationProperties("spring.datasource1")
public DataSourceProperties userDSProperties() {
return new DataSourceProperties();
}
#Primary
#Bean
public DataSource userDS(#Qualifier("userDSProperties") DataSourceProperties userDSProperties) {
return userDSProperties.initializeDataSourceBuilder().build();
}
#Bean
public LocalContainerEntityManagerFactoryBean userDSEmFactory(#Qualifier("userDS") DataSource userDS, EntityManagerFactoryBuilder builder) {
return builder.dataSource(userDS).packages(User.class).build();
}
#Primary
#Bean
public PlatformTransactionManager userDSTransactionManager(EntityManagerFactory userDSEmFactory) {
return new JpaTransactionManager(userDSEmFactory);
}
}
#Configuration
#EnableTransactionManagement
#EnableJpaRepositories(basePackageClasses = ProductRepository.class, entityManagerFactoryRef = "productDSEmFactory", transactionManagerRef = "productDSTransactionManager")
public class ProductDBConfiguration {
#Bean
#ConfigurationProperties("spring.datasource2")
public DataSourceProperties productDSProperties() {
return new DataSourceProperties();
}
#Bean
public DataSource productDS(#Qualifier("productDSProperties") DataSourceProperties productDSProperties) {
return productDSProperties.initializeDataSourceBuilder().build();
}
#Bean
public LocalContainerEntityManagerFactoryBean productDSEmFactory(#Qualifier("productDS") DataSource productDS, EntityManagerFactoryBuilder builder) {
return builder.dataSource(productDS).packages(Product.class).build();
}
#Bean
public PlatformTransactionManager productDSTransactionManager(EntityManagerFactory productDSEmFactory) {
return new JpaTransactionManager(productDSEmFactory);
}
}
Application.properties:
spring.jpa.hibernate.ddl-auto=create
spring.jpa.generate-ddl=true
spring.datasource1.url=jdbc:mysql://localhost:3306/userdb?autoReconnect=true&useSSL=false
spring.datasource1.username=user
spring.datasource1.password=pass
spring.datasource2.url=jdbc:mysql://localhost:3306/productdb?autoReconnect=true&useSSL=false
spring.datasource2.username=user
spring.datasource2.password=pass
And a simple main app that adds some init data:
#SpringBootApplication
public class SpringDataManyDbsApplication {
public static void main(String[] args) {
SpringApplication.run(SpringDataManyDbsApplication.class, args);
}
#Autowired
private UserRepository userRepository;
#Autowired
private ProductRepository productRepository;
#PostConstruct
void init () {
User user = new User("john", "doe");
userRepository.save(user);
Product product = new Product("chair", 5);
productRepository.save(product);
}
}
I tried to add the #Primary annotation to userDSEmFactory. The error goes away but the product data isn't saved anymore. Any ideas how to save this problem? I don't have a lot of experience with Spring-boot and I've read dozens of articles but many seem to do things in a different way. Thanks so much for helping me out!
update
I've added the #Primary annotation like this:
#Primary
#Bean
public LocalContainerEntityManagerFactoryBean userDSEmFactory(#Qualifier("userDS") DataSource userDS, EntityManagerFactoryBuilder builder) {
return builder.dataSource(userDS).packages(User.class).build();
}
No there are no more errors, but only the user is being saved in the primary db and nothing is being saved to the product (second) database.
So if anybody has suggestions what could be the cause of that..
You are missing #Primary annotation on your userDSEmFactory
#Primary
#Bean
public LocalContainerEntityManagerFactoryBean userDSEmFactory(#Qualifier("userDS") DataSource userDS, EntityManagerFactoryBuilder builder) {
return builder.dataSource(userDS).packages(User.class).build();
}
And also it is goot to use once #EnableTransactionManagement at your main class.

Executing queries with EntityManager in Spring+Hibernate project

I have a Spring + Hibernate project with configured DataSource:
#Configuration
#Component
public class DataSourceConfig {
#ConfigurationProperties(prefix = "spring.datasource")
#Bean
public DataSource getDataSource() {
return DataSourceBuilder
.create()
.url("jdbc:firebirdsql://**********:3050/mydb?charSet=utf8")
.username("*****")
.password("*****")
.driverClassName("org.firebirdsql.jdbc.FBDriver")
.build();
}
}
EntityManagerFactoriesConfig:
#Configuration
public class EntityManagerFactoriesConfig {
#Autowired
private DataSource dataSource;
#Bean(name = "entityManagerFactory")
public LocalContainerEntityManagerFactoryBean emf() {
LocalContainerEntityManagerFactoryBean emf = new LocalContainerEntityManagerFactoryBean();
emf.setDataSource(dataSource);
emf.setPackagesToScan(
new String[]{"ekoncept"});
emf.setJpaVendorAdapter(
new HibernateJpaVendorAdapter());
return emf;
}
}
TransactionManagerConfig:
#EnableTransactionManagement
public class TransactionManagersConfig {
#Autowired
EntityManagerFactory emf;
#Autowired
private DataSource dataSource;
#Bean(name = "transactionManager")
public PlatformTransactionManager transactionManager() {
JpaTransactionManager tm =
new JpaTransactionManager();
tm.setEntityManagerFactory(emf);
tm.setDataSource(dataSource);
return tm;
}
}
and that's all, no db config in application.properties.
Now I want to access EntityManager or EntityManagerFactory to execute SQL. I know that I should use #Query annotation with Spring, but in this one case I need to execute a simple SQL. I was trying to do it in many ways and finally in all cases I got EntityManager==null. How to get access to Entity manager or EntityManagerFactory??

Resources