UPDATED
I'm getting an issue with Spring framework while I try to connect to 2 identical databases (with same tables) and then choose one of them to make requests. I'm using for this, 3 config files DbConfig, AbstractRoutingDataSource and an application.properties file. I'm currently not getting any ERRORS, while starting my application, but any time I use my restful webservice I get empty data, check the controller file below.
NOTE: I've followed all these links and still no results:
- Baeldung AbstractRoutingDataSource guide
- Alexander guide
- Dynamic DataSource Routing on Spring website
I'm working on Spring boot v2.0.5.
NOTE: please do NOT answer me if you don't have any ideas about AbstractRoutingDataSource class and how it works.
My application.properties file
spring.jpa.database=mysql
spring.jpa.open-in-view=false
# Main database DEV_database1
first.datasource.url = jdbc:mysql://localhost:3306/DEV_database1
first.datasource.username = root
first.datasource.password =
first.datasource.driver-class-name = com.mysql.jdbc.Driver
# second database DEV_database2
second.datasource.url = jdbc:mysql://localhost:3306/DEV_database2
second.datasource.username = root
second.datasource.password =
second.datasource.driver-class-name = com.mysql.jdbc.Driver
spring.jpa.database-platform = org.hibernate.dialect.H2Dialect
spring.jpa.show-sql = true
spring.jpa.hibernate.naming.implicit-strategy=org.hibernate.boot.model.naming.ImplicitNamingStrategyLegacyJpaImpl
spring.jpa.hibernate.naming.physical-strategy=org.hibernate.boot.model.naming.PhysicalNamingStrategyStandardImpl
#...
[UPDATE] My DbConfig file
@Configuration
public class DbConfig {
@Bean
@Primary
@ConfigurationProperties("first.datasource")
public DataSourceProperties firstDataSourceProperties() {
return new DataSourceProperties();
}
@Bean
@Primary
public HikariDataSource firstDataSource() {
return firstDataSourceProperties().initializeDataSourceBuilder().type(HikariDataSource.class).build();
}
@Bean
@ConfigurationProperties("second.datasource")
public DataSourceProperties secondDataSourceProperties() {
return new DataSourceProperties();
}
@Bean
public HikariDataSource secondDataSource() {
return secondDataSourceProperties().initializeDataSourceBuilder().type(HikariDataSource.class).build();
}
@Bean
DataSource dataSources() {
AbstractRoutingDataSource dataSource = new CustomerRoutingDataSource();
Map<Object, Object> resolvedDataSources = new HashMap<>();
resolvedDataSources.put(DbType.DATASOURCE1, firstDataSource());
resolvedDataSources.put(DbType.DATASOURCE2, secondDataSource());
dataSource.setDefaultTargetDataSource(firstDataSource()); // << default
dataSource.setTargetDataSources(resolvedDataSources);
return dataSource;
}
}
My CustomerRoutingDataSource.java
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.jdbc.datasource.lookup.AbstractRoutingDataSource;
/**
* AbstractRoutingDatasource can be used in place of standard DataSource implementations and enables a mechanism to
* determine which concrete DataSource to use for each operation at runtime.
*
* @author fre
*/
public class CustomerRoutingDataSource extends AbstractRoutingDataSource {
private static final Logger log = LoggerFactory.getLogger(CustomerRoutingDataSource.class);
@Override
protected Object determineCurrentLookupKey() {
log.info(">>> determineCurrentLookupKey thread: {}", Thread.currentThread().getName() );
log.info(">>> RoutingDataSource: {}", DbContextHolder.getDbType());
return DbContextHolder.getDbType();
}
}
My DbContextHolder.java
public class DbContextHolder {
private static final ThreadLocal<DbType> contextHolder =
new ThreadLocal<DbType>();
// set the datasource
public static void setDbType(DbType dbType) {
if(dbType == null){
throw new NullPointerException();
}
contextHolder.set(dbType);
}
// get the current datasource in use
public static DbType getDbType() {
return (DbType) contextHolder.get();
}
// clear datasource
public static void clearDbType() {
contextHolder.remove();
}
}
DbType.java
public enum DbType {
DATASOURCE1,
DATASOURCE2,
}
This is how I'm using it in Controller:
My Controller
@RestController
@RequestMapping("/api/user")
public class MembreController extends MainController {
@Autowired
MembreRepository membreRepository;
@GetMapping("/login")
public ResponseString login(String userName, String password) {
// setting the datasouce to DATASOURCE1
DbContextHolder.setDbType(DbType.DATASOURCE1);
// finding the list of user in DataSource1
List<Membres> list = membreRepository.findAll();
// clearing the current datasource
DbContextHolder.clearDbType();
for (Iterator<Membres> membreIter = list.iterator(); membreIter.hasNext();) {
Membres membre = membreIter.next();
if (membre.getUserName().equals(userName) && membre.getPassword().equals(password)) {
return new ResponseString("Welcome" + membre.getFirstName() + " " + membre.getLastName());
}
}
return new ResponseString("User not found");
}
}
my Application.java file
@ComponentScan
@SpringBootApplication
public class Application extends SpringBootServletInitializer {
/**
* main method
*
* @param args
*/
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
@Override
protected SpringApplicationBuilder configure(SpringApplicationBuilder application) {
return application.sources(Application.class);
}
}
spring.jpa.open-in-view=falseto your properties, if you don't anEntitymanagerandConnectionare opened very early before you have time to set the correct value for theAbstractRoutingDataSource. also remove the loading of theapplication.propertiesfrom your xml as that should be handled by Spring Boot already. Also yourspring.jpaproperties are of no use as you are configuring JPA yourself.