在多模块多数据源项目中指示将哪个数据源注入我的 DAO 的正确方法是什么?
What is the proper way to indicate which data source to inject into my DAOs in a multi-module multi-datasource project?
我有一个项目分为 3 个模块(到目前为止)- 核心(模型 1)、用户管理(模型 2)和 Web(视图和控制器)。我的项目结构(为了切中要点,简化为仅相关的 classes)如下:
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
我的class如下(在调试我之前的问题时,我不得不将一些值初始化直接移动到代码而不是使用application.properties,正如TODO所指出的,所以请忽略为了手头的问题)
- 核心数据源配置:
@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.
}
- 用户管理配置:
@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
}
如您所见,我实现它的方法是在我的 SomeUserManagementDaoImpl class.
中指定要在自动装配的 setDataSource 方法中使用哪个 bean
我显然更愿意避免在每个 daoImpl class 中都必须这样做,虽然我可以考虑将其提取到单个 class,但这似乎不是“ spring" 预期的解决方案。
现在(再次,显然)- 数据源是特定于模块的,最初,我什至假设 spring 会以某种方式在幕后解决它,而不是使用 @Primary 数据源,会使用给定模块中定义的模块(除非该模块有 none,在这种情况下我假设它会退回到 @Primary 模块)。
然而,事实并非如此,我想知道是否有某种方法可以告诉 spring 在整个模块中使用给定的数据源配置...
我一直在寻找许多处理多数据源项目的类似线程和指南,但实际上我从未找到答案。事实上,我在实施多数据源解决方案时参考的指南根本没有提到这一点(除非我错过了),例如
https://www.baeldung.com/spring-boot-failed-to-configure-data-source
https://www.baeldung.com/spring-data-jpa-multiple-databases
也完全有可能是我做的其他事情大错特错,这是根本原因,这种情况还请大家帮帮我。
因此,如果有人遇到同样的问题,请看我目前的解决方法。将来我可能会想出一个更优雅的解决方案,或者更可能的是,发现这个问题,但现在它似乎在工作(虽然还没有做太多测试):
在 user-management 模块(不使用 @Primary 数据源的模块)中,我创建了以下摘要 class,将数据源注入(使用指定数据源的限定符)提取到一个地方:
abstract class WithDataSource {
protected lateinit var namedParameterJdbcTemplate: NamedParameterJdbcTemplate
@Autowired
fun setDataSource(@Qualifier(USER_MANAGEMENT_DATA_SOURCE_BEAN_NAME) dataSource: DataSource) {
namedParameterJdbcTemplate = NamedParameterJdbcTemplate(dataSource)
}
}
我的每个 user-management DaoImpl classes 然后扩展这个 class,因此隐式实现我的 GenericDao 接口的 setDataSource() 方法。
为了完整起见,user-management 模块现在看起来像这样(我包含了一些以前省略的接口,但仍然保留了“示例”命名并省略了一些特定的实用程序代码):
Project
|-- core
| |-- src.main.java.com.romco.example
| | |-- config.CoreDataSourceConfiguration
| | |-- persistence.daoimpl.SomeCoreDaoImpl
|-- user-management
| |-- src.main.kotlin.com.romco.example
| | |-- config.UserManagementConfiguration
| | |-- persistence.dao.GenericDao
| | |-- persistence.daoimpl.SomeUserManagementDaoImpl
| | |-- persistence.util.DaoUtil.kt
|-- web
| // not important right now
- UserManagementConfiguration(添加 bean 名称作为常量 USER_MANAGEMENT_DATA_SOURCE_BEAN_NAME):
@Configuration
open class UserManagementDataSourceConfiguration {
companion object {
const val USER_MANAGEMENT_DATA_SOURCE_BEAN_NAME = "userManagementDataSource"
}
@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)
}
}
- GenericDao(在原始问题中没有提到,因为它不太相关,包括解决方案的完整性):
interface GenericDao<T> {
fun setDataSource(dataSource: DataSource)
// retrieves all
fun retrieveAll(): Collection<T>
// creates and returns id of the newly created record. In case of failure, returns -1.
fun create(t: T): Long
// updates by id, returns true if success.
fun update(t: T): Boolean
// deletes by id, returns true if success.
fun delete(t: T): Boolean
// performs cleanup, for example, might delete all test records (id < 0)
fun cleanup()
}
- SomeUserManagementDao(在原始问题中没有提到,因为它不太相关,包括解决方案的完整性):
interface SomeUserManagementDao: GenericDao<SomeUserManagementClass> {
fun retrieveBySpecificValue(specificValue: String): SomeUserManagementClass?
}
- SomeUserManagementDaoImpl(如评论中所述更新):
@Repository
open class SomeUserManagementDaoImpl: SomeUserManagementDao, WithDataSource() {
// constants are here
// namedParameterJdbcTemplate and setDataSource() are now inherited from the parent class - WithDataSource
// DB code here
}
- DaoUtil.kt(包含最初提到的摘要 class 以及其他一些,在本例中省略的实用程序):
abstract class WithDataSource {
protected lateinit var namedParameterJdbcTemplate: NamedParameterJdbcTemplate
@Autowired
fun setDataSource(@Qualifier(USER_MANAGEMENT_DATA_SOURCE_BEAN_NAME) dataSource: DataSource) {
namedParameterJdbcTemplate = NamedParameterJdbcTemplate(dataSource)
}
}
我有一个项目分为 3 个模块(到目前为止)- 核心(模型 1)、用户管理(模型 2)和 Web(视图和控制器)。我的项目结构(为了切中要点,简化为仅相关的 classes)如下:
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
我的class如下(在调试我之前的问题时,我不得不将一些值初始化直接移动到代码而不是使用application.properties,正如TODO所指出的,所以请忽略为了手头的问题)
- 核心数据源配置:
@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.
}
- 用户管理配置:
@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
}
如您所见,我实现它的方法是在我的 SomeUserManagementDaoImpl class.
中指定要在自动装配的 setDataSource 方法中使用哪个 bean我显然更愿意避免在每个 daoImpl class 中都必须这样做,虽然我可以考虑将其提取到单个 class,但这似乎不是“ spring" 预期的解决方案。
现在(再次,显然)- 数据源是特定于模块的,最初,我什至假设 spring 会以某种方式在幕后解决它,而不是使用 @Primary 数据源,会使用给定模块中定义的模块(除非该模块有 none,在这种情况下我假设它会退回到 @Primary 模块)。
然而,事实并非如此,我想知道是否有某种方法可以告诉 spring 在整个模块中使用给定的数据源配置...
我一直在寻找许多处理多数据源项目的类似线程和指南,但实际上我从未找到答案。事实上,我在实施多数据源解决方案时参考的指南根本没有提到这一点(除非我错过了),例如
https://www.baeldung.com/spring-boot-failed-to-configure-data-source
https://www.baeldung.com/spring-data-jpa-multiple-databases
也完全有可能是我做的其他事情大错特错,这是根本原因,这种情况还请大家帮帮我。
因此,如果有人遇到同样的问题,请看我目前的解决方法。将来我可能会想出一个更优雅的解决方案,或者更可能的是,发现这个问题,但现在它似乎在工作(虽然还没有做太多测试):
在 user-management 模块(不使用 @Primary 数据源的模块)中,我创建了以下摘要 class,将数据源注入(使用指定数据源的限定符)提取到一个地方:
abstract class WithDataSource {
protected lateinit var namedParameterJdbcTemplate: NamedParameterJdbcTemplate
@Autowired
fun setDataSource(@Qualifier(USER_MANAGEMENT_DATA_SOURCE_BEAN_NAME) dataSource: DataSource) {
namedParameterJdbcTemplate = NamedParameterJdbcTemplate(dataSource)
}
}
我的每个 user-management DaoImpl classes 然后扩展这个 class,因此隐式实现我的 GenericDao 接口的 setDataSource() 方法。
为了完整起见,user-management 模块现在看起来像这样(我包含了一些以前省略的接口,但仍然保留了“示例”命名并省略了一些特定的实用程序代码):
Project
|-- core
| |-- src.main.java.com.romco.example
| | |-- config.CoreDataSourceConfiguration
| | |-- persistence.daoimpl.SomeCoreDaoImpl
|-- user-management
| |-- src.main.kotlin.com.romco.example
| | |-- config.UserManagementConfiguration
| | |-- persistence.dao.GenericDao
| | |-- persistence.daoimpl.SomeUserManagementDaoImpl
| | |-- persistence.util.DaoUtil.kt
|-- web
| // not important right now
- UserManagementConfiguration(添加 bean 名称作为常量 USER_MANAGEMENT_DATA_SOURCE_BEAN_NAME):
@Configuration
open class UserManagementDataSourceConfiguration {
companion object {
const val USER_MANAGEMENT_DATA_SOURCE_BEAN_NAME = "userManagementDataSource"
}
@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)
}
}
- GenericDao(在原始问题中没有提到,因为它不太相关,包括解决方案的完整性):
interface GenericDao<T> {
fun setDataSource(dataSource: DataSource)
// retrieves all
fun retrieveAll(): Collection<T>
// creates and returns id of the newly created record. In case of failure, returns -1.
fun create(t: T): Long
// updates by id, returns true if success.
fun update(t: T): Boolean
// deletes by id, returns true if success.
fun delete(t: T): Boolean
// performs cleanup, for example, might delete all test records (id < 0)
fun cleanup()
}
- SomeUserManagementDao(在原始问题中没有提到,因为它不太相关,包括解决方案的完整性):
interface SomeUserManagementDao: GenericDao<SomeUserManagementClass> {
fun retrieveBySpecificValue(specificValue: String): SomeUserManagementClass?
}
- SomeUserManagementDaoImpl(如评论中所述更新):
@Repository
open class SomeUserManagementDaoImpl: SomeUserManagementDao, WithDataSource() {
// constants are here
// namedParameterJdbcTemplate and setDataSource() are now inherited from the parent class - WithDataSource
// DB code here
}
- DaoUtil.kt(包含最初提到的摘要 class 以及其他一些,在本例中省略的实用程序):
abstract class WithDataSource {
protected lateinit var namedParameterJdbcTemplate: NamedParameterJdbcTemplate
@Autowired
fun setDataSource(@Qualifier(USER_MANAGEMENT_DATA_SOURCE_BEAN_NAME) dataSource: DataSource) {
namedParameterJdbcTemplate = NamedParameterJdbcTemplate(dataSource)
}
}