{"record":{"id":"55b4fe079f78c7ce","repo":"yudaocode/SpringBoot-Labs","slug":"error-55b4fe","errorCode":null,"errorMessage":"我就是故意抛出一个异常，测试下事务回滚","messagePattern":"我就是故意抛出一个异常，测试下事务回滚","errorType":"http","errorClass":"RuntimeException","httpStatus":500,"severity":"error","filePath":"lab-27/lab-27-webflux-r2dbc/src/main/java/cn/iocoder/springboot/lab27/springwebflux/controller/UserController.java","lineNumber":89,"sourceCode":"                    @Override\n                    public Mono<Integer> apply(UserDO userDO) {\n                        if (userDO != USER_NULL) {\n                            // 返回 -1 表示插入失败。\n                            // 实际上，一般是抛出 ServiceException 异常。因为这个示例项目里暂时没做全局异常的定义，所以暂时返回 -1 啦\n                            return Mono.just(-1);\n                        }\n                        // 将 addDTO 转成 UserDO\n                        userDO = new UserDO()\n                                .setUsername(addDTO.getUsername())\n                                .setPassword(addDTO.getPassword())\n                                .setCreateTime(new Date());\n                        // 插入数据库\n                        return userRepository.save(userDO).flatMap(new Function<UserDO, Mono<Integer>>() {\n                            @Override\n                            public Mono<Integer> apply(UserDO userDO) {\n                                // 如果编号为偶数，抛出异常。\n                                if (userDO.getId() % 2 == 0) {\n                                    throw new RuntimeException(\"我就是故意抛出一个异常，测试下事务回滚\");\n                                }\n\n                                // 返回编号\n                                return Mono.just(userDO.getId());\n                            }\n                        });\n                    }\n\n                });\n    }\n\n    /**\n     * 更新指定用户编号的用户\n     *\n     * @param updateDTO 更新用户信息 DTO\n     * @return 是否修改成功\n     */\n    @PostMapping(\"/update\")","sourceCodeStart":71,"sourceCodeEnd":107,"githubUrl":"https://github.com/yudaocode/SpringBoot-Labs/blob/6c12efaed06d12907a0f40dd2ad1f7020aec8798/lab-27/lab-27-webflux-r2dbc/src/main/java/cn/iocoder/springboot/lab27/springwebflux/controller/UserController.java#L71-L107","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","solutions":["Verify a ReactiveTransactionManager bean exists (e.g., R2dbcTransactionManager) and the insert path is wrapped with @Transactional or TransactionalOperator; otherwise the 'rollback' demo commits anyway.","Ensure the exception is thrown inside the flatMap/map lambda (as shown) so it travels the same chain as the save() publisher.","Test by inserting rows repeatedly: odd ids persist, even ids roll back — check the table to confirm.","For production, replace the even/id gimmick with a real failure signal mapped to a business exception."],"exampleFix":"// before: throwing inside nested flatMap — works only if tx wraps the whole chain\nreturn userRepository.save(userDO).flatMap(saved -> {\n    if (saved.getId() % 2 == 0) {\n        throw new RuntimeException(\"我就是故意抛出一个异常，测试下事务回滚\");\n    }\n    return Mono.just(saved.getId());\n});\n\n// after: explicit error signal, same semantics, clearer intent\nreturn userRepository.save(userDO).flatMap(saved ->\n    saved.getId() % 2 == 0\n        ? Mono.error(new RuntimeException(\"我就是故意抛出一个异常，测试下事务回滚\"))\n        : Mono.just(saved.getId()));","handlingStrategy":"validation","validationCode":"// Validate before insert so the demo exception cannot fire unexpectedly:\nif (addDTO.getUsername() == null || addDTO.getUsername().isEmpty()) {\n    return Mono.error(new IllegalArgumentException(\"username required\"));\n}","typeGuard":null,"tryCatchPattern":"// Consumer side, treat rollback as a business failure:\nuserService.add(addDTO)\n    .onErrorResume(ex -> {\n        log.warn(\"insert rolled back: {}\", ex.getMessage());\n        return Mono.just(-1);\n    });","preventionTips":["Ensure a ReactiveTransactionManager bean exists before relying on rollback.","Throw/return errors from inside the reactive chain (flatMap/map), never from external threads.","Verify rollback behavior with an integration test that asserts the row count is unchanged."],"tags":["webflux","r2dbc","reactive-transactions","rollback","tutorial"],"backgroundTag":null,"analyzedSha":"6c12efaed06d12907a0f40dd2ad1f7020aec8798","analyzedAt":"2026-08-14T13:06:31.500Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}