I have a project split into 3 modules (so far) - core (Model 1), user-management (model 2) and web (View and Controller). My project structure (simplified to only relevant classes for the sake of getting to the point) is as follows:
Project
|-- core
| |-- src.main.java.com.romco.example
| | |-- config.CoreDataSourceConfiguration
| | |-- persistence.daoimpl.SomeCoreDaoImpl
|-- user-management
| |-- src.main.kotlin.com.romco.example
| | |-- config.UserManagementConfiguration
| | |-- persistence.daoimpl.SomeUserManagementDaoImpl
|-- web
| // not important right now
My classes are as follows (while debugging my previous issue, I had to move some value initialization directly to code instead of using application.properties, as noted by the TODO, so please ignore it for the sake of the problem at hand)
- CoreDataSourceConfiguration:
@Configuration
public class CoreDataSourceConfiguration {
@Bean
@Primary
public DataSourceProperties coreDataSourceProperties() {
return new DataSourceProperties();
}
//TODO values should be retrieved from application.properties
@Bean(name = "coreDataSource")
@Primary
public DataSource coreDataSource() {
DataSourceBuilder dataSourceBuilder = DataSourceBuilder.create();
dataSourceBuilder.driverClassName("com.mysql.cj.jdbc.Driver");
dataSourceBuilder.url("...");
dataSourceBuilder.username("...");
dataSourceBuilder.password("...");
return dataSourceBuilder.build();
}
@Bean(name = "coreTransactionManager")
@Autowired
DataSourceTransactionManager coreTransactionManager(@Qualifier("coreDataSource") DataSource dataSource) {
return new DataSourceTransactionManager(dataSource);
}
}
- SomeCoreDaoImpl:
@Repository
public class SomeCoreDaoImpl implements SomeCoreDao {
// some constants here
private NamedParameterJdbcTemplate namedParameterJdbcTemplate;
@Autowired
@Override
public void setDataSource(DataSource dataSource) {
namedParameterJdbcTemplate = NamedParameterJdbcTemplateHolder.get(dataSource);
}
// DB code here - create, update, etc.
}
- UserManagementConfiguration:
@Configuration
open class UserManagementDataSourceConfiguration {
@Bean
open fun userManagementDataSourceProperties(): DataSourceProperties {
return DataSourceProperties()
}
@Bean(name = ["userManagementDataSource"])
open fun userManagementDataSource(): DataSource {
val dataSourceBuilder = DataSourceBuilder.create()
dataSourceBuilder
.driverClassName("com.mysql.cj.jdbc.Driver")
.url("...")
.username("...")
.password("...")
return dataSourceBuilder.build()
}
@Bean(name = ["userManagementTransactionManager"])
@Autowired
open fun userManagementTransactionManager(@Qualifier("userManagementDataSource") dataSource: DataSource): DataSourceTransactionManager {
return DataSourceTransactionManager(dataSource)
}
}
- SomeUserManagementDaoImpl:
@Repository
open class SomeUserManagementDaoImpl: SomeUserManagementDao{
// constants are here
private lateinit var namedParameterJdbcTemplate: NamedParameterJdbcTemplate
@Autowired
fun setDataSource(@Qualifier("userManagementDataSource") dataSource: DataSource) {
namedParameterJdbcTemplate = NamedParameterJdbcTemplate(dataSource)
}
// DB code here
}
As you can see, the way I made it work is by specifying which bean to use in the autowired setDataSource method inside my SomeUserManagementDaoImpl class.
I would obviously prefer to avoid having to do this in every daoImpl class, and while I can think of extracting this to a single class, it doesn't seem like that's the "spring" intended solution.
Now (again, obviously) - The data sources are module-specific, and initially, I even assumed spring would somehow figure it out under the hood and, instead of using the @Primary datasource, would use the one defined in a given module (unless that module had none, in which case I assumed it would fall back to the @Primary one).
However, that was not the case, and I'm left wondering if there is some way to tell spring to use a given data source configuration across that entire module...
I've been looking at many similiar threads and guides that deal with multi-datasource projects, but I actually never found the answer. In fact, the guides which I consulted when I was implementing my multi-datasource solution never mentioned this at all (unless I missed it), eg.
https://www.baeldung.com/spring-boot-failed-to-configure-data-source
https://www.baeldung.com/spring-data-jpa-multiple-databases
It is also entirely possible that I'm doing something else terribly wrong, and that is the root cause, in which case, please, also help me out.
So, in case anyone stumbles upon the same problem, here's how I solved it so far. I might come up with a more elegant solution in the future, or more likely, find an issue with this one, but for now it seems to be working (haven't done much testing yet though):
In the user-management module (the one NOT using the @Primary datasource), I created the following abstract class, extracting the dataSource injection (with the qualifier specifying the datasource) to one place:
Each of my user-management DaoImpl classes then extends this class, and therefore implicitly implements the setDataSource() method of my GenericDao interface.
For completeness, the user-management module now looks like this (I included some previously omitted interfaces, but still left the "example" naming and omitted some specific utility code):