两个数据库连接的通用 spring 个数据存储库
Generic spring data repositories for two database connections
我有一个应用程序需要连接到两个数据库,但是两个数据库的实体重复。
我设法让应用程序连接并在两个数据库中保留实体,但我必须创建两个存储库,每个存储库一个,但我发现这种方法不正确。
有一种更好的管理方法,我创建一个存储库并根据标志为其定义它应该使用的连接。
第一次数据库配置
@Configuration
@EnableTransactionManagement
@EnableJpaRepositories(
basePackages = ["xxx.xxx.domains.portadapter.repository.firstRepository"],
entityManagerFactoryRef = "firstEntityManegerFactory",
transactionManagerRef = "firstTransactionManager"
)
class FirstDatabaseConfiguration {
@Bean(name = ["firstDatasource"])
@ConfigurationProperties(prefix = "first.datasource")
fun dataSource(): DataSource? {
return DataSourceBuilder.create().build()
}
@Bean(name = ["firstEntityManegerFactory"])
fun firstEntityManegerFactory(
builder: EntityManagerFactoryBuilder, @Qualifier("firstDatasource") dataSource: DataSource?
): LocalContainerEntityManagerFactoryBean? {
return builder.dataSource(dataSource).packages("xxx").persistenceUnit("xxx")
.build()
}
@Bean(name = ["firstTransactionManager"])
fun firstTransactionManager(
@Qualifier("firstEntityManegerFactory") barEntityManagerFactory: EntityManagerFactory?
): PlatformTransactionManager? {
return JpaTransactionManager(barEntityManagerFactory!!)
}
}
第二个数据库配置
@Configuration
@EnableTransactionManagement
@EnableJpaRepositories(
basePackages = ["xxx.xxx.domains.portadapter.repository.secondRepository"],
entityManagerFactoryRef = "secondEntityManegerFactory",
transactionManagerRef = "secondTransactionManeger"
)
class secondDatabaseConfiguration {
@Primary
@Bean(name = ["secondDatasource"])
@ConfigurationProperties(prefix = "spring.datasource")
fun dataSource(): DataSource? {
return DataSourceBuilder.create().build()
}
@Primary
@Bean(name = ["secondEntityManegerFactory"])
fun secondEntityManegerFactory(
builder: EntityManagerFactoryBuilder, @Qualifier("secondDatasource") dataSource: DataSource?
): LocalContainerEntityManagerFactoryBean? {
return builder.dataSource(dataSource).packages("xxx").persistenceUnit("xxx")
.build()
}
@Primary
@Bean(name = ["secondTransactionManager"])
fun secondTransactionManager(
@Qualifier("secondEntityManegerFactory") barEntityManagerFactory: EntityManagerFactory?
): PlatformTransactionManager? {
return JpaTransactionManager(barEntityManagerFactory!!)
}
}
将实体保存在第一个数据库中的年龄组存储库
interface FirstAgeGroupsRepository : JpaRepository<AgeGroups, Int> {
fun findBySispacId(sispacId : String): Optional<AgeGroups>
}
将实体保存在第二个数据库中的年龄组存储库
interface SecondAgeGroupsRepository : JpaRepository<AgeGroups, Int> {
fun findBySispacId(sispacId : String): Optional<AgeGroups>
}
例如,在这个控制器中,我必须实例化每个存储库,并通过条件定义我应该使用哪个存储库
@Service
class AgeGroupsController(
@Autowired val scoobyAgeGroupRepository: ScoobyAgeGroupsRepository,
@Autowired val fisiaAgeGroupRepository: FisiaAgeGroupsRepository
) {
fun saveAgeGroup(body: AgeGroupDto): ResponseDto {
return try {
val ageGroupFound: Optional<AgeGroups> = fisiaAgeGroupRepository.findBySispacId(body.sispacId)
if (!ageGroupFound.isEmpty) {
return ResponseDto(HttpStatus.FOUND, "Faixa etária ja existe, persistencia não realizada.")
}
val formatter = SimpleDateFormat("yyyy-MM-dd")
val ageGroup = AgeGroups(
body.ageGroupName, formatter.parse(body.createdAt), body.sispacId,
formatter.parse(body.updatedAt)
)
body.ageGroupIndexDescription?.let { ageGroup.setAgeGroupIndexDescription(it) }
fisiaAgeGroupRepository.save(ageGroup)
ResponseDto(HttpStatus.OK, "Faixa etária criada com sucesso!")
} catch (erro: Exception) {
print(erro.message)
ResponseDto(HttpStatus.BAD_REQUEST, erro.message)
}
}
}
如果有人知道任何可以帮助我改进此代码的教程或相关内容,因为如果有一天我想添加另一个数据库,我将不得不复制存储库,然后在我的每个控制器中再添加一个条件。
我乐于接受建议
将此添加为答案是因为我没有足够的声誉来发表评论。以下是改进此代码的几种方法:
- Spring 使用 Controller/Service/Repository 模式。为了让这段代码更readable/maintainable,把controller逻辑切割成单独的
@Service
class。将控制器用于严格的前端逻辑。作为一般规则,如果您需要访问代码中的存储库,它很可能属于服务。
- 吹毛求疵:
@Autowired
依赖项可以是私有的
编辑:删除了微服务参考
如果您创建一个包含所有额外方法的存储库接口,然后在专用包中扩展它,以便不同的 JPA 配置可以从那里获取它们,那么您可以稍微减少代码。
例如,在 JPA 不搜索存储库的包中:
interface AgeGroupsRepository : JpaRepository<AgeGroups, Int> {
fun findBySispacId(sispacId : String): Optional<AgeGroups>
}
在第一个数据库存储库的包中:
interface FirstAgeGroupsRepository : AgeGroupsRepository {
}
在第二个数据库存储库的包中:
interface SecondAgeGroupsRepository : AgeGroupsRepository {
}
但是,我认为您的描述中遗漏了一些重要的内容。实际上,您不仅需要单独的存储库,还需要单独的服务。这是因为您需要根据使用的存储库使用 @Transactional("firstTransactionManager")
或 @Transactional("secondTransactionManager")
注释服务方法。因此,您要么放弃 Spring 事务管理并自行推出,要么也复制该服务。
如您所见,Spring 引导架构针对单一数据源用例进行了优化。由于您的用例不同,使用比 Spring Data JPA 级别更低的技术可能会更好:jOOQ、Spring JdbcTemplate
或普通 JDBC。您肯定会有更多的可能性来消除代码重复。
原来我需要的是使用多租户让我的应用程序根据标志连接到两个不同的数据库。
我使用本教程解决了我的问题:
https://medium.com/swlh/multi-tenancy-implementation-using-spring-boot-hibernate-6a8e3ecb251a
正如@Alexey Veleshko 所说spring 引导并非设计用于处理 N 个数据库,因此需要更“原始”的方法
我有一个应用程序需要连接到两个数据库,但是两个数据库的实体重复。
我设法让应用程序连接并在两个数据库中保留实体,但我必须创建两个存储库,每个存储库一个,但我发现这种方法不正确。
有一种更好的管理方法,我创建一个存储库并根据标志为其定义它应该使用的连接。
第一次数据库配置
@Configuration
@EnableTransactionManagement
@EnableJpaRepositories(
basePackages = ["xxx.xxx.domains.portadapter.repository.firstRepository"],
entityManagerFactoryRef = "firstEntityManegerFactory",
transactionManagerRef = "firstTransactionManager"
)
class FirstDatabaseConfiguration {
@Bean(name = ["firstDatasource"])
@ConfigurationProperties(prefix = "first.datasource")
fun dataSource(): DataSource? {
return DataSourceBuilder.create().build()
}
@Bean(name = ["firstEntityManegerFactory"])
fun firstEntityManegerFactory(
builder: EntityManagerFactoryBuilder, @Qualifier("firstDatasource") dataSource: DataSource?
): LocalContainerEntityManagerFactoryBean? {
return builder.dataSource(dataSource).packages("xxx").persistenceUnit("xxx")
.build()
}
@Bean(name = ["firstTransactionManager"])
fun firstTransactionManager(
@Qualifier("firstEntityManegerFactory") barEntityManagerFactory: EntityManagerFactory?
): PlatformTransactionManager? {
return JpaTransactionManager(barEntityManagerFactory!!)
}
}
第二个数据库配置
@Configuration
@EnableTransactionManagement
@EnableJpaRepositories(
basePackages = ["xxx.xxx.domains.portadapter.repository.secondRepository"],
entityManagerFactoryRef = "secondEntityManegerFactory",
transactionManagerRef = "secondTransactionManeger"
)
class secondDatabaseConfiguration {
@Primary
@Bean(name = ["secondDatasource"])
@ConfigurationProperties(prefix = "spring.datasource")
fun dataSource(): DataSource? {
return DataSourceBuilder.create().build()
}
@Primary
@Bean(name = ["secondEntityManegerFactory"])
fun secondEntityManegerFactory(
builder: EntityManagerFactoryBuilder, @Qualifier("secondDatasource") dataSource: DataSource?
): LocalContainerEntityManagerFactoryBean? {
return builder.dataSource(dataSource).packages("xxx").persistenceUnit("xxx")
.build()
}
@Primary
@Bean(name = ["secondTransactionManager"])
fun secondTransactionManager(
@Qualifier("secondEntityManegerFactory") barEntityManagerFactory: EntityManagerFactory?
): PlatformTransactionManager? {
return JpaTransactionManager(barEntityManagerFactory!!)
}
}
将实体保存在第一个数据库中的年龄组存储库
interface FirstAgeGroupsRepository : JpaRepository<AgeGroups, Int> {
fun findBySispacId(sispacId : String): Optional<AgeGroups>
}
将实体保存在第二个数据库中的年龄组存储库
interface SecondAgeGroupsRepository : JpaRepository<AgeGroups, Int> {
fun findBySispacId(sispacId : String): Optional<AgeGroups>
}
例如,在这个控制器中,我必须实例化每个存储库,并通过条件定义我应该使用哪个存储库
@Service
class AgeGroupsController(
@Autowired val scoobyAgeGroupRepository: ScoobyAgeGroupsRepository,
@Autowired val fisiaAgeGroupRepository: FisiaAgeGroupsRepository
) {
fun saveAgeGroup(body: AgeGroupDto): ResponseDto {
return try {
val ageGroupFound: Optional<AgeGroups> = fisiaAgeGroupRepository.findBySispacId(body.sispacId)
if (!ageGroupFound.isEmpty) {
return ResponseDto(HttpStatus.FOUND, "Faixa etária ja existe, persistencia não realizada.")
}
val formatter = SimpleDateFormat("yyyy-MM-dd")
val ageGroup = AgeGroups(
body.ageGroupName, formatter.parse(body.createdAt), body.sispacId,
formatter.parse(body.updatedAt)
)
body.ageGroupIndexDescription?.let { ageGroup.setAgeGroupIndexDescription(it) }
fisiaAgeGroupRepository.save(ageGroup)
ResponseDto(HttpStatus.OK, "Faixa etária criada com sucesso!")
} catch (erro: Exception) {
print(erro.message)
ResponseDto(HttpStatus.BAD_REQUEST, erro.message)
}
}
}
如果有人知道任何可以帮助我改进此代码的教程或相关内容,因为如果有一天我想添加另一个数据库,我将不得不复制存储库,然后在我的每个控制器中再添加一个条件。
我乐于接受建议
将此添加为答案是因为我没有足够的声誉来发表评论。以下是改进此代码的几种方法:
- Spring 使用 Controller/Service/Repository 模式。为了让这段代码更readable/maintainable,把controller逻辑切割成单独的
@Service
class。将控制器用于严格的前端逻辑。作为一般规则,如果您需要访问代码中的存储库,它很可能属于服务。 - 吹毛求疵:
@Autowired
依赖项可以是私有的
编辑:删除了微服务参考
如果您创建一个包含所有额外方法的存储库接口,然后在专用包中扩展它,以便不同的 JPA 配置可以从那里获取它们,那么您可以稍微减少代码。
例如,在 JPA 不搜索存储库的包中:
interface AgeGroupsRepository : JpaRepository<AgeGroups, Int> {
fun findBySispacId(sispacId : String): Optional<AgeGroups>
}
在第一个数据库存储库的包中:
interface FirstAgeGroupsRepository : AgeGroupsRepository {
}
在第二个数据库存储库的包中:
interface SecondAgeGroupsRepository : AgeGroupsRepository {
}
但是,我认为您的描述中遗漏了一些重要的内容。实际上,您不仅需要单独的存储库,还需要单独的服务。这是因为您需要根据使用的存储库使用 @Transactional("firstTransactionManager")
或 @Transactional("secondTransactionManager")
注释服务方法。因此,您要么放弃 Spring 事务管理并自行推出,要么也复制该服务。
如您所见,Spring 引导架构针对单一数据源用例进行了优化。由于您的用例不同,使用比 Spring Data JPA 级别更低的技术可能会更好:jOOQ、Spring JdbcTemplate
或普通 JDBC。您肯定会有更多的可能性来消除代码重复。
原来我需要的是使用多租户让我的应用程序根据标志连接到两个不同的数据库。
我使用本教程解决了我的问题: https://medium.com/swlh/multi-tenancy-implementation-using-spring-boot-hibernate-6a8e3ecb251a
正如@Alexey Veleshko 所说spring 引导并非设计用于处理 N 个数据库,因此需要更“原始”的方法