how to configure muiti datasource in springboot + mybatis - java

Here is the problem, I need to configure additional datasource in my project.
There was only one datasource before, so I just configured one single datasource in application.properties, and then i coded like this:
#Repository
public class DrClusterManagerImpl implements DrClusterManager {
private final SqlSession sqlSession;
public DrClusterManagerImpl(SqlSession sqlSession) {
this.sqlSession = sqlSession;
}
}
it works fine, thanks to springboot, but now I'm puzzled that how can i inject a specific sqlsession into this bean?
I've checked springboot reference, but it's not helpful.
https://docs.spring.io/spring-boot/docs/current/reference/htmlsingle/#howto-two-datasources

Related

Java Spring boot multiple databases, repository is using the wrong database

(help to create multiple databases in spring boot in my answer)
I want to make a spring boot application for different games. The data of each game is placed in a separate database. I have been trying to connect a secondary database to my existing application, but the controller/repository keeps using the primary database.
This is what the controller for the second database looks like:
#RestController
#RequestMapping("/valorant")
#CrossOrigin
public class UserController {
#Autowired
private UserRepository userRepository;
#Autowired
#Qualifier("valorantDataSource")
private DataSource valorantDataSource;
#GetMapping("/users")
public Iterable<User> getUsers() {
return userRepository.findAll();
}
}
This is the repository that should take the data from the second database:
#Repository
#Qualifier("valorantDataSource")
public interface UserRepository extends JpaRepository<User, Long> {
}
I have defined the datasources in a different file. (Adding .url(), .username() and .password() to the datasourcebuilder did not help)
#Configuration
public class DatasourceConfig {
#Primary
#Bean(name = "apexDataSource")
#ConfigurationProperties(prefix = "spring.datasource")
public DataSource dataSource() {
return DataSourceBuilder.create().build();
}
#Bean(name = "valorantDataSource")
#ConfigurationProperties(prefix = "spring.second-datasource")
public DataSource valorantDataSource() {
return DataSourceBuilder.create()
.build();
}
}
# First database
spring.datasource.jdbc-url=jdbc:mysql://localhost:3306/apex
spring.datasource.username=root
spring.datasource.password=
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
# Second database
spring.second-datasource.jdbc-url=jdbc:mysql://localhost:3306/valorant
spring.second-datasource.username=root
spring.second-datasource.password=
spring.second-datasource.driver-class-name=com.mysql.cj.jdbc.Driver
When I used postman to go to http://localhost:8080/valorant/users, i got the error:
java.sql.SQLSyntaxErrorException: Table 'apex.user' doesn't exist
So it seems like the application is trying to look in the wrong database.
Thanks in advance.
You should not use #Primary annotation in this case.
When you mark bean with #Primary, it will be selected for dependency injection
Marking your beans with #Qualifier would be enough to select concrete bean.
I ended up using an existing project. I cloned this repo, changed some names and values and it was ready to use in my own project.

"BasicBatchConfigurer" has protected access - Spring Batch/Spring Boot not persisting data to database

I recently migrated my spring boot/batch Java application from spring-boot/spring-framework (respectively) 1.x.x/4.x.x to => 2.x.x/5.x.x (2.2.4/5.2.3 to be specific). The problem is something is definitely wrong (in my opinion) with the transaction/entity manager, as when the .saveAll() method is called from the JpaRepository class of my database persistance layer, it jumps into the SpringAOP framework/libarary code and into a infinite loop. I see it returning a "DefaulTransaction" object from a method (invoke()). My application on 1.x.x/4.x.x when it worked, would return the actual ArrayList here of my entities. I am using spring-boot-starter, spring-boot-starter-web, spring-boot-starter-data-jpa, spring-boot-starter-batch, and hibernate/hibernate-envers/hibernate-entitymanager (also of course many other dependencies, let me know if you would like me to list them).
After some research, I'm finding people are saying that Spring Batch #EnableBatchProcessing annotation sets up a default transaction manager, which if I'm using JPA could be causing issues. Reference:
https://github.com/spring-projects/spring-boot/issues/2363
wilkinsona suggested defining this Bean in my #Configuration class:
#Bean
public BatchConfigurer batchConfigurer(DataSource dataSource, EntityManagerFactory entityManagerFactory) {
return new BasicBatchConfigurer(dataSource, entityManagerFactory);
}
I'm getting an error when I do this because its saying the BasicBatchConfigurer() has protected access. What is the best way to instantiate this?
I also saw some people saying removing the #EnableBatchProcessing annotation fixes the persistance to database issue, but when I remove this, I lose the ability to Autowire my JobBuilderFactory and StepBuilderFactory. Is there a way to remove the annotation and get these objects in my code so I can at-least test if this works? Sorry, I'm not completely a master with Spring Batch/Spring.
In my #Configuration class, I am using the PlatformTransactionManager. I am setting up my JobRepository something like this.:
#Bean
public JobRepository jobRepository(PlatformTransactionManager transactionManager,
#Qualifier("dataSource") DataSource dataSource) throws Exception {
JobRepositoryFactoryBean jobRepositoryFactoryBean = new JobRepositoryFactoryBean();
jobRepositoryFactoryBean.setDataSource(dataSource);
jobRepositoryFactoryBean.setTransactionManager(transactionManager);
jobRepositoryFactoryBean.setDatabaseType("POSTGRES");
return jobRepositoryFactoryBean.getObject();
}
I can provide any other information if needed. Another question is - if I was using the same code basically, transaction manager, entity manager etc.. how was old my code working on 1.x.x? Could I have a wrong dependency somewhere in my pom.xml such that my new migrated code is using a wrong method or something from the wrong dependency?
By default, #EnableBatchProcessing configures Spring Batch to use a DataSourceTransactionManager if you provide a DataSource. This transaction manager knows nothing about your JPA context. So if you want to use a JPA repository to save data, you need to configure Spring Batch to use a JpaTransactionManager.
Now in order to provide a custom transaction manager, you need to register a BatchConfigurer and override the getTransactionManager() method, something like:
#Bean
public BatchConfigurer batchConfigurer(DataSource dataSource) {
return new DefaultBatchConfigurer(dataSource) {
#Override
public PlatformTransactionManager getTransactionManager() {
return new JpaTransactionManager();
}
};
}
This is explained in the Configuring A Job section and in the Javadoc of #EnableBatchProcessing.

How does Spring deal with #Autowired on fields that required a parameter? In this case, JdbcTemplate with DataSource

I have a SpringBoot app that I want to connect to my MySQL database and I want to connect it with JDBC (by itself, not using JPA). And from what I have seen on articles, one way to achieve this is with JdbcTemplate and DataSource objects).
Now I have a RestController where I call my database, "CoffeeShop" which has me with the following class/code:
#RestController
public class MenuController {
#Autowired
private DataSource dataSource;
#Autowired
private JdbcTemplate jdbcTemplate;
private String menuQuery = "SELECT * FROM menu";
#CrossOrigin(origins = "http://localhost:4200")
#GetMapping(path="/menu")
public String getMenu(){
jdbcTemplate.query(menuQuery, (rs, rowNum) -> new Menu(rs.getString("name"))).forEach(
customer-> System.out.println(customer.getName()));
return "worked";
}
private List<Menu> organizeMenu() {
return null;
}
}
If my understanding is correct, I expect that dataSource will be able to see in my application.properties file the following contents when being compiled and then Spring figures out that jdbcTemplate requires it?:
spring.jpa.hibernate.ddl-auto=none
spring.datasource.driverclassname = com.mysql.jdbc.Driver
hibernate.dialect=org.hibernate.dialect.MySQL5Dialect
spring.datasource.url=jdbc:mysql://${MYSQL_HOST:localhost}:3306/CoffeeShop?useUnicode=true&useLegacyDatetimeCode=false&serverTimezone=UTC
spring.datasource.username=springuser
spring.datasource.password=ThePassword
To my surprise this worked, my code queried the DB and logged the correct output. But I'm not sure how this worked since jdbcTemplate required dataSource?
See the Spring Boot Getting Started | Accessing Relational Data using JDBC with Spring guide, which says:
Spring Boot supports H2 (an in-memory relational database engine) and automatically creates a connection. Because we use spring-jdbc, Spring Boot automatically creates a JdbcTemplate. The #Autowired JdbcTemplate field automatically loads it and makes it available.
This is what you get with the auto-configuration provided by Spring Boot: Fully functional JdbcTemplate automatically created and configured from the application.properties file.
FYI: The JdbcTemplate is already configured to use the DataSource, so you don't need to auto-wire the DataSource. As you can see in your own code, the dataSource field isn't used anywhere, so you should remove it.
It is actually the DataSource that is auto-configured from the application.properties file.

Spring Boot Weblogic 12c JNDI DataSources: injection not working gives NullPointerException

I had asked a question here but was incomplete and was marked as duplicate here
So based on the question already asked - one particular answer by #surasin-tancharoen seemed to be what I needed.
However trying that too gives me a NullPointerException since the data source is never created / injected.
Here are the details:
In the below code I have defined 2 beans. I have defined both datasources with #Qualifier annotation and #ConfigurationProperties to read the JNDI name from properties file.
#Configuration
public class DataSourceConfig {
#Bean
#Primary
#Qualifier("ds1")
#ConfigurationProperties(prefix="spring.datasource1")
public DataSource primaryDataSource() {
return DataSourceBuilder.create().build();
}
#Bean
#Qualifier("ds2")
#ConfigurationProperties(prefix="spring.datasource2")
public DataSource secondaryDataSource() {
return DataSourceBuilder.create().build();
}
}
In application.properties:
spring.datasource1.jndi-name=AbcDS
spring.datasource2.jndi-name=XyzDS
Then in my DAO - I am trying to use this datasource:
#Autowired
#Qualifier("ds1")
DataSource dataSource;
However the datasource is not injected since I get a NullPointerException at this line of code:
conn = dataSource.getConnection();
All of this is being attempted by deploying the Spring Boot application to Weblogic 12c
The solution is related to incorrect usage of 'DataSourceBuilder' which should not be used for a JNDI datasource.
Here is how I got it working :
( deploying spring boot war on weblogic and consuming datasources defined in weblogic )
First we specify the datasources defined in weblogic - here we specify the JNDI name of the datasources defined in weblogic:
spring.datasource.xyz.jndi-name=XYZDS
spring.datasource.test.jndi-name=TESTDS
This is where the Datasource is created using above defined properties and exposed:
We are injecting the properties from application.properties into string variables to store JNDI names of datasources.
#Configuration
public class DataSourceConfig {
#Value( "${spring.datasource.xyz.jndi-name}" )
private String xyzJndiName;
#Value( "${spring.datasource.test.jndi-name}" )
private String testJndiName;
#Bean(name = "XyzDataSource")
public DataSource getXyzDataSource() throws Exception {
JndiDataSourceLookup dataSourceLookup = new JndiDataSourceLookup();
return dataSourceLookup.getDataSource(xyzJndiName);
}
#Bean(name = "TestDataSource")
public DataSource getTestDataSource() throws Exception {
JndiDataSourceLookup dataSourceLookup = new JndiDataSourceLookup();
return dataSourceLookup.getDataSource(testJndiName);
}
A few key points to note :
We should not be using 'DataSourceBuilder' for a JNDI datasource - it will not work.
This was the reason why it was not working in my case .
Now I am using 'JndiDataSourceLookup' which does the trick.
Another thing to note is that we need to use the standard #Bean annotation with the attribute 'name'.
This will be important in the piece of code that consumes this datasource.
So now the datasources are created successfully and exposed.
OK time to consume :
#Repository
public class SomeDaoImpl {
#Autowired
#Qualifier("XyzDataSource")
DataSource dataSource;
#Override
public List <String> create(Employee request) {
Connection conn = null;
conn = dataSource.getConnection();
Here we are using the #Qualifier annotation to pickup the appropriate datasource .
Thats it - this works now.
I tried it with the other data source - it worked as well.
NOTE:
I would not like to accept my own answer - so I will wait for a couple of days if anyone else has a better and more elegant solution please answer - will be happy to accept your answer rather than answering my own question and accepting !

Using Spring #Value in bean construction

When my SpringApplicationContext is being boostrapped together, I want to get some values for that out of a properties file. From what I've read, it's best to use PropertySourcesPlaceholderConfigurer for this. I'm creating that bean in my spring java config and it is properly resolving the properties.
The problem is when I'm trying to use those properties in the construction of my other spring beans, like in my hibernate config. In my hibernate config, I have the following field:
#Value(value = "${myapp.database-access.url}")
public static String DB_ACCESS_URL;
Then I use that property to declare the url to access the database in my datasource bean like this:
#Bean
public DataSource dataSource() {
BasicDataSource dataSource = new BasicDataSource();
dataSource.setDriverClassName(DRIVER_CLASS_NAME);
dataSource.setUrl(DATABASE_URL);
dataSource.setUsername(DATABASE_USERNAME);
dataSource.setPassword(DATABASE_PASSWORD);
return dataSource;
}
Unfortunately the value is null at that point so my application bootstrapping fails. Post-construction the value is available, but apparently not mid-construction. Is there a way to get the #Value annotation to work for me in this circumstance, or do I have to use something else?
If I have to use something else, does anyone have any suggestions?
Spring does not allow injecting to static fields.
One approach:
public class Config {
public static String DB_ACCESS_URL;
#Value(value = "${myapp.database-access.url}")
public void setDbAccessUrl(String dbAccessUrl) {
Config.DB_ACCESS_URL = dbAccessUrl;
}
}
I think this is similar to the issue I was facing with the #PropertySource annotation where #Value wasn't injected in #Configuration POJOs. There are 2 ways I found to solve this:
1) Create a Spring XML config (if you don't have one already) which you'll refer to at your #Configuration class using #ImportResource and have in there a single entry: <context:property-placeholder />. That config should make #Value injection on the #Configuration class to work.
2) Don't use #Value in the #Configuration POJO but instead inject Environment and retrieve the props from there:
#Autowired Environment env;
//and further down at your dataSource bean retrieve the properties
//...
env.getProperty("myapp.database-access.url");
This is late but you can simply use #Value with constructor injection public DataSource dataSource(#Value(value = "${myapp.database-access.url}"))

Categories