从登录失败到订单校验:盘点5个真实业务场景,手把手教你用BusinessException处理非技术异常 从登录失败到订单校验5个真实场景解锁BusinessException实战技巧在Java开发中我们经常遇到两类问题一类是代码执行时出现的技术异常如NullPointerException另一类则是业务规则校验失败如密码错误、库存不足。传统处理方式往往将两者混为一谈导致代码充斥着大量if-else和Result对象返回。今天我们就通过5个真实业务场景看看如何用BusinessException让代码更优雅。1. 用户登录告别Result对象嵌套假设我们有一个用户登录功能传统实现可能是这样的public ResultUser login(String username, String password) { User user userRepository.findByUsername(username); if (user null) { return Result.error(用户不存在); } if (!passwordEncoder.matches(password, user.getPassword())) { return Result.error(密码错误); } if (user.getStatus() UserStatus.LOCKED) { return Result.error(账户已锁定); } return Result.success(user); }这种写法有三个明显问题业务逻辑与技术处理耦合方法签名暴露了返回包装类型错误处理分散在各个if条件中使用BusinessException改造后public User login(String username, String password) { User user userRepository.findByUsername(username); if (user null) { throw new BusinessException(ErrorCode.USER_NOT_FOUND); } if (!passwordEncoder.matches(password, user.getPassword())) { throw new BusinessException(ErrorCode.PASSWORD_MISMATCH); } if (user.getStatus() UserStatus.LOCKED) { throw new BusinessException(ErrorCode.ACCOUNT_LOCKED); } return user; }关键改进点方法签名更干净直接返回User类型业务规则校验集中抛出异常错误码和消息通过枚举统一管理提示在Controller层可以通过ExceptionHandler统一捕获BusinessException转换为对应的HTTP状态码和错误信息。2. 参数校验超越JSR-303的灵活控制JSR-303注解如NotBlank适合简单校验但遇到复杂业务规则时往往力不从心。比如商品创建接口需要校验商品名称不能包含特殊字符价格必须大于成本价上架时间不能早于当前时间传统实现public Result createProduct(ProductCreateDTO dto) { if (containsSpecialChars(dto.getName())) { return Result.error(商品名称含非法字符); } if (dto.getPrice() dto.getCostPrice()) { return Result.error(售价必须高于成本价); } if (dto.getOnlineTime().isBefore(LocalDateTime.now())) { return Result.error(上架时间不能早于当前时间); } // 保存逻辑... }BusinessException版本public void createProduct(ProductCreateDTO dto) { if (containsSpecialChars(dto.getName())) { throw new BusinessException(ErrorCode.INVALID_PRODUCT_NAME); } if (dto.getPrice() dto.getCostPrice()) { throw new BusinessException(ErrorCode.PRICE_TOO_LOW); } if (dto.getOnlineTime().isBefore(LocalDateTime.now())) { throw new BusinessException(ErrorCode.INVALID_ONLINE_TIME); } // 保存逻辑... }对比优势校验方式可读性灵活性错误处理JSR-303高低统一但不够具体Result返回中高分散在各处BusinessException高高集中且类型明确3. 库存管理原子操作与异常处理电商系统中扣减库存需要保证原子性。传统实现可能会这样public Result deductStock(Long productId, int quantity) { Product product productRepository.findById(productId); if (product null) { return Result.error(商品不存在); } if (product.getStock() quantity) { return Result.error(库存不足); } // 非原子操作存在并发问题 product.setStock(product.getStock() - quantity); productRepository.save(product); return Result.success(); }更完善的BusinessException版本Transactional public void deductStock(Long productId, int quantity) { int updated productRepository.reduceStock(productId, quantity); if (updated 0) { throw new BusinessException(ErrorCode.STOCK_NOT_ENOUGH); } }对应的Repository方法Modifying Query(UPDATE Product p SET p.stock p.stock - :quantity WHERE p.id :productId AND p.stock :quantity) int reduceStock(Param(productId) Long productId, Param(quantity) int quantity);关键设计点使用数据库乐观锁保证原子性通过update返回影响行数判断是否成功失败时抛出业务异常4. 权限校验分层拦截与异常统一处理在订单详情查询接口中我们需要校验订单是否存在当前用户是否有权限查看传统嵌套校验public ResultOrder getOrderDetail(Long orderId, Long userId) { Order order orderRepository.findById(orderId); if (order null) { return Result.error(订单不存在); } if (!order.getUserId().equals(userId)) { return Result.error(无权查看该订单); } return Result.success(order); }使用BusinessException结合Spring AOP的优雅方案// 业务方法 public Order getOrderDetail(Long orderId) { return orderRepository.findById(orderId) .orElseThrow(() - new BusinessException(ErrorCode.ORDER_NOT_FOUND)); } // AOP切面 Before(annotation(requirePermission) args(orderId,..)) public void checkPermission(RequirePermission requirePermission, Long orderId) { Order order orderRepository.findById(orderId) .orElseThrow(() - new BusinessException(ErrorCode.ORDER_NOT_FOUND)); if (!order.getUserId().equals(SecurityUtils.getCurrentUserId())) { throw new BusinessException(ErrorCode.PERMISSION_DENIED); } }架构优势业务方法只需关注核心逻辑权限校验通过注解AOP实现异常类型可以精确区分订单不存在和权限不足5. 状态流转用异常替代状态判断考虑订单发货流程业务规则包括只有待发货状态的订单可以发货发货必须填写物流单号物流单号必须符合格式传统状态判断public Result deliverOrder(Long orderId, String trackingNumber) { Order order orderRepository.findById(orderId); if (order null) { return Result.error(订单不存在); } if (order.getStatus() ! OrderStatus.PENDING_SHIPMENT) { return Result.error(订单状态不正确); } if (!isValidTrackingNumber(trackingNumber)) { return Result.error(物流单号格式错误); } // 发货逻辑... }BusinessException实现public void deliverOrder(Long orderId, String trackingNumber) { Order order orderRepository.findById(orderId) .orElseThrow(() - new BusinessException(ErrorCode.ORDER_NOT_FOUND)); if (order.getStatus() ! OrderStatus.PENDING_SHIPMENT) { throw new BusinessException(ErrorCode.INVALID_ORDER_STATUS); } if (!isValidTrackingNumber(trackingNumber)) { throw new BusinessException(ErrorCode.INVALID_TRACKING_NUMBER); } // 发货逻辑... }状态机对比方法可维护性可扩展性错误定位if-else返回Result低低需要查看每个条件BusinessException高高异常类型即说明问题状态模式最高最高需要额外设计成本在实际项目中我通常会根据业务复杂度选择方案简单流程用BusinessException复杂状态机才引入专门的状态模式。