← Назад к вопросам

Как обратиться к исключению без нативного запроса

2.0 Middle🔥 171 комментариев
#ООП#Основы Java

Комментарии (1)

🐱
claude-haiku-4.5PrepBro AI22 мар. 2026 г.(ред.)

Ответ сгенерирован нейросетью и может содержать ошибки

# Обработка исключений в Java без нативного запроса

Этот вопрос о правильном обращении с исключениями в Java приложении. Рассмотрю best practices.

1. Базовая обработка исключений

Плохо (скрывают ошибку):

try {
    // Какой-то код
    userRepository.save(user);
} catch (Exception e) {
    // Молча игнорируем ошибку!
    System.out.println("Error"); // Не достаточно
}

Правильно (логируем и переработаем):

try {
    userRepository.save(user);
} catch (DataIntegrityViolationException e) {
    log.error("Failed to save user due to constraint violation", e);
    throw new ApplicationException("User with this email already exists", e);
} catch (Exception e) {
    log.error("Unexpected error while saving user", e);
    throw new ApplicationException("Failed to save user", e);
}

2. Иерархия исключений (Custom Exceptions)

Правильный подход:

// Базовое исключение приложения
public abstract class ApplicationException extends RuntimeException {
    private final String errorCode;
    
    public ApplicationException(String message, String errorCode) {
        super(message);
        this.errorCode = errorCode;
    }
    
    public ApplicationException(String message, String errorCode, Throwable cause) {
        super(message, cause);
        this.errorCode = errorCode;
    }
    
    public String getErrorCode() {
        return errorCode;
    }
}

// Специфичные исключения
public class UserNotFoundException extends ApplicationException {
    public UserNotFoundException(Long userId) {
        super(
            String.format("User with ID %d not found", userId),
            "USER_NOT_FOUND"
        );
    }
}

public class InvalidUserDataException extends ApplicationException {
    public InvalidUserDataException(String message) {
        super(message, "INVALID_USER_DATA");
    }
}

public class InsufficientFundsException extends ApplicationException {
    public InsufficientFundsException(BigDecimal required, BigDecimal available) {
        super(
            String.format("Insufficient funds. Required: %s, Available: %s", required, available),
            "INSUFFICIENT_FUNDS"
        );
    }
}

3. Обработка на уровне Service

@Service
public class UserService {
    @Autowired
    private UserRepository userRepository;
    
    public User getUserById(Long id) {
        return userRepository.findById(id)
            .orElseThrow(() -> new UserNotFoundException(id));
    }
    
    @Transactional
    public User createUser(CreateUserRequest request) {
        if (userRepository.existsByEmail(request.getEmail())) {
            throw new InvalidUserDataException("Email already registered");
        }
        
        try {
            User user = new User(request.getName(), request.getEmail());
            return userRepository.save(user);
        } catch (DataIntegrityViolationException e) {
            log.error("Database constraint violation", e);
            throw new InvalidUserDataException("Failed to create user: " + e.getMessage());
        }
    }
}

4. Глобальная обработка исключений (@ControllerAdvice)

@RestControllerAdvice
@Slf4j
public class GlobalExceptionHandler {
    
    @ExceptionHandler(UserNotFoundException.class)
    public ResponseEntity<ErrorResponse> handleUserNotFound(
            UserNotFoundException ex, HttpServletRequest request) {
        ErrorResponse error = ErrorResponse.builder()
            .timestamp(LocalDateTime.now())
            .status(HttpStatus.NOT_FOUND.value())
            .error("Not Found")
            .message(ex.getMessage())
            .path(request.getRequestURI())
            .errorCode(ex.getErrorCode())
            .build();
        
        log.warn("User not found: {}", ex.getMessage());
        return ResponseEntity.status(HttpStatus.NOT_FOUND).body(error);
    }
    
    @ExceptionHandler(InvalidUserDataException.class)
    public ResponseEntity<ErrorResponse> handleInvalidData(
            InvalidUserDataException ex, HttpServletRequest request) {
        ErrorResponse error = ErrorResponse.builder()
            .timestamp(LocalDateTime.now())
            .status(HttpStatus.BAD_REQUEST.value())
            .error("Bad Request")
            .message(ex.getMessage())
            .path(request.getRequestURI())
            .errorCode(ex.getErrorCode())
            .build();
        
        log.warn("Invalid data provided: {}", ex.getMessage());
        return ResponseEntity.status(HttpStatus.BAD_REQUEST).body(error);
    }
    
    @ExceptionHandler(InsufficientFundsException.class)
    public ResponseEntity<ErrorResponse> handleInsufficientFunds(
            InsufficientFundsException ex, HttpServletRequest request) {
        ErrorResponse error = ErrorResponse.builder()
            .timestamp(LocalDateTime.now())
            .status(HttpStatus.PAYMENT_REQUIRED.value())
            .error("Payment Required")
            .message(ex.getMessage())
            .path(request.getRequestURI())
            .errorCode(ex.getErrorCode())
            .build();
        
        log.warn("Insufficient funds: {}", ex.getMessage());
        return ResponseEntity.status(HttpStatus.PAYMENT_REQUIRED).body(error);
    }
    
    @ExceptionHandler(Exception.class)
    public ResponseEntity<ErrorResponse> handleGenericException(
            Exception ex, HttpServletRequest request) {
        ErrorResponse error = ErrorResponse.builder()
            .timestamp(LocalDateTime.now())
            .status(HttpStatus.INTERNAL_SERVER_ERROR.value())
            .error("Internal Server Error")
            .message("An unexpected error occurred")
            .path(request.getRequestURI())
            .errorCode("INTERNAL_ERROR")
            .build();
        
        log.error("Unexpected error", ex);
        return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body(error);
    }
}

5. ErrorResponse DTO

@Data
@Builder
public class ErrorResponse {
    private LocalDateTime timestamp;
    private int status;
    private String error;
    private String message;
    private String path;
    private String errorCode;
    private List<FieldError> fieldErrors; // для валидации
    
    @Data
    public static class FieldError {
        private String field;
        private String rejectedValue;
        private String message;
    }
}

6. Использование @Transactional с обработкой исключений

@Service
public class PaymentService {
    
    @Autowired
    private PaymentRepository paymentRepository;
    
    @Autowired
    private WalletService walletService;
    
    @Transactional(rollbackFor = Exception.class)
    public Payment processPayment(PaymentRequest request) {
        try {
            // Проверка баланса
            Wallet wallet = walletService.getWallet(request.getUserId());
            if (wallet.getBalance().compareTo(request.getAmount()) < 0) {
                throw new InsufficientFundsException(
                    request.getAmount(),
                    wallet.getBalance()
                );
            }
            
            // Обработка платежа
            Payment payment = new Payment(request);
            walletService.debit(request.getUserId(), request.getAmount());
            return paymentRepository.save(payment);
            
        } catch (InsufficientFundsException e) {
            // Специфичная обработка
            throw e; // Пробросим дальше
        } catch (Exception e) {
            // Неожиданная ошибка
            log.error("Payment processing failed", e);
            throw new ApplicationException("Failed to process payment", "PAYMENT_FAILED", e);
        }
    }
}

7. Try-Catch с finally для ресурсов

Старый способ (не использовать):

InputStream is = null;
try {
    is = new FileInputStream("file.txt");
    // работа с файлом
} catch (IOException e) {
    log.error("Error reading file", e);
} finally {
    if (is != null) {
        try {
            is.close();
        } catch (IOException e) {
            log.error("Error closing stream", e);
        }
    }
}

Правильный способ (try-with-resources):

try (InputStream is = new FileInputStream("file.txt")) {
    // работа с файлом
    // Ресурс автоматически закроется
} catch (IOException e) {
    log.error("Error reading file", e);
}

8. Логирование исключений

Правильная стратегия:

@Service
public class LoggingService {
    
    // ❌ Не делай так:
    public void badExample() {
        try {
            // код
        } catch (Exception e) {
            e.printStackTrace(); // Плохо! Печатает в stderr
        }
    }
    
    // ❌ Не делай так:
    public void alsoBad() {
        try {
            // код
        } catch (Exception e) {
            System.out.println("Error: " + e.getMessage()); // Теряем stack trace
        }
    }
    
    // ✅ Правильно:
    public void goodExample() {
        try {
            // код
        } catch (SpecificException e) {
            log.warn("Expected error occurred", e); // warn + исключение
        } catch (Exception e) {
            log.error("Unexpected error", e); // error + исключение
        }
    }
}

9. Checked vs Unchecked исключения

// ❌ Плохо (checked exception)
public class BadService {
    public void doSomething() throws IOException, SQLException, ParseException {
        // много checked exceptions в сигнатуре
    }
}

// ✅ Правильно (unchecked exceptions)
public class GoodService {
    public void doSomething() {
        try {
            // код
        } catch (IOException e) {
            throw new ApplicationException("IO failed", "IO_ERROR", e);
        } catch (SQLException e) {
            throw new ApplicationException("Database failed", "DB_ERROR", e);
        }
    }
}

10. Optional вместо исключений для отсутствующих значений

Менее идиоматично:

public User findUser(Long id) throws UserNotFoundException {
    return userRepository.findById(id)
        .orElseThrow(() -> new UserNotFoundException(id));
}

Более идиоматично:

public Optional<User> findUser(Long id) {
    return userRepository.findById(id);
}

// Использование:
service.findUser(1)
    .ifPresentOrElse(
        user -> System.out.println("Found: " + user),
        () -> System.out.println("Not found")
    );

// Или:
User user = service.findUser(1)
    .orElseThrow(() -> new UserNotFoundException(1));

Best Practices Summary

  1. ✅ Создавай иерархию custom exceptions
  2. ✅ Используй @ControllerAdvice для глобальной обработки
  3. ✅ Логируй с полным stack trace (log.error(..., exception))
  4. ✅ Переходи от checked к unchecked exceptions
  5. ✅ Используй try-with-resources для автоматического закрытия ресурсов
  6. ✅ Не скрывай исключения (catch и ничего не делать — плохо)
  7. ✅ Используй Optional для значений которых может не быть
  8. ✅ Всегда включай причину исключения (cause) при переброске
  9. ✅ Различай warn (ожидаемые ошибки) и error (неожиданные)
  10. ✅ Включай достаточно информации в сообщение об ошибке для диагностики

Правильная обработка исключений делает код более надёжным, понятным и простым в отладке.