Spring WebFlux中R2DBCRepository使用@Query插入数据失败
问题分析与解决方案
可能的失败原因
- 异步操作未被订阅/错误未处理:R2DBC基于Reactor异步非阻塞模型,
void返回值的方法不会触发操作执行,且异常会被静默吞噬。因为Reactor的数据流只有在被订阅时才会执行,没有订阅的话插入操作根本没运行,就算运行出现异常也没有渠道抛出。 - Repository泛型的复合键映射问题:你的Repository继承了
R2dbcRepository<Test, testEmbeddedId>,如果testEmbeddedId这个复合键类的映射不符合R2DBC要求(比如未正确实现equals/hashCode、字段映射错误),可能导致上下文初始化时存在隐性错误,影响后续操作执行。 - 事务配置失效:R2DBC的
@Transactional需要依赖正确配置的R2dbcTransactionManager,如果事务管理器未配置或配置错误,事务不会生效,也可能导致操作无反馈。
替代方案
1. 修改Repository方法的返回值
将void改为Mono<Integer>(返回受影响行数)或Mono<Void>,确保操作被订阅并能捕获异常:
@Repository("testRepository") public interface TestRepository extends R2dbcRepository<Test, testEmbeddedId> { @Modifying @Query(value = "insert into test(id, name, status, created_date) VALUES ('b128f97d-d52c-4677-8746-00e2959c9ec6', 'c', 'd', '2022-10-28')") @Transactional Mono<Integer> insertData(); }
调用时必须订阅这个Mono才能触发执行并处理结果/异常:
testRepository.insertData() .subscribe( rows -> System.out.println("成功插入 " + rows + " 条数据"), error -> { System.err.println("插入失败:"); error.printStackTrace(); } );
2. 用@IdClass替代@Embedded处理复合键
如果必须使用复合键,可以尝试用@IdClass注解代替@Embedded,将复合键的字段直接定义在实体类中,同时创建对应的主键类:
// 主键类,需实现equals和hashCode public class TestEmbeddedId implements Serializable { private UUID id; private String name; // getter、setter、equals、hashCode实现 } // 实体类 @Table("test") @IdClass(TestEmbeddedId.class) public class Test { @Id private UUID id; @Id private String name; private String status; private LocalDate createdDate; // getter、setter }
之后Repository可以正常继承R2dbcRepository<Test, TestEmbeddedId>,尝试用标准的save方法插入数据,避免自定义查询的问题。
3. 直接使用DatabaseClient执行操作
绕开Spring Data R2DBC Repository,用DatabaseClient直接执行SQL,更灵活且能明确处理异常:
@Component public class TestDataService { private final DatabaseClient databaseClient; public TestDataService(DatabaseClient databaseClient) { this.databaseClient = databaseClient; } public Mono<Integer> insertTestData() { return databaseClient.sql("INSERT INTO test(id, name, status, created_date) VALUES (:id, :name, :status, :createdDate)") .bind("id", UUID.fromString("b128f97d-d52c-4677-8746-00e2959c9ec6")) .bind("name", "c") .bind("status", "d") .bind("createdDate", LocalDate.of(2022, 10, 28)) .fetch() .rowsUpdated(); } }
调用时同样需要订阅Mono来触发执行并处理结果。
内容的提问来源于stack exchange,提问作者lahirumw
相关产品推荐
相关产品推荐

