← Назад к вопросам
Как обработать 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
- Try-with-resources для автоматического закрытия
- Logger вместо printStackTrace()
- Специфичные исключения (DuplicateKeyException, etc)
- Error code проверка для различных типов ошибок
- Retry логика для восстанавливаемых ошибок
- PreparedStatement для защиты от SQL injection
- Транзакции для многоступенчатых операций
- Информативные сообщения об ошибках для клиента
В production коде всегда обрабатывай SQLException правильно — это спасает от потери данных и упрощает отладку!