
本文深入探讨了spring boot应用中配置多数据源时遇到的实体保存失败问题,特别是`transactionrequiredexception`。通过分析默认事务管理的局限性,文章详细阐述了如何为每个数据源配置独立的事务管理器,并演示了在业务逻辑层通过`@transactional`注解明确指定事务管理器来解决此问题的实践方法,确保多数据源操作的事务一致性。
在企业级应用中,为了满足不同的业务需求或实现数据隔离,Spring Boot应用常常需要连接到多个数据库。配置多数据源涉及定义各自的数据源、实体管理器工厂和事务管理器。
主数据源通常通过@Primary注解标识,作为默认的数据源。其配置包括数据源连接信息、实体扫描路径以及事务管理器。
@Configuration
@EnableJpaRepositories(
basePackages = ["org.my.app.repository.primary"], // 主数据源Repository包路径
entityManagerFactoryRef = "primaryDbEntityManager",
transactionManagerRef = "primaryDbTransactionManager"
)
class PrimaryConfig {
@Primary
@Bean
@ConfigurationProperties("spring.primary-db") // 绑定application.properties中的配置
fun primaryDbDataSource(): DataSource {
return DataSourceBuilder.create()
.driverClassName("org.postgresql.Driver")
.url("jdbc:postgresql://127.0.0.1:5432/primary_db")
.username("postgres")
.password("********")
.build()
}
@Primary
@Bean
@ConfigurationProperties("hibernate.primary-db")
fun primaryDbEntityManager(): LocalContainerEntityManagerFactoryBean {
val em = LocalContainerEntityManagerFactoryBean()
em.dataSource = primaryDbDataSource()
em.setPackagesToScan("org.my.app.entity.primary") // 主数据源Entity包路径
val vendorAdapter = HibernateJpaVendorAdapter()
em.jpaVendorAdapter = vendorAdapter
val properties = HashMap<String, String?>()
properties["hibernate.dialect"] = "org.hibernate.dialect.PostgreSQLDialect"
properties["hibernate.temp.use_jdbc_metadata_defaults"] = "false"
em.setJpaPropertyMap(properties)
return em
}
@Primary
@Bean(name = ["primaryDbTransactionManager"])
fun primaryDbTransactionManager(): PlatformTransactionManager? {
val transactionManager = JpaTransactionManager()
transactionManager.entityManagerFactory = primaryDbEntityManager().getObject()
return transactionManager
}
}副数据源的配置与主数据源类似,但不需要@Primary注解,且所有Bean的名称(包括数据源、实体管理器工厂和事务管理器)都应与主数据源区分开。
@Configuration
@EnableJpaRepositories(
basePackages = ["org.my.app.repository.secondary"], // 副数据源Repository包路径
entityManagerFactoryRef = "secondaryDbEntityManager",
transactionManagerRef = "secondaryDbTransactionManager"
)
class SecondaryConfig {
@Bean
@ConfigurationProperties("spring.secondary-db")
fun secondaryDbDataSource(): DataSource {
return DataSourceBuilder.create()
.driverClassName("org.postgresql.Driver")
.url("jdbc:postgresql://127.0.0.1:5432/secondary_db")
.username("postgres")
.password("********")
.build()
}
@Bean
@ConfigurationProperties("hibernate.secondary-db")
fun secondaryDbEntityManager(): LocalContainerEntityManagerFactoryBean {
val em = LocalContainerEntityManagerFactoryBean()
em.dataSource = secondaryDbDataSource()
em.setPackagesToScan("org.my.app.entity.secondary") // 副数据源Entity包路径
val vendorAdapter = HibernateJpaVendorAdapter()
em.jpaVendorAdapter = vendorAdapter
val properties = HashMap<String, String?>()
properties["hibernate.dialect"] = "org.hibernate.dialect.PostgreSQLDialect"
properties["hibernate.temp.use_jdbc_metadata_defaults"] = "false"
em.setJpaPropertyMap(properties)
return em
}
@Bean(name = ["secondaryDbTransactionManager"]) // 注意Bean名称与主数据源区分
fun secondaryDbTransactionManager(): PlatformTransactionManager? { // 方法名也应区分
val transactionManager = JpaTransactionManager()
transactionManager.entityManagerFactory = secondaryDbEntityManager().getObject()
return transactionManager
}
}每个数据源拥有独立的实体类和仓库接口。实体类使用@Entity和@Table注解,仓库通过EntityManager进行数据操作。
// 主数据源实体
package org.my.app.entity.primary
@Entity
@Table(name="table1", schema="public")
data class PrimaryEntity(
@Id
val id: Long,
@Column(name="data_value")
val dataValue: String?
)
// 副数据源实体
package org.my.app.entity.secondary
@Entity
@Table(name="table1", schema="public")
data class SecondaryEntity(
@Id
val id: Long,
@Column(name="data_value")
val dataValue: String?
)
// 主数据源仓库
package org.my.app.repository.primary
import javax.persistence.EntityManager
import org.springframework.beans.factory.annotation.Qualifier
import org.springframework.stereotype.Service
import org.springframework.transaction.annotation.Transactional
@Service
class PrimaryDbRepository(
@Qualifier("primaryDbEntityManager") // 注入主数据源的EntityManager
private val entityManager: EntityManager
) {
@Transactional // 默认使用主数据源的事务管理器
fun batchInsert(entityList: List<PrimaryEntity>) {
for (entity in entityList) {
entityManager.persist(entity)
}
entityManager.clear() // 触发flush
}
}
// 副数据源仓库 (存在事务问题前)
package org.my.app.repository.secondary
import javax.persistence.EntityManager
import org.springframework.beans.factory.annotation.Qualifier
import org.springframework.stereotype.Service
import org.springframework.transaction.annotation.Transactional
@Service
class SecondaryDbRepository(
@Qualifier("secondaryDbEntityManager") // 注入副数据源的EntityManager
private val entityManager: EntityManager
) {
@Transactional // 此时可能存在事务问题
fun batchInsert(entityList: List<SecondaryEntity>) {
for (entity in entityList) {
entityManager.persist(entity)
}
entityManager.clear() // 触发flush
}
}当在Service层同时操作多个数据源时,可能会遇到javax.persistence.TransactionRequiredException: no transaction is in progress异常。这通常发生在尝试对一个EntityManager执行持久化操作(如persist、merge、remove)时,但当前线程没有激活的事务。
考虑以下Service层代码:
@Service
class Saver (
private val primaryRepository: PrimaryDbRepository,
private val secondaryRepository: SecondaryDbRepository
) {
fun saveMyData() {
val data = listOf(PrimaryEntity(1, "A"), PrimaryEntity(2, "B"))
primaryRepository.batchInsert(data) // 正常工作
val data2 = listOf(SecondaryEntity(1, "A"), SecondaryEntity(2, "B"))
secondaryRepository.batchInsert(data2) // 抛出 TransactionRequiredException
}
}尽管SecondaryDbRepository.batchInsert方法上标注了@Transactional,但在saveMyData方法中调用时,Spring的事务管理机制可能无法正确识别并激活副数据源的事务。原因在于:
TransactionRequiredException正是明确指出,在尝试执行需要事务的操作时,没有找到一个活跃的事务上下文。
解决此问题的核心在于,对于每个数据源的事务操作,显式地告知Spring应该使用哪个事务管理器。这可以通过在@Transactional注解中指定事务管理器的Bean名称来实现。
修改副数据源的Repository方法如下:
package org.my.app.repository.secondary
import javax.persistence.EntityManager
import org.springframework.beans.factory.annotation.Qualifier
import org.springframework.stereotype.Service
import org.springframework.transaction.annotation.Transactional
@Service
class SecondaryDbRepository(
@Qualifier("secondaryDbEntityManager")
private val entityManager: EntityManager
) {
@Transactional("secondaryDbTransactionManager") // 明确指定使用副数据源的事务管理器
fun batchInsert(entityList: List<SecondaryEntity>) {
for (entity in entityList) {
entityManager.persist(entity)
}
entityManager.clear()
}
}通过@Transactional("secondaryDbTransactionManager")注解,Spring AOP在拦截batchInsert方法时,会确保使用名为secondaryDbTransactionManager的事务管理器来开启、提交或回滚事务。这样,secondaryDbEntityManager就能在其正确的事务上下文中执行持久化操作,从而避免TransactionRequiredException。
对于Service层,如果saveMyData方法需要在一个事务中同时操作两个数据源,可以考虑在其上添加一个更高层次的事务,并根据业务逻辑处理事务传播行为,或者将每个数据源的操作封装在各自的事务中。
@Service
class Saver (
private val primaryRepository: PrimaryDbRepository,
private val secondaryRepository: SecondaryDbRepository
) {
// 如果需要两个操作都在一个事务中,且事务管理器不同,需要更复杂的编程式事务或设计
// 但对于本例,Repository层已各自处理事务,Service层无需额外事务注解
fun saveMyData() {
val data = listOf(PrimaryEntity(1, "A"), PrimaryEntity(2, "B"))
primaryRepository.batchInsert(data) // 此时会使用 primaryDbTransactionManager
val data2 = listOf(SecondaryEntity(1, "A"), SecondaryEntity(2, "B"))
secondaryRepository.batchInsert(data2) // 此时会使用 secondaryDbTransactionManager
}
}在此场景下,saveMyData方法本身不需要@Transactional注解,因为其内部调用的Repository方法已经各自管理了事务。如果saveMyData方法需要确保两个batchInsert操作要么都成功要么都失败(即一个分布式事务),则需要更复杂的解决方案,例如使用XA事务或Saga模式。但对于大多数场景,各自数据源的独立事务已足够。
在Spring Boot多数据源应用中,正确配置和管理事务是确保数据一致性和应用稳定性的关键。当遇到TransactionRequiredException时,通常意味着Spring的事务管理机制未能为当前操作提供正确的事务上下文。通过在@Transactional注解中明确指定事务管理器的Bean名称,可以有效地解决此问题,确保每个数据源的操作都在其专属的事务中执行。遵循良好的命名规范、包扫描隔离以及对事务传播行为的理解,将有助于构建更加健壮和可维护的多数据源应用。
以上就是Spring Boot多数据源事务管理:解决实体保存异常的实践指南的详细内容,更多请关注php中文网其它相关文章!
每个人都需要一台速度更快、更稳定的 PC。随着时间的推移,垃圾文件、旧注册表数据和不必要的后台进程会占用资源并降低性能。幸运的是,许多工具可以让 Windows 保持平稳运行。
Copyright 2014-2025 https://www.php.cn/ All Rights Reserved | php.cn | 湘ICP备2023035733号