Перебросьте SQLException как доменное исключение, не потеряв исходную причину
Репозиторий ниже выпускает сырое SQLException наружу к вызывающему. Оберните его в доменный тип UserLoadException, чтобы вызывающие видели один словарь, — но не выбросив при этом исходный сбой.
Ограничения:
- исходное
SQLExceptionдолжно оставаться доступным черезgetCause() - в напечатанном стектрейсе оно должно быть видно под
Caused by: UserLoadExceptionнепроверяемое, аfindUserне должен объявлятьthrows
class UserLoadException extends RuntimeException {
// ваш код здесь
}
User findUser(long id) {
try {
return jdbc.queryForUser(id); // throws SQLException
} catch (SQLException e) {
// ваш код здесь
}
}
Допишите реализацию.
Дайте своему типу конструктор (String, Throwable), который передаёт аргументы в super(message, cause), и перебросьте через throw new UserLoadException(message, e);. Сам Throwable хранит эту причину, поэтому getCause() вернёт SQLException, а printStackTrace напечатает его кадры под Caused by:. Передача одного лишь e.getMessage() в конструктор только с сообщением сохраняет текст и молча выбрасывает исходный стектрейс.
- ✗Перебрасывать только с
e.getMessage(), теряя исходный стектрейс - ✗Считать, что причина связывается сама, раз
throwстоит внутриcatch - ✗Путать
addSuppressedсinitCause— подавление это не цепочка
- →Когда стоит применить
initCause()вместо конструктора, принимающего причину? - →Насколько глубокой бывает цепочка причин и как обойти её программно?
Решение
class UserLoadException extends RuntimeException {
UserLoadException(String message, Throwable cause) {
super(message, cause);
}
}
User findUser(long id) {
try {
return jdbc.queryForUser(id);
} catch (SQLException e) {
throw new UserLoadException("cannot load user " + id, e);
}
}
Что здесь делает super(message, cause). Поле cause живёт в самом Throwable, а не в вашем классе. Конструктор Throwable(String, Throwable) записывает его один раз; дальше getCause() возвращает исходное SQLException, а printStackTrace печатает его кадры отдельной секцией:
UserLoadException: cannot load user 42
at UserRepository.findUser(UserRepository.java:17)
...
Caused by: java.sql.SQLException: connection reset
at com.mysql.cj.jdbc.ConnectionImpl.execSQL(ConnectionImpl.java:...)
...
Антипаттерн. throw new UserLoadException(e.getMessage()) оставляет только текст. Кадры SQLException — те самые, что показывают, какой запрос и какой драйвер упали, — исчезают навсегда, и в инциденте останется лишь строка cannot load user 42 без единой зацепки.
Если объект исключения уже создан (например, фабрикой), причину можно доставить один раз через initCause(e) — повторный вызов бросит IllegalStateException.
Не путайте с подавлением: addSuppressed — про второстепенные сбои при закрытии ресурсов, а cause — про «из-за чего произошло вот это».