You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

@Transactional事务异常未捕获:回滚提示与返回结果不符问题

Hey there, let's figure out why you're hitting that Transaction silently rolled back because it has been marked as rollback-only error, even though you've wrapped everything in a try-catch. I've run into this exact issue before, so let's break it down:

What's Actually Happening Here?

Your method is marked with @Transactional, so Spring is managing the transaction for you. Here's the play-by-play when that duplicate primary key error hits:

  1. When insertUtilisateurTracking throws a duplicate key SQL exception (a runtime exception), Spring's transaction manager immediately marks the current transaction as rollback-only (this is the default behavior for runtime exceptions).
  2. You catch that exception in your try-catch block, set your response to ERREUR_INTERNE, and return it like nothing went wrong.
  3. But here's the kicker: after your method finishes, Spring tries to commit the transaction. It sees the transaction is marked rollback-only, so it rolls it back—but since your method didn't throw an exception to signal the failure, Spring spits out that warning/error, which is what Postman picks up instead of your custom response.

Fixes That Will Work for Your Scenario

I've got two solid solutions for you, depending on how you want to structure your code:

Solution 1: Manually Mark Rollback & Throw a Custom Exception

This approach keeps your transaction logic in one place, ensures proper rollback, and lets you return your custom response:
First, you'll need to tell Spring to rollback explicitly when you catch the exception, then throw a custom exception that your controller can handle to send the right response.

Here's the modified code:

import org.springframework.transaction.interceptor.TransactionAspectSupport;
import javax.transaction.Transactional;

// Add rollbackFor to make sure all exceptions trigger rollback (optional but safe)
@Transactional(rollbackFor = Exception.class)
public UpdateUserPasswordResponse updateUserPassword(String login, String pwd, String newPwd) throws InvalidKeyException, IllegalBlockSizeException, BadPaddingException, InvalidAlgorithmParameterException, NoSuchAlgorithmException, NoSuchPaddingException, UnsupportedEncodingException, CustomInternalErrorException {
    
    GetUserByUsernameAndPasswordResponse responseLogin = new GetUserByUsernameAndPasswordResponse();
    UpdateUserPasswordResponse response = new UpdateUserPasswordResponse();
    
    // Your existing setup code here
    
    if (responseLogin.getUser() != null) {
        // Your existing password encryption code here
        try {
            int lineUpdated = dao.updateUserPassword(login, Cryptedpwd);
            int lineInserted = dao.insertUtilisateurTracking(responseLogin.getUser().getId(), new Date());
            response.setMessage(Constants.SUCCESS);
            response.setStatus(Constants.OK);
        } catch (Exception e) {
            // Manually mark the transaction for rollback
            TransactionAspectSupport.currentTransactionStatus().setRollbackOnly();
            // Set your error response
            response.setMessage(Constants.ERREUR_INTERNE);
            response.setStatus(Constants.KO);
            // Throw a custom exception so your controller can catch it
            throw new CustomInternalErrorException("Transaction failed", response);
        }
    } else {
        // Your existing login failure logic here
        if (responseLogin.getMessage().equals(Constants.LOGIN_PASSWORD_INCORRECT)) {
            response.setStatus(Constants.KO);
            response.setMessage(Constants.LOGIN_PASSWORD_INCORRECT);
        } else {
            response.setMessage(Constants.ERREUR_INTERNE);
            response.setStatus(Constants.KO);
        }
    }
    return response;
}

Then add a global exception handler in your controller layer to catch that custom exception and return your response:

@RestControllerAdvice
public class GlobalExceptionHandler {

    @ExceptionHandler(CustomInternalErrorException.class)
    @ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR)
    public UpdateUserPasswordResponse handleInternalError(CustomInternalErrorException ex) {
        return ex.getErrorResponse();
    }
}

Solution 2: Move the Try-Catch Outside the Transaction

If you don't want to mess with custom exceptions, split your code into two parts: a transactional inner method, and an outer method that handles the try-catch. This way, the transaction is properly rolled back before the outer method handles the error response:

// Outer method: no @Transactional, handles try-catch and response
public UpdateUserPasswordResponse updateUserPassword(String login, String pwd, String newPwd) throws InvalidKeyException, IllegalBlockSizeException, BadPaddingException, InvalidAlgorithmParameterException, NoSuchAlgorithmException, NoSuchPaddingException, UnsupportedEncodingException {
    
    GetUserByUsernameAndPasswordResponse responseLogin = new GetUserByUsernameAndPasswordResponse();
    UpdateUserPasswordResponse response = new UpdateUserPasswordResponse();
    
    // Your existing setup code here
    
    if (responseLogin.getUser() != null) {
        // Your existing password encryption code here
        try {
            // Call the transactional inner method
            doPasswordUpdateAndTracking(login, Cryptedpwd, responseLogin.getUser().getId());
            response.setMessage(Constants.SUCCESS);
            response.setStatus(Constants.OK);
        } catch (Exception e) {
            response.setMessage(Constants.ERREUR_INTERNE);
            response.setStatus(Constants.KO);
        }
    } else {
        // Your existing login failure logic here
        if (responseLogin.getMessage().equals(Constants.LOGIN_PASSWORD_INCORRECT)) {
            response.setStatus(Constants.KO);
            response.setMessage(Constants.LOGIN_PASSWORD_INCORRECT);
        } else {
            response.setMessage(Constants.ERREUR_INTERNE);
            response.setStatus(Constants.KO);
        }
    }
    return response;
}

// Inner method: handles transactional operations
@Transactional(rollbackFor = Exception.class)
private void doPasswordUpdateAndTracking(String login, String cryptedPwd, Long userId) {
    int lineUpdated = dao.updateUserPassword(login, cryptedPwd);
    int lineInserted = dao.insertUtilisateurTracking(userId, new Date());
}

Quick Notes to Remember

  • By default, @Transactional only rolls back on runtime exceptions/errors. If your DAO throws checked exceptions, add rollbackFor = Exception.class to cover all cases.
  • Never catch an exception in a transactional method and return normally—this confuses Spring's transaction manager, which expects exceptions to signal transaction failures.

内容的提问来源于stack exchange,提问作者taha manar

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.14 09:17:50