JPA with Multiple database and application.yml - spring-boot

My application (spring-boot) need to access multiple databases.
For some reason, I can't find a propper example using application.yml
This example: http://smasue.github.io/spring-yml-datasources
-> application.yml but not jpa
this example: https://www.baeldung.com/spring-data-jpa-multiple-databases
-> jpa but not application.yml
So I created a very simple project based on this gs: https://spring.io/guides/gs/accessing-data-jpa/
You can find my simple example here: https://github.com/Tyvain/JpaMultipleDatabaseAndApplicationYml
spring:
datasource:
db-1:
url: jdbc:postgresql://10.10.100.100:5432/db1
username: db1
password: db1
driver-class-name: org.postgresql.Driver
db-2:
url: jdbc:postgresql://10.10.100.100:5432/db2
username: db2
password: db2
driver-class-name: org.postgresql.Driver
From here, I am not sure how to affect my repositories to each database.
This example https://www.baeldung.com/spring-data-jpa-multiple-databases is unclear as it's based on properties... and I am not sure how to adapt all code
#PropertySource({ "classpath:persistence-multiple-db.properties" })
[...]
properties.put("hibernate.hbm2ddl.auto",
env.getProperty("hibernate.hbm2ddl.auto"));
properties.put("hibernate.dialect",
env.getProperty("hibernate.dialect"));
How would you assign each repo (CustomerRepositoryDB1 and CustomerRepositoryDB2) to their database ?

Properties and yaml are two absolutely equal means of configuration. The format is only slgihtly different.
You could just replace foo.properties with foo.yml and
com.foobar.var1=value
com.foobar.var2=value2
simply becomes
com.foobar:
var1: value
var2: value2
Plus there is an official Spring Data Repository on Github full of examples. There is even one with two datasources, configured completely in code, no yaml or properties needed:
https://github.com/spring-projects/spring-data-examples/tree/master/jpa/multiple-datasources
In Application.java they exclude the AutoConfig classes and then in each package (Order, Customer), they have a Config class, configuring the datasource. Then there is no need to set the datasource on the repository itself, as that is handled by package scanning with:
factoryBean.setPackagesToScan(OrderConfig.class.getPackage().getName());
in the config. To reiterate: it's datasource per java package, no annotation on the Repository needed.

Related

Define multi level spring application.yml hierarchy

However, the most important profiles-related feature that Spring Boot brings is profile-specific properties files. These have to be named in the format application-{profile}.properties.
Spring Boot will automatically load the properties in an application.properties file for all profiles, and the ones in profile-specific .properties files only for the specified profile.
-- from https://www.baeldung.com/spring-profiles
So this is possible:
application.yml:
spring:
datasource:
driver-class-name: com.microsoft.sqlserver.jdbc.SQLServerDriver
application-dev.yml:
spring:
datasource:
url: jdbc:sqlserver://development.server:14331;databaseName=Dev;
When you use the dev profile, Spring would load the 'driver-class-name' from application.yml
I am searching for something like this:
application-dev-myconfig.yml:
spring:
datasource:
username: myVeryOwnDbUser
password: myVeryOwnPassword
Now if you use the dev-myconfig profile it should take the 'url' from dev profile and the 'driver-class-name' from the default profile.
Is it possible to have another level, like this? Or how would you do that?
Thanks and have a good Day :)
You can have more than one active spring profile and that would solve your problem.
In your case -Dspring.profiles.active=dev,dev-myconfig

Spring boot 2.4.x cannot handle multi document yml files from config server

Java version: 8
Spring Boot version: 2.4.1
Spring Cloud version: 2020.0.0, specifically I use a Spring Cloud Config Server connected to GIT and our services are Spring Cloud Config Clients.
I have migrated away from using bootstrap.yml and started using spring.config.import and spring.config.activate.on-profile as mentioned in the documentation here and here
My configuration in my service, who is a client to the config server looks like this:
server.port: 9001
spring:
application.name: my-rest-service
config.import: configserver:http://localhost:8888
cloud.config.profile: ${spring.profiles.active}
My configuration in the config server looks like this:
application.yml (has two documents separated by the ---)
logging:
file.name: <omitted>
level:
root: INFO
---
spring:
config.activate.on-profile: dev
logging.level.root: DEBUG
my-rest-sercive.yml (has two documents separated by the ---)
spring:
datasource:
driver-class-name: <omitted>
username: <omitted>
password: <omitted>
---
spring:
config.activate.on-profile: dev
datasource.url: <omitted>
Because there is a profile "dev" active, I successfully get the following 4 configurations from config server:
application.yml: general logging level
application.yml: specific logging for dev
my-rest-sercive.yml: general datasource properties
my-rest-sercive.yml: specific datasource url for dev
I can see these 4 sources successfully being fetched when I use my browser or when I debug or in the logs when I lower the loglevel to trace:
o.s.b.c.config.ConfigDataEnvironment : Adding imported property source 'configserver:https://git.company.com/path.git/file:C:\configservergit\config\my-rest-service.yml'
o.s.b.c.config.ConfigDataEnvironment : Adding imported property source 'configserver:https://git.company.com/path.git/file:C:\configservergit\config\my-rest-service.yml'
o.s.b.c.config.ConfigDataEnvironment : Adding imported property source 'configserver:https://git.company.com/path.git/file:C:\configservergit\config\application.yml'
o.s.b.c.config.ConfigDataEnvironment : Adding imported property source 'configserver:https://git.company.com/path.git/file:C:\configservergit\config\application.yml'
However, notice that because I use multi document yml files, out of these 4 property sources only TWO unique names are used.
In a later step, when Spring creates the data source bean, he complains he cannot find the data source URL. If I debug the spring bean factory I can indeed see that out of the 4 property files returned by the config server, only two have remained (the ones that don't contain the dev profile specific configuration). I assume this is because they have an identical name and they overwrite each other. This is an effect of this piece of code in the MutablePropertySource.class:
public void addLast(PropertySource<?> propertySource) {
synchronized(this.propertySourceList) {
this.removeIfPresent(propertySource); <-- this is the culrprit!
this.propertySourceList.add(propertySource);
}
}
This is a breaking change from Spring 2.3/Spring Cloud Hoxton where it correctly collected all properties. I think spring cloud needs to change the config server so that every document within a yml has has a unique name when returned to Spring. This is exactly how Spring Boot handles multi document yml files, by appending the String (documenyt #1) to the property source name
I found an interesting note about profiles and multi document yml, basically saying it is not supported, but this doesn't apply to my use case because my yml files are not profiles based (there is no -{profileName} in the last part of the file name).
This is a known issue with the new release. We can track the issue here on the spring cloud config server github page.
The workaround seems to be stop using multi document yml files and use multiple distinct files with the profile name in the filename.

Why is the placeholder not set properly in hikari.data-source-properties

I am setting the hikari.data-source-properties with a placeholder, but the placeholder value does not get substituted. It is a spring-boot 2.1.4.RELEASE based application. My intention is to set the Session properties so the DBAs can identify my application. Currently I get a default JDBC Thin Client for my connection when I run SELECT PROGRAM FROM V$SESSION from the DB
Below is what I have in my application.yml file
spring:
datasource:
url: "jdbc:oracle:thin:#machine:1521:service_name"
driver-class-name: "oracle.jdbc.driver.OracleDriver"
hikari.data-source-properties:
v$session.program:${spring.application.name}
Below is what I have in my bootstrap.yml file
spring:
application:
name: ms-db-service
When I query the Oracle DB, I notice that the value for program is literally ${spring.application.name}, instead of the expected value ms-db-service
I have tried the below, with same logical result:
Tried redefining the spring.application.name again in the application.yml
Tried using a different locally defined key ${name}
Added the following bean
public static PropertySourcesPlaceholderConfigurer propertySourcesPlaceholderConfigurer() {
return new PropertySourcesPlaceholderConfigurer();
}
Tried specifying the property as such: spring.datasource.hikari.data-source-properties: v$session.program=${spring.application.name}
I tried the below items to gain more knowledge of the situation:
I tried using a placeholder for spring.datasource.url and that worked fine
I tried to read the value manually using the below code, and that shows the correct values: #Value("${spring.datasource.hikari.data-source-properties}") String propVal;
My conclusion is that HikariCp reads these values before the placeholder substitution is done. I don't understand the relative timing of when the substitution and datasource bean creation happen in the spring-boot bean life-cycle
I am trying to avoid using a bean for hikaricp datasource (not sure if that will even solve the issue), as I don't want to manually build the whole datasource as hikari supports huge number of properties.
So, how can I set the spring.datasource.hikari.data-source-properties in application.yml using a placeholder. And, is there any other way to assign spring.application.name to identify my current db connection?
You have to use it like this:
spring:
datasource:
hikari:
data-source-properties:
"[v$session.program]": ${spring.application.name}

Spring Boot: failed to load JNDI variables into yml configuration file

According to Spring Boot documentation about Externalized configuration, I tried to load a JNDI variable into my yml configuration file, like this:
spring:
# Show or not log for each sql query
jpa:
show-sql: java:global/bc-api-immop/hibernate/show_sql
And it doesn't work.
I have my variable in my JNDI context:
I also tried this:
spring:
# Show or not log for each sql query
jpa:
show-sql
jndi-name: java:global/bc-api-immop/hibernate/show_sql
But, still the same result.
Do you have any idea what I'm doing wrong?
As weird as it sounds, I also have this code, and it works:
spring:
# Set here configurations for the database connection
datasource:
jndi-name: java:jboss/datasources/bc-appli-as400-ds
Edit: When I do this, it works to (so my issue really comes from getting my JNDI variable):
spring:
# Show or not log for each sql query
jpa:
show-sql: true
The way you have written your yaml is the real problem here
The general concept is key: value so by writing show-sql spring boot expects a value of true or false as it appears on Appendix A. Common application properties so it is normal that your property configuration fails and I am pretty sure that this is showing up somewhere in your log files.
On the first example when you write show-sql: java:global/bc-api-immop/hibernate/show_sql you are actually answering the question "Should I show you the generated SQL statements" with "Hi spring Boot this is your datasource" which we both understand it makes no sence to spring boot :)
Your last statement, on the other hand, is correct. You are defining under the yaml collection item datasource the property jndi-name: with value java:jboss/datasources/bc-appli-as400-ds
I would also suggest spending 10 minutes to read this article Learn X in Y minutes
So a correct approach would probably be
spring:
# Set here configurations for the database connection
datasource:
jndi-name: java:jboss/datasources/bc-appli-as400-ds
jpa:
show-sql: true
Let me know if I can help you more

How to config mybatis mapper xml files in Spring boot's application.yml

I am a newbie to spring boot and I want to make my spring boot application integrate with mybatis and mysql database.
I generate the entity,mapper interfaces,and the mapper xml files with the generator. And I also use #MapperScan annotation to tell the application how to find the mappers, my application.yml looks like following:
spring:
datasource:
username: root
password:
url: jdbc:mysql://localhost:3306/bidding
driver-class-name: com.mysql.jdbc.Driver
mybatis:
mapperLocations: classpath:mapping/*.xml
But when I run the application with command 'mvn spring-boot:run', it always give me the error message 'Invalid bound statement (not found)'.
Is there anything I missed in my code?
Something wrong with the mapping of mybatis interface and xml.
check package of interface and xml ,they should be the same package
check the namespace in xml and package of xml,they should be same path

Resources