Spring Boot @Transactional在处理异常时不会回滚数据库插入

Spring Boot @Transactional does not roll back database inserts when I handle exceptions

提问人:Nhân Nguyễn 提问时间:11/7/2023 更新时间:11/7/2023 访问量:47

问:

我在这里需要一些帮助,我不明白为什么我的事务在发生异常时没有被回滚。我在 Oracle Database 3.0.11 中使用 Spring Boot 3.17.15 和 JOOQ 12c。

JooqConfiguration

@Configuration
@EnableTransactionManagement
public class JooqConfiguration {

    @Primary
    @Bean
    @ConfigurationProperties(prefix = "spring.datasource.thutienchamno")
    public DataSource dataSourceThuTienChamNo() {
        return DataSourceBuilder.create().build();
    }

    @Bean
    public DSLContext dslThuTienChamNo(@Qualifier("dataSourceThuTienChamNo") DataSource dataSource) {
        return DSL.using(dataSource, SQLDialect.ORACLE);
    }

}

GlobalExceptionHandlerController

@RestControllerAdvice
public class GlobalExceptionHandlerController {
    private static final Logger logger = LoggerFactory.getLogger(GlobalExceptionHandlerController.class);

    @Bean
    public ErrorAttributes errorAttributes() {
        // Hide exception field in the return object
        return new DefaultErrorAttributes() {
            @Override
            public Map<String, Object> getErrorAttributes(WebRequest webRequest, ErrorAttributeOptions options) {
                return super.getErrorAttributes(webRequest, ErrorAttributeOptions.defaults().excluding(ErrorAttributeOptions.Include.EXCEPTION));
            }
        };
    }

    @ExceptionHandler(CustomException.class)
    public ResponseEntity<ResponseDTO> handleCustomException(CustomException ex) {
        ResponseDTO responseDTO = new ResponseDTO(false, ex.getMessage(), null);

        logger.error("Có lỗi xả ra với service Nợ khó đòi: ", ex);

        return ResponseEntity.status(ex.getHttpStatus()).body(responseDTO);
    }

    @ExceptionHandler(Exception.class)
    public ResponseEntity<ResponseDTO> handleException(Exception ex) {
        ResponseDTO responseDTO = new ResponseDTO();

        // Lấy chi tiết lỗi ra
        StackTraceElement[] stackTraceElement = ex.getStackTrace();
        if (stackTraceElement != null && stackTraceElement.length > 0) {
            OutputError outputError = new OutputError(stackTraceElement[0].getFileName(), stackTraceElement[0].getClassName(), stackTraceElement[0].getMethodName(), stackTraceElement[0].getLineNumber());

            responseDTO = new ResponseDTO(false, "Có lỗi xả ra với service Nợ khó đòi: " + ex.getMessage(), outputError);
        } else {
            responseDTO = new ResponseDTO(false, "Có lỗi xả ra với service Nợ khó đòi: " + ex.getMessage(), null);
        }

        // logger.error("Có lỗi xả ra với service Nợ khó đòi: ", ex);

        return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body(responseDTO);
    }

    @ExceptionHandler(MethodArgumentNotValidException.class)
    public ResponseEntity<ResponseDTO> handleInvalidArgument(MethodArgumentNotValidException ex, HttpServletResponse res) {
        Map<String, String> errors = new HashMap<>();
        ex.getBindingResult().getAllErrors().forEach((error) -> {
            String fieldName = ((FieldError) error).getField();
            String message = error.getDefaultMessage();
            errors.put(fieldName, message);
        });

        logger.error("Có lỗi xả ra với service Nợ khó đòi: ", ex);

        ObjectMapper mapper = new ObjectMapper();
        String jsonResult;
        try {
            jsonResult = mapper.writerWithDefaultPrettyPrinter().writeValueAsString(errors);

            return ResponseEntity.status(HttpStatus.BAD_REQUEST).body(new ResponseDTO(false, HttpStatus.BAD_REQUEST.name(), mapper.readValue(jsonResult, Object.class)));
        } catch (JsonProcessingException e) {
            throw new RuntimeException(e);
        }
    }

    @ExceptionHandler(BindException.class)
    @ResponseStatus(HttpStatus.BAD_REQUEST)
    public String handleBindException(BindException e) {
        // Trả về message của lỗi đầu tiên
        if (e.getBindingResult().hasErrors()) {
            e.getBindingResult().getAllErrors().get(0).getDefaultMessage();
        }

        return "Request không hợp lệ";
    }
}

CustomException

public class CustomException extends RuntimeException {

    private static final long serialVersionUID = 1L;

    private final String message;
    private final HttpStatus httpStatus;

    public CustomException(String message, HttpStatus httpStatus) {
        super(message);

        this.message = message;
        this.httpStatus = httpStatus;
    }

    @Override
    public String getMessage() {
        return message;
    }

    public HttpStatus getHttpStatus() {
        return httpStatus;
    }

}

CNHSoUploadNKDRepositoryImpl

@Repository
public class CNHSoUploadNKDRepositoryImpl implements CNHSoUploadNKDRepository {
    private final DSLContext dslContext;
    CnHsoUpload cnHsoUpload = CnHsoUpload.CN_HSO_UPLOAD.as("cnHsoUpload");

    public CNHSoUploadNKDRepositoryImpl(DSLContext dslContext) {
        this.dslContext = dslContext;
    }


    @Override
    public Boolean save(ThongTinHoSoNKD hoSo) {
        dslContext.insertInto(cnHsoUpload)
                .set(cnHsoUpload.ID_TTHAI_NO, hoSo.ID_TTHAI_NO)
                .set(cnHsoUpload.MA_LOAI_HSO, hoSo.MA_LOAI_HSO)
                .set(cnHsoUpload.MA_DVIQLY, hoSo.MA_DVIQLY)
                .set(cnHsoUpload.ID_HSO, dslContext.nextval(Sequences.SEQ_CN_HSO_UPLOAD))
                .execute();

        return true;
    }


    @Override
    public Boolean update(ThongTinHoSoNKD hoSo) {

        // I intentionally handled it this way to trigger an exception to enable transaction rollback.
        String aa = hoSo.MA_DVIQLY.concat("");

        dslContext.update(cnHsoUpload)
                .set(cnHsoUpload.ID_TTHAI_NO, hoSo.ID_TTHAI_NO)
                .set(cnHsoUpload.MA_LOAI_HSO, hoSo.MA_LOAI_HSO)
                .set(cnHsoUpload.MA_DVIQLY, hoSo.MA_DVIQLY)
                .where(cnHsoUpload.ID_HSO.eq(hoSo.ID_HSO))
                .execute();

        return true;
    }
}

NoKhoDoiService

@Service
public class NoKhoDoiService {
    private final CNHSoUploadNKDRepository cnhSoUploadNKDRepository;

    public NoKhoDoiService(CNHSoUploadNKDRepository cnhSoUploadNKDRepository) {
        super();
        this.cnhSoUploadNKDRepository = cnhSoUploadNKDRepository;
    }

    @Transactional(propagation = Propagation.REQUIRED, rollbackFor = {CustomException.class, Exception.class, RuntimeException.class})
    public void testRollBackData() throws CustomException {
        ThongTinHoSoNKD thongTinHoSoNKD_1 = new ThongTinHoSoNKD("PD0601", -1l, -1l, "TEST_1");
        thongTinHoSoNKD_1 = cnhSoUploadNKDRepository.saveAndReturn(thongTinHoSoNKD_1);

        //The code I intentionally triggered an exception, but the data inserted above is still not rolled back.
        cnhSoUploadNKDRepository.update(new ThongTinHoSoNKD()); 
    }
}

application.yml

server:
  port: 8804

spring:
  profiles:
    active: debug
  application:
    name: service-nokhodoi
  main:
    allow-bean-definition-overriding: true
  datasource:
    type: com.zaxxer.hikari.HikariDataSource
    thutienchamno:
      jdbcUrl: jdbc:oracle:thin:@10.0.40.87:1522:CMIS3
      username: TTIENCNO
      password: TTIENCNODB27
      hikari:
        auto-commit: false

这是我的代码。我记录了该过程,当我测试它时,日志输出如下。

22:36:09.660 [http-nio-8804-exec-6] DEBUG o.s.j.support.JdbcTransactionManager - Creating new transaction with name [evn.evnict.nokhodoi.services.NoKhoDoiService.testGetData]: PROPAGATION_REQUIRED,ISOLATION_DEFAULT,-evn.evnict.nokhodoi.exceptions.CustomException,-java.lang.Exception,-java.lang.RuntimeException
22:36:09.663 [http-nio-8804-exec-6] DEBUG o.s.j.support.JdbcTransactionManager - Acquired Connection [HikariProxyConnection@2110726266 wrapping oracle.jdbc.driver.T4CConnection@d7732f5] for JDBC transaction
22:36:09.663 [http-nio-8804-exec-6] DEBUG o.s.j.support.JdbcTransactionManager - Switching JDBC Connection [HikariProxyConnection@2110726266 wrapping oracle.jdbc.driver.T4CConnection@d7732f5] to manual commit
22:36:09.664 [http-nio-8804-exec-6] DEBUG org.jooq.tools.LoggerListener - Executing query          : select "TTIENCNO"."SEQ_CN_HSO_UPLOAD".nextval from DUAL
22:36:09.670 [http-nio-8804-exec-6] DEBUG org.jooq.tools.LoggerListener - Fetched result           : +-------+
22:36:09.670 [http-nio-8804-exec-6] DEBUG org.jooq.tools.LoggerListener -                          : |nextval|
22:36:09.670 [http-nio-8804-exec-6] DEBUG org.jooq.tools.LoggerListener -                          : +-------+
22:36:09.670 [http-nio-8804-exec-6] DEBUG org.jooq.tools.LoggerListener -                          : |     77|
22:36:09.670 [http-nio-8804-exec-6] DEBUG org.jooq.tools.LoggerListener -                          : +-------+
22:36:09.670 [http-nio-8804-exec-6] DEBUG org.jooq.tools.LoggerListener - Fetched row(s)           : 1
22:36:09.671 [http-nio-8804-exec-6] DEBUG org.jooq.tools.LoggerListener - Executing query          : insert into "TTIENCNO"."CN_HSO_UPLOAD" "cnHsoUpload" ("ID_TTHAI_NO", "MA_LOAI_HSO", "MA_DVIQLY", "ID_HSO") values (?, ?, ?, ?)
22:36:09.671 [http-nio-8804-exec-6] DEBUG org.jooq.tools.LoggerListener - -> with bind values      : insert into "TTIENCNO"."CN_HSO_UPLOAD" "cnHsoUpload" ("ID_TTHAI_NO", "MA_LOAI_HSO", "MA_DVIQLY", "ID_HSO") values (-1, 'TEST_1', 'PD0601', 77)
22:36:09.680 [http-nio-8804-exec-6] DEBUG org.jooq.tools.LoggerListener - Affected row(s)          : 1
22:36:09.681 [http-nio-8804-exec-6] DEBUG org.jooq.tools.LoggerListener - Fetched result           : +---------+------+-----------+-----------+
22:36:09.681 [http-nio-8804-exec-6] DEBUG org.jooq.tools.LoggerListener -                          : |MA_DVIQLY|ID_HSO|ID_TTHAI_NO|MA_LOAI_HSO|
22:36:09.681 [http-nio-8804-exec-6] DEBUG org.jooq.tools.LoggerListener -                          : +---------+------+-----------+-----------+
22:36:09.681 [http-nio-8804-exec-6] DEBUG org.jooq.tools.LoggerListener -                          : |PD0601   |    77|         -1|TEST_1     |
22:36:09.681 [http-nio-8804-exec-6] DEBUG org.jooq.tools.LoggerListener -                          : +---------+------+-----------+-----------+
22:36:09.681 [http-nio-8804-exec-6] DEBUG org.jooq.tools.LoggerListener - Fetched row(s)           : 1
22:36:09.681 [http-nio-8804-exec-6] DEBUG o.s.j.support.JdbcTransactionManager - Initiating transaction rollback
22:36:09.681 [http-nio-8804-exec-6] DEBUG o.s.j.support.JdbcTransactionManager - Rolling back JDBC transaction on Connection [HikariProxyConnection@2110726266 wrapping oracle.jdbc.driver.T4CConnection@d7732f5]
22:36:09.682 [http-nio-8804-exec-6] DEBUG o.s.j.support.JdbcTransactionManager - Releasing JDBC Connection [HikariProxyConnection@2110726266 wrapping oracle.jdbc.driver.T4CConnection@d7732f5] after transaction
22:36:09.683 [http-nio-8804-exec-6] DEBUG o.s.w.s.m.m.a.ExceptionHandlerExceptionResolver - Using @ExceptionHandler evn.evnict.nokhodoi.exceptions.GlobalExceptionHandlerController#handleException(Exception)
22:36:09.683 [http-nio-8804-exec-6] DEBUG o.s.w.s.m.m.a.HttpEntityMethodProcessor - Using 'application/json', given [application/json] and supported [application/json, application/*+json]
22:36:09.683 [http-nio-8804-exec-6] DEBUG o.s.w.s.m.m.a.HttpEntityMethodProcessor - Writing [ResponseDTO(success=false, message=Có lỗi xả ra với service Nợ khó đòi: Cannot invoke "String.concat (truncated)...]
22:36:09.684 [http-nio-8804-exec-6] WARN  o.s.w.s.m.m.a.ExceptionHandlerExceptionResolver - Resolved [java.lang.NullPointerException: Cannot invoke "String.concat(String)" because "hoSo.MA_DVIQLY" is null]
22:36:09.684 [http-nio-8804-exec-6] DEBUG o.s.web.servlet.DispatcherServlet - Completed 500 INTERNAL_SERVER_ERROR
22:36:09.685 [http-nio-8804-exec-6] DEBUG o.a.coyote.http11.Http11Processor - Socket: [org.apache.tomcat.util.net.NioEndpoint$NioSocketWrapper@1ab663d1:org.apache.tomcat.util.net.NioChannel@459072d5:java.nio.channels.SocketChannel[connected local=/127.0.0.1:8804 remote=/127.0.0.1:52455]], Status in: [OPEN_READ], State out: [CLOSED]
22:36:09.685 [http-nio-8804-exec-6] DEBUG o.a.coyote.http11.Http11NioProtocol - Pushed Processor [org.apache.coyote.http11.Http11Processor@11b95d27]
22:36:09.685 [http-nio-8804-exec-6] DEBUG o.a.tomcat.util.threads.LimitLatch - Counting down[http-nio-8804-exec-6] latch=1
22:36:09.685 [http-nio-8804-exec-6] DEBUG o.apache.tomcat.util.net.NioEndpoint - Calling [org.apache.tomcat.util.net.NioEndpoint@3d68f3a5].closeSocket([org.apache.tomcat.util.net.NioEndpoint$NioSocketWrapper@1ab663d1:org.apache.tomcat.util.net.NioChannel@459072d5:java.nio.channels.SocketChannel[connected local=/127.0.0.1:8804 remote=/127.0.0.1:52455]])

我已经研究并尝试了各种方法,但是当发生异常时,我仍然无法回滚数据。你能看看我的代码并帮助我确定问题可能出在哪里吗?

java spring-boot spring-data-jpa 事务 jooq

评论

0赞 Lukas Eder 11/8/2023
您的日志表明回滚工作正常,那么似乎有什么失败呢?
0赞 Nhân Nguyễn 11/8/2023
@LukasEder 是的,我已经阅读了日志并注意到回滚过程成功。但是,当我检查数据库中的数据时,添加的数据仍然保留而不回滚。我真的很难找到问题可能出在哪里。NoKhoDoiService
0赞 Lukas Eder 11/8/2023
我想这更像是一个 Spring 问题,而不是一个 jOOQ 问题。一些假设:1)也许你在接口中声明了错误(即不同的交易语义)?2) 也许你给 jOOQ 注入了一个错误?jOOQ 是否获得与您的日志相同的实例?CNHSoUploadNKDRepositoryDataSourceHikariProxyConnection

答: 暂无答案