Spring Data MongoDB事务不生效问题排查(Testcontainers环境)
问题:Spring Data MongoDB事务无法生效
在基础Spring Data MongoDB应用中,事务无法生效。已查阅相关文章及同类问题但未解决。测试使用Testcontainers启动带副本集的MongoDB(理论上支持事务),非事务代码配置可正常运行。
领域服务代码
public class EntityDomainService { private final EntityARepository repository; private final EntityBRepository entityBRepository; EntityDomainService(EntityARepository repository, EntityBRepository entityBRepository) { this.repository = repository; this.entityBRepository = entityBRepository; } @Transactional public void transactionalAdd(String id1, String id2) { repository.save(EntityA.builder().id(id1).build()); fail(); repository.save(EntityA.builder().id(id2).build()); } private void fail() { throw new RuntimeException(); } }
模块专属配置
@Configuration @EnableMongoRepositories class EntityDomainConfiguration { @Bean EntityDomainService domainService( EntityARepository entityARepository, EntityBRepository entityBRepository) { return new EntityDomainService(entityARepository, entityBRepository); } }
全局Mongo配置
@Configuration @EnableMongoRepositories() @EnableTransactionManagement class ApplicationConfiguration extends AbstractMongoClientConfiguration { // usual stuff @Bean MongoTransactionManager transactionManager(MongoDatabaseFactory dbFactory) { return new MongoTransactionManager(dbFactory); } }
测试Mongo容器配置
@EnableMongoRepositories public class MongoDBContainerTestConfiguration { @Bean @ServiceConnection public MongoDBContainer mongoDBContainer() { return new MongoDBContainer(DockerImageName.parse("mongo:7.0")).withExposedPorts(27017); } }
测试类代码
@Testcontainers @DataMongoTest @ContextConfiguration(classes = [MongoDBContainerTestConfiguration.class]) class EntityADomainSpec extends Specification { @Autowired EntityAMongoRepository simulationRepository @Autowired EntityBMongoRepository plotRepository EntityDomainService service def setup() { service = new EntityDomainConfiguration().domainService(simulationRepository, plotRepository) } def cleanup() { simulationRepository.deleteAll() plotRepository.deleteAll() } def "basic transaction test"() { when: service.transactionalAdd("abc", "def") then: thrown(RuntimeException) !simulationRepository.existsById("abc") } }
问题分析与解决方案
核心问题点
- 手动实例化服务导致事务失效:测试类中通过
new EntityDomainConfiguration().domainService(...)手动创建EntityDomainService实例,未经过Spring容器代理处理,@Transactional注解无法被Spring事务管理器识别。 - 重复的仓库扫描配置:全局配置、模块配置、测试配置都添加了
@EnableMongoRepositories,可能导致仓库实例重复创建或配置冲突。 - MongoDB未启用副本集:Testcontainers启动的MongoDB默认是单节点模式,不支持事务,必须显式配置为副本集模式。
修复步骤
修改测试类,通过Spring注入服务
去掉手动实例化代码,直接注入EntityDomainService,同时确保所有必要配置类被加载:@Testcontainers @DataMongoTest @ContextConfiguration(classes = [MongoDBContainerTestConfiguration.class, EntityDomainConfiguration.class, ApplicationConfiguration.class]) class EntityADomainSpec extends Specification { @Autowired EntityDomainService service @Autowired EntityAMongoRepository simulationRepository @Autowired EntityBMongoRepository plotRepository def cleanup() { simulationRepository.deleteAll() plotRepository.deleteAll() } def "basic transaction test"() { when: service.transactionalAdd("abc", "def") then: thrown(RuntimeException) !simulationRepository.existsById("abc") } }调整
@EnableMongoRepositories配置
为每个@EnableMongoRepositories指定具体的basePackages,避免重复扫描:// 全局配置 @Configuration @EnableMongoRepositories(basePackages = "com.yourpackage.global.repos") @EnableTransactionManagement class ApplicationConfiguration extends AbstractMongoClientConfiguration { // ... 原有代码 } // 模块配置 @Configuration @EnableMongoRepositories(basePackages = "com.yourpackage.entity.repos") class EntityDomainConfiguration { // ... 原有代码 }测试配置中可移除
@EnableMongoRepositories,因为@DataMongoTest已包含仓库扫描逻辑。配置MongoDB副本集
修改Testcontainers配置,启用并初始化副本集:public class MongoDBContainerTestConfiguration { @Bean @ServiceConnection public MongoDBContainer mongoDBContainer() { return new MongoDBContainer(DockerImageName.parse("mongo:7.0")) .withCommand("--replSet rs0") .withCreateContainerCmdModifier(cmd -> cmd.withEntrypoint("mongod", "--replSet", "rs0", "--bind_ip_all")); } @Bean MongoClient mongoClient(MongoDBContainer container) { // 初始化副本集 try (MongoClient client = MongoClients.create(container.getReplicaSetUrl())) { client.getDatabase("admin").runCommand(new Document("replSetInitiate", new Document())); } catch (Exception e) { // 处理初始化异常 } return MongoClients.create(container.getReplicaSetUrl()); } }
内容的提问来源于stack exchange,提问作者rafal.sz
相关产品推荐
相关产品推荐

