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

Как обработать SQLException

1.0 Junior🔥 161 комментариев
#Базы данных и SQL#Основы Java

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

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

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

# Как обработать SQLException в Java

1. Базовая обработка SQLException

import java.sql.*;

public class DatabaseExample {
    public void queryDatabase() {
        String query = "SELECT * FROM users WHERE id = 1";
        
        try {
            Connection connection = DriverManager.getConnection(
                "jdbc:mysql://localhost:3306/mydb",
                "username",
                "password"
            );
            
            Statement stmt = connection.createStatement();
            ResultSet rs = stmt.executeQuery(query);
            
            while (rs.next()) {
                System.out.println("User: " + rs.getString("name"));
            }
            
            rs.close();
            stmt.close();
            connection.close();
            
        } catch (SQLException e) {
            // ❌ Плохо — просто печатаем стек
            e.printStackTrace();
        }
    }
}

2. Правильная обработка SQLException

public class DatabaseService {
    private static final Logger logger = LoggerFactory.getLogger(DatabaseService.class);
    
    public List<User> getUsers() {
        String query = "SELECT id, name, email FROM users";
        List<User> users = new ArrayList<>();
        
        try {
            Connection connection = getConnection();
            Statement stmt = connection.createStatement();
            ResultSet rs = stmt.executeQuery(query);
            
            while (rs.next()) {
                User user = new User(
                    rs.getInt("id"),
                    rs.getString("name"),
                    rs.getString("email")
                );
                users.add(user);
            }
            
            rs.close();
            stmt.close();
            connection.close();
            
        } catch (SQLException e) {
            // ✅ Логирование + информативная ошибка
            logger.error("Ошибка при получении пользователей из БД", e);
            
            // Либо пробросим дальше
            throw new DataAccessException("Не удалось получить список пользователей", e);
        }
        
        return users;
    }
}

3. Определение типа ошибки по error code

public void updateUser(User user) {
    String query = "UPDATE users SET name = ?, email = ? WHERE id = ?";
    
    try {
        Connection connection = getConnection();
        PreparedStatement pstmt = connection.prepareStatement(query);
        pstmt.setString(1, user.getName());
        pstmt.setString(2, user.getEmail());
        pstmt.setInt(3, user.getId());
        pstmt.executeUpdate();
        
        pstmt.close();
        connection.close();
        
    } catch (SQLException e) {
        // Проверяем тип ошибки по error code
        int errorCode = e.getErrorCode();
        String sqlState = e.getSQLState();
        
        switch (errorCode) {
            case 1062: // MySQL duplicate key error
                logger.warn("Пользователь с этим email уже существует");
                throw new DuplicateKeyException("Email уже используется", e);
                
            case 1452: // Foreign key constraint violation
                logger.error("Нарушение внешнего ключа");
                throw new ForeignKeyViolationException("Неправильная ссылка на другую таблицу", e);
                
            case 1406: // Data too long for column
                logger.error("Данные слишком длинные для столбца");
                throw new DataTooLongException("Значение превышает допустимую длину", e);
                
            default:
                logger.error("Неизвестная SQL ошибка: " + sqlState, e);
                throw new DataAccessException("Ошибка при обновлении пользователя", e);
        }
    }
}

4. Try-with-resources (автоматическое закрытие ресурсов)

// ✅ Правильно — ресурсы закроются автоматически
public List<User> getAllUsers() {
    String query = "SELECT * FROM users";
    List<User> users = new ArrayList<>();
    
    try (
        Connection connection = getConnection();
        Statement stmt = connection.createStatement();
        ResultSet rs = stmt.executeQuery(query)
    ) {
        while (rs.next()) {
            users.add(new User(
                rs.getInt("id"),
                rs.getString("name"),
                rs.getString("email")
            ));
        }
    } catch (SQLException e) {
        logger.error("Ошибка при получении пользователей", e);
        throw new DataAccessException("Не удалось получить пользователей", e);
    }
    // Connection, Statement и ResultSet закроются автоматически!
    
    return users;
}

5. Иерархия SQLException

public void handleSQLException() {
    try {
        // Какой-то SQL код
    } catch (SQLRecoverableException e) {
        // ❌ Временная ошибка — попробуй повторить
        logger.warn("Временная ошибка БД, переподключаюсь...", e);
        retryConnection();
        
    } catch (SQLNonTransientException e) {
        // ❌ Постоянная ошибка (syntax error, constraint violation)
        logger.error("Неправимая ошибка SQL", e);
        throw new BadRequestException("Неправильный запрос", e);
        
    } catch (SQLTimeoutException e) {
        // ❌ Timeout
        logger.error("Timeout при выполнении запроса", e);
        throw new TimeoutException("Запрос занял слишком много времени", e);
        
    } catch (SQLException e) {
        // Прочие SQL ошибки
        logger.error("SQL ошибка: " + e.getSQLState(), e);
        throw new DataAccessException("Ошибка БД", e);
    }
}

6. Кастомная обработка в Spring

// Spring имеет свой механизм преобразования SQLException
@Repository
public class UserRepository {
    @Autowired
    private JdbcTemplate jdbcTemplate;
    
    public User findById(int id) {
        try {
            String sql = "SELECT * FROM users WHERE id = ?";
            return jdbcTemplate.queryForObject(
                sql,
                new UserRowMapper(),
                id
            );
        } catch (EmptyResultDataAccessException e) {
            // Spring преобразует SQLException в EmptyResultDataAccessException
            throw new UserNotFoundException("Пользователь не найден");
        } catch (DataAccessException e) {
            // DataAccessException — обёртка над SQLException
            logger.error("Ошибка при получении пользователя", e);
            throw new SystemException("Ошибка БД", e);
        }
    }
}

7. Обработка нескольких операций

public void transferMoney(int fromId, int toId, BigDecimal amount) {
    String withdrawSql = "UPDATE accounts SET balance = balance - ? WHERE id = ?";
    String depositSql = "UPDATE accounts SET balance = balance + ? WHERE id = ?";
    
    try (
        Connection connection = getConnection()
    ) {
        // Отключаем auto-commit для управления транзакцией
        connection.setAutoCommit(false);
        
        try (
            PreparedStatement withdrawStmt = connection.prepareStatement(withdrawSql);
            PreparedStatement depositStmt = connection.prepareStatement(depositSql)
        ) {
            // Снять со счёта
            withdrawStmt.setBigDecimal(1, amount);
            withdrawStmt.setInt(2, fromId);
            int rowsAffected = withdrawStmt.executeUpdate();
            
            if (rowsAffected == 0) {
                throw new SQLException("Счёт источник не найден");
            }
            
            // Добавить на счёт
            depositStmt.setBigDecimal(1, amount);
            depositStmt.setInt(2, toId);
            rowsAffected = depositStmt.executeUpdate();
            
            if (rowsAffected == 0) {
                throw new SQLException("Счёт получатель не найден");
            }
            
            // Коммитим если всё успешно
            connection.commit();
            
        } catch (SQLException e) {
            // Откатываем транзакцию при ошибке
            try {
                connection.rollback();
                logger.info("Транзакция отменена");
            } catch (SQLException rollbackEx) {
                logger.error("Ошибка при откате транзакции", rollbackEx);
            }
            
            logger.error("Ошибка при переводе денег", e);
            throw new TransferException("Не удалось перевести деньги", e);
        }
        
    } catch (SQLException e) {
        logger.error("Ошибка подключения", e);
        throw new ConnectionException("Ошибка подключения к БД", e);
    }
}

8. Retry логика

public void executeWithRetry(Runnable task, int maxRetries) {
    int attempts = 0;
    
    while (attempts < maxRetries) {
        try {
            task.run();
            return; // Успех
            
        } catch (SQLRecoverableException e) {
            attempts++;
            if (attempts >= maxRetries) {
                logger.error("Не удалось выполнить запрос после " + maxRetries + " попыток", e);
                throw new DataAccessException("Критическая ошибка БД", e);
            }
            
            // Ждём перед повторной попыткой (exponential backoff)
            long delayMs = (long) Math.pow(2, attempts) * 1000;
            logger.warn("Повторная попытка через " + delayMs + "ms");
            
            try {
                Thread.sleep(delayMs);
            } catch (InterruptedException ie) {
                Thread.currentThread().interrupt();
                throw new RuntimeException("Прервана ожидание повтора", ie);
            }
            
        } catch (SQLNonTransientException e) {
            // Не повторяем для постоянных ошибок
            logger.error("Постоянная SQL ошибка", e);
            throw new BadRequestException("Ошибка в запросе", e);
        }
    }
}

9. Логирование правильным образом

public void processUserData(int userId) {
    String query = "SELECT * FROM users WHERE id = ?";
    
    try (
        Connection connection = getConnection();
        PreparedStatement pstmt = connection.prepareStatement(query)
    ) {
        pstmt.setInt(1, userId);
        ResultSet rs = pstmt.executeQuery();
        
        if (rs.next()) {
            processUser(rs);
        }
        
    } catch (SQLException e) {
        // ✅ Логируй полную информацию для диагностики
        logger.error("Ошибка при обработке пользователя userId={}, query={}, sqlState={}, errorCode={}",
                    userId, query, e.getSQLState(), e.getErrorCode(), e);
        
        // Информативное исключение для клиента
        throw new DataAccessException("Не удалось получить данные пользователя", e);
    }
}

10. Лучшие практики

// ✅ Используй try-with-resources
try (
    Connection conn = getConnection();
    PreparedStatement pstmt = conn.prepareStatement(sql)
) {
    // код
} catch (SQLException e) {
    // обработка
}

// ✅ Используй PreparedStatement (защита от SQL injection)
PreparedStatement pstmt = conn.prepareStatement("SELECT * FROM users WHERE id = ?");
pstmt.setInt(1, userId);

// ❌ НЕ используй String concatenation
Statement stmt = conn.createStatement();
stmt.executeQuery("SELECT * FROM users WHERE id = " + userId); // Уязвимо!

// ✅ Логируй с Logger (Slf4j, Log4j)
logger.error("Ошибка SQL", e);

// ❌ НЕ используй e.printStackTrace()
e.printStackTrace(); // Плохая практика

// ✅ Проверяй error code для понимания ошибки
int errorCode = e.getErrorCode();
String sqlState = e.getSQLState();

Резюме: правильная обработка SQLException

  1. Try-with-resources для автоматического закрытия
  2. Logger вместо printStackTrace()
  3. Специфичные исключения (DuplicateKeyException, etc)
  4. Error code проверка для различных типов ошибок
  5. Retry логика для восстанавливаемых ошибок
  6. PreparedStatement для защиты от SQL injection
  7. Транзакции для многоступенчатых операций
  8. Информативные сообщения об ошибках для клиента

В production коде всегда обрабатывай SQLException правильно — это спасает от потери данных и упрощает отладку!

Как обработать SQLException | PrepBro