Spring MVC三层架构下DAO层预期异常的合规处理方案咨询
Spring MVC三层架构DAO预期业务异常的正确处理方案
核心结论
你最初的思路方向完全可行,调整自定义异常的类型即可解决事务回滚冲突问题,同时完全符合DDD分层规范,不会出现业务逻辑泄露。
具体实现步骤
- 定义自定义业务运行时异常
不要使用受检异常,直接继承RuntimeException,符合Spring事务默认回滚规则,无需额外配置即可触发事务回滚:
// 自定义业务异常 public class UserDuplicateException extends RuntimeException { public UserDuplicateException(String message) { super(message); } }
- 持久层异常透传
DAO层不需要捕获DataIntegrityViolationException,直接将异常向上抛到Service层即可,避免持久层技术细节向上泄露。 - Service层统一处理业务校验与异常转换
Service层作为业务逻辑的收敛层,同时做两层校验:
- 前置校验:调用
userDao.findByUsername()判断用户名是否存在,提前拦截大部分重复请求 - 兜底异常处理:捕获
DataIntegrityViolationException,判断为唯一约束冲突后抛出UserDuplicateException,解决高并发场景下前置校验的漏判问题
@Service public class UserServiceImpl implements UserService { @Autowired private UserDao userDao; @Transactional @Override public void register(User user) { // 前置业务校验,属于注册逻辑的一部分,全部收敛在Service层 Optional<User> existUser = userDao.findByUsername(user.getUsername()); if (existUser.isPresent()) { throw new UserDuplicateException("用户名已存在"); } try { userDao.insert(user); } catch (DataIntegrityViolationException e) { // 兜底校验,处理并发场景下的重复插入问题 throw new UserDuplicateException("用户名已存在"); } } }
- Controller层统一异常处理
用@ControllerAdvice全局捕获自定义业务异常,不需要在每个Controller方法里写try/catch,Controller只负责参数校验和正常流程的视图跳转:
@ControllerAdvice public class GlobalBizExceptionHandler { @ExceptionHandler(UserDuplicateException.class) public ModelAndView handleUserDuplicateException(UserDuplicateException e) { ModelAndView mav = new ModelAndView("register"); mav.addObject("errorTip", e.getMessage()); return mav; } }
两个备选方案的问题
- 方案1:直接在Controller层捕获
DataAccessException会将持久层技术细节泄露到表现层,后续更换持久层框架时需要同步修改Controller代码,违反分层隔离原则 - 方案2:在Controller层做用户名存在性校验,会导致业务规则散落在不同层级,后续其他业务调用注册接口时需要重复写校验逻辑,容易出现校验不一致的问题,确实违反DDD的业务逻辑收敛要求
事务回滚说明
你之前担心的事务冲突问题不存在:
- 如果自定义异常继承
RuntimeException,Spring的@Transactional默认就会触发回滚 - 如果一定要用受检异常,只需要在
@Transactional注解上添加rollbackFor = 你的自定义受检异常.class即可指定回滚规则,不会有冲突
内容的提问来源于stack exchange,提问作者Delsh
相关产品推荐
相关产品推荐

