【问题标题】:How to throw custom exception from transactional Spring service?如何从事务性 Spring 服务中抛出自定义异常?
【发布时间】:2026-02-16 10:00:01
【问题描述】:

我有这个春季服务:

@Service
@Transactional
public class ConsorcioServiceImpl implements ConsorcioService {

    ...

    @Autowired
    private ConsorcioRepository consorcioRepository;

    @Override
    public void saveBank(Consorcio consorcio) throws BusinessException {

        try {
            consorcioRepository.save(consorcio);
        }
        catch(DataIntegrityViolationException divex) {
            if(divex.getMessage().contains("uq_codigo")) {
                throw new DuplicatedCodeException(divex);
            }
            else {
                throw new BusinessException(dives); 
            }
        }
        catch (Exception e) {
            throw new BusinessException(e);
        }
    }


}

该服务使用此 Spring Data 存储库:

@Repository
public interface ConsorcioRepository extendsCrudRepository<Consorcio, Integer> {


}

我正在从 spring 控制器调用服务:

@Controller
@RequestMapping(value = "/bank")
public class BancaController {

    @Autowired
    private ConsorcioService consorcioService;

    @RequestMapping(value="create", method=RequestMethod.POST)
    public ModelAndView crearBanca(@Valid BancaViewModel bancaViewModel, BindingResult bindingResult,
                                   RedirectAttributes redirectAttributes) {
        ModelAndView modelAndView;

        MessageViewModel result;
        try {

            consorcioService.saveBank(bancaViewModel.buildBanca());
            result = new MessageViewModel(MessageType.SUCESS);
            redirectAttributes.addFlashAttribute("messageViewModel", result);
            modelAndView = new ModelAndView("redirect:/banca/crear");
            return modelAndView;
        } catch (Exception e) {
            result = new MessageViewModel(MessageType.ERROR);
            modelAndView = new ModelAndView("crear-bancas");
            modelAndView.addObject("messageViewModel", result);
            return modelAndView;
        }
}

但我在控制器中得到的例外是:org.springframework.transaction.TransactionSystemException: Could not commit JPA transaction; nested exception is javax.persistence.RollbackException: Transaction marked as rollbackOnly 而不是 DuplicatedCodeException 我投入服务。我需要识别异常的类型,以便我可以给自定义友好的用户消息。

【问题讨论】:

    标签: java spring spring-mvc jpa


    【解决方案1】:

    发生这种情况是因为您的异常被包装在 RollbackException 中作为 Throwable 原因。反过来RollbackException也是TransactionSystemException的原因。

    您可以构建全局异常处理程序以根据需要捕获和自定义所有异常:

    @ControllerAdvice
    class GlobalControllerExceptionHandler {
        @ResponseStatus(HttpStatus.CONFLICT) 
        @ExceptionHandler(TransactionSystemException.class)
        public ModelAndView handleDupilatedCode(HttpServletRequest req, TransactionSystemException ex) {
            // Build you exception body here
            Throwable e = ex.getOriginalException();
            if(e instanceof DuplicatedCodeException)
              // throw
            // Or build custom exception as you want
            ModelAndView mav = new ModelAndView();
            mav.addObject("exception", e);
            mav.addObject("url", req.getRequestURL());
            mav.setViewName("error");
            return mav;
        }
    }
    

    @ControllerAdvice 自 Spring 3.2 起可用

    您还可以在@Controller 级别使用@ExceptionHandler,或者如果您有一个作为所有控制器的超类,则在abstract-controller 中创建此处理。

    public class FooController {
    
        //...
        @ExceptionHandler({ CustomException1.class, CustomException2.class })
        public void handleException() {
            //
        }
    }
    

    还有其他一些方法,完整参考请关注:

    Spring IO: Exception handling in Spring MVC

    Baeldung: Exception handling with Spring

    【讨论】:

    • 经过几个小时的斗争(以及至少 3 种其他尝试使用 @ControllerAdvice 使其工作的方法),使这个扩展 TransactionSystemException 而不是 Exception 的简单更改做到了。谢谢!
    【解决方案2】:

    你的 DuplicatedCodeException , BusinessException 应该是运行时异常,或者为方法 saveBank 添加:

    @Transactinal(rolbackFor={BusinessException.class,DuplicatedCodeException.,class })

    在其他情况下 spring 不会回滚事务。

    来自 Spring 文档:

    虽然 EJB 默认行为是让 EJB 容器 在系统异常时自动回滚事务(通常 运行时异常),EJB CMT 不回滚事务 自动在应用程序异常(即,检查 java.rmi.RemoteException 以外的异常)。虽然春天 声明式事务管理的默认行为遵循 EJB 约定(仅在未经检查的异常情况下自动回滚),它 自定义它通常很有用。

    【讨论】:

    • 我继承自 RuntimeException 而不是 Exception 并且行为符合我的预期。
    【解决方案3】:

    只需在catch (Exception e) 分支之前添加catch (TransactionSystemException tse),然后使用getOriginalException() 提取您的异常。

    try {
        consorcioService.saveBank(bancaViewModel.buildBanca());
        result = new MessageViewModel(MessageType.SUCESS);
        redirectAttributes.addFlashAttribute("messageViewModel", result);
        modelAndView = new ModelAndView("redirect:/banca/crear");
        return modelAndView;
    } catch (TransactionSystemException tse) {
        final Throwable ex = tse.getOriginalException();
        if (ex instanceof DuplicatedCodeException) {
            // DuplicatedCodeException
        }
    } catch (Exception e) {
        result = new MessageViewModel(MessageType.ERROR);
        modelAndView = new ModelAndView("crear-bancas");
        modelAndView.addObject("messageViewModel", result);
        return modelAndView;
    }
    

    【讨论】: