yudaocode/SpringBoot-Labs · error · RuntimeException

我就是故意抛出一个异常,测试下事务回滚

Error message

我就是故意抛出一个异常,测试下事务回滚

What it means

A RuntimeException('我就是故意抛出一个异常,测试下事务回滚' — 'intentionally throwing to test transaction rollback') thrown inside a flatMap when a newly inserted user's generated id is even. The endpoint inserts a UserDO via R2DBC and, within a reactive transaction (TransactionalOperator / @Transactional on a reactive method), the exception triggers rollback of the INSERT, demonstrating declarative transactional semantics in WebFlux + spring-data-r2dbc.

Source

Thrown at lab-27/lab-27-webflux-r2dbc/src/main/java/cn/iocoder/springboot/lab27/springwebflux/controller/UserController.java:89

                    @Override
                    public Mono<Integer> apply(UserDO userDO) {
                        if (userDO != USER_NULL) {
                            // 返回 -1 表示插入失败。
                            // 实际上,一般是抛出 ServiceException 异常。因为这个示例项目里暂时没做全局异常的定义,所以暂时返回 -1 啦
                            return Mono.just(-1);
                        }
                        // 将 addDTO 转成 UserDO
                        userDO = new UserDO()
                                .setUsername(addDTO.getUsername())
                                .setPassword(addDTO.getPassword())
                                .setCreateTime(new Date());
                        // 插入数据库
                        return userRepository.save(userDO).flatMap(new Function<UserDO, Mono<Integer>>() {
                            @Override
                            public Mono<Integer> apply(UserDO userDO) {
                                // 如果编号为偶数,抛出异常。
                                if (userDO.getId() % 2 == 0) {
                                    throw new RuntimeException("我就是故意抛出一个异常,测试下事务回滚");
                                }

                                // 返回编号
                                return Mono.just(userDO.getId());
                            }
                        });
                    }

                });
    }

    /**
     * 更新指定用户编号的用户
     *
     * @param updateDTO 更新用户信息 DTO
     * @return 是否修改成功
     */
    @PostMapping("/update")

View on GitHub (pinned to 6c12efaed0)

Solutions

  1. Verify a ReactiveTransactionManager bean exists (e.g., R2dbcTransactionManager) and the insert path is wrapped with @Transactional or TransactionalOperator; otherwise the 'rollback' demo commits anyway.
  2. Ensure the exception is thrown inside the flatMap/map lambda (as shown) so it travels the same chain as the save() publisher.
  3. Test by inserting rows repeatedly: odd ids persist, even ids roll back — check the table to confirm.
  4. For production, replace the even/id gimmick with a real failure signal mapped to a business exception.

Example fix

// before: throwing inside nested flatMap — works only if tx wraps the whole chain
return userRepository.save(userDO).flatMap(saved -> {
    if (saved.getId() % 2 == 0) {
        throw new RuntimeException("我就是故意抛出一个异常,测试下事务回滚");
    }
    return Mono.just(saved.getId());
});

// after: explicit error signal, same semantics, clearer intent
return userRepository.save(userDO).flatMap(saved ->
    saved.getId() % 2 == 0
        ? Mono.error(new RuntimeException("我就是故意抛出一个异常,测试下事务回滚"))
        : Mono.just(saved.getId()));
Defensive patterns

Strategy: validation

Validate before calling

// Validate before insert so the demo exception cannot fire unexpectedly:
if (addDTO.getUsername() == null || addDTO.getUsername().isEmpty()) {
    return Mono.error(new IllegalArgumentException("username required"));
}

Try / catch

// Consumer side, treat rollback as a business failure:
userService.add(addDTO)
    .onErrorResume(ex -> {
        log.warn("insert rolled back: {}", ex.getMessage());
        return Mono.just(-1);
    });

Prevention

When it happens

Trigger: POST /user/add (the enclosing insert flow) where the DB assigns an AUTO_INCREMENT id that is even: userDO.getId() % 2 == 0 causes the throw, so roughly every other insert should roll back. Requires a live R2DBC database (MySQL/PostgreSQL) with the transaction manager configured.

Common situations: Classic reactive-transaction pitfall: the exception only rolls back if it propagates through the reactive chain that the TransactionalOperator wraps. If you throw from a thread outside the chain (e.g., inside subscribe() or an ExecutorService lambda) the rollback never happens, and the row stays committed. Also fails silently if ReactiveTransactionManager is not configured — then there is no transaction at all to roll back.

Related errors


AI-assisted analysis of yudaocode/SpringBoot-Labs@6c12efaed0 (2026-08-14). Data as JSON: /api/errors/55b4fe079f78c7ce. Report an issue: GitHub.