Почему Connection нужно закрывать
Достаточно частая проблема у разработчиков, использующих JDBC - это не закрытый connection в базу данных.
Давайте рассмотрим пример, в котором создается 1000 соединений с БД, внутри каждого соединения выполняется какая-та полезная работа ( в нашем случае создание и удаление таблицы), но никакой обработки завершения работы с БД нет.
Заодно вы сможете заметить, что открытие 1000 соединений с вашей любимой БД - удовольствие достаточно затратное по времени и памяти (попробуйте замерить разницу до выполнения кода и после в качестве упражнения).
Пример кода с github: TODO
public static void main(String[] args) {
Logger log = LogManager.getRootLogger();
try {
for (int i = 0; i < 1_000; i++) {
Connection connection = getConnection();
Statement st = connection.createStatement();
st.execute("CREATE TABLE IF NOT EXISTS users (id INT NOT NULL AUTO_INCREMENT, name VARCHAR(30), PRIMARY KEY (id))");
st.execute("DROP TABLE users");
log.info("Connection # " + i + " was created");
}
} catch (SQLException e) {
e.printStackTrace();
}
}
private static Connection getConnection() throws SQLException {
return DriverManager.getConnection(URL, USER_NAME, PASSWORD);
}
Если вы просто запустите этот код и подождете, то по достижении лимита на количество соединений вы получите следующее сообщение:
Если по какой-то причине подобная исключительная ситуация не возникает, попробуйте увеличить количество итераций в цикле или же уменьшить верхний лимит на соединения с БД в настройка самой БД (для MySQL эта настройка называется max_connections и легко меняется через Workbench)
Вся проблема в исчерпании ресурсов: мы сделали какие-то дела, но не обратились к БД с просьбой закрыть двери за гостями.
Так давайте же закроем за собой двери!
Достаточно добавить connection.close() в конце блока кода.
А давайте дадим меньше памяти?
Попробуйте запустить "проблемное" приложение с 10 Мб хипа и вы увидите странную картину: приложение не падает на лимите подключений. Напротив, с минимальным запасом памяти оно умудряется открыть всю тысячу соединений, выполнить работу и спокойно завершиться.
В чем же дело?
Не буду долго вас томить, да вы и сами догадались, что тут замешан сборщик мусора!
Дело в том, что мы объявили переменную connection локально, внутри цикла. Когда мы переходим на новую итерацию цикла, по ссылке становится доступен новый объект, а старый пригоден для сборки мусора.
При таком маленьком хипе сборка происходит очень часто. Специальная подобласть хипа под названием Old быстро наполняется, в Eden/S1/S2 вообще никто долго не задерживается (их размеры 2/1/1 Мб соотвественно), сборки мусора происходят часто, объекты отправляются в мир иной, MySQL отправляет свои коннекты к праотцам.
Никогда так не делайте в проде. Некоторые базы данных, да и старые версии MySQL не удаляют в подобных случаях соединения на свой стороне. Вы легко получите трудноустранимую утечку памяти, решаемую только перезагрузкой сервера.
Урезанная память нас подстраховала, конечно. Но лучше не полагаться на особенности функционирования JVM, а быть предельно корректным при работе с ресурсами, особенно если вы все еще пишите на Java 6.