Подключение Java JDBC
Открывайте соединения с базой данных и управляйте ими с помощью интерфейса Connection — открытие, закрытие и настройка.
Connection — это активный сеанс работы с базой данных. Это объект, который возвращает DriverManager (или DataSource), и он является фабрикой для всего остального — операторов, транзакций, точек сохранения, метаданных. Соединение — это также редкий и дорогостоящий ресурс: каждое открытое соединение занимает сокет и серверный сеанс, поэтому главное правило — открывать поздно, закрывать быстро.
В этой главе рассматривается, как построить URL подключения, три способа открыть Connection, почему try-with-resources обязателен, настройки сеанса, которые можно задать, и ошибки, которые возникнут при неправильной конфигурации. Предполагается, что вы уже загрузили драйвер — см. Драйверы JDBC и Введение в JDBC для общего понимания.
URL подключения
Всё, что нужно DriverManager для нахождения и подключения к базе данных, закодировано в URL:
jdbc:<subprotocol>://<host>:<port>/<database>?<key=value&...>Например jdbc:postgresql://db.internal:5432/shop?ssl=true. Префикс jdbc: обязателен; субпротокол выбирает драйвер; остальное зависит от производителя, но обычно это хост, порт, база данных и строка запроса с параметрами настройки. Несколько реальных URL для распознавания:
| База данных | Пример URL |
|---|---|
| PostgreSQL | jdbc:postgresql://localhost:5432/shop |
| MySQL | jdbc:mysql://localhost:3306/shop?useSSL=true |
| H2 (в памяти) | jdbc:h2:mem:testdb |
| SQLite (файл) | jdbc:sqlite:/data/shop.db |
Субпротокол (postgresql, mysql, h2, sqlite) — это то, как DriverManager определяет, какой зарегистрированный драйвер должен обработать URL, поэтому его правильное указание сообщает JDBC, к какой базе данных вы обращаетесь.
Три способа открыть соединение
// 1. URL with credentials as arguments
Connection a = DriverManager.getConnection(url, "app", "secret");
// 2. URL with a Properties bag (user, password, plus driver-specific keys)
Properties props = new Properties();
props.setProperty("user", "app");
props.setProperty("password", "secret");
props.setProperty("connectTimeout", "10");
Connection b = DriverManager.getConnection(url, props);
// 3. From a pooled DataSource (preferred for applications)
Connection c = dataSource.getConnection();DriverManager vs. DataSource
DriverManager.getConnection(...) каждый раз открывает совершенно новое физическое соединение, а затем разрывает его при закрытии. Это рукопожатие — поиск DNS, TCP, TLS, аутентификация — занимает десятки миллисекунд, что приемлемо для скрипта, но губительно для сервера, обслуживающего множество запросов.
DataSource, подкреплённый пулом соединений (HikariCP, Apache DBCP или предоставленный сервером приложений), поддерживает набор открытых физических соединений и выдаёт их по запросу. Вызов getConnection() берёт одно из них в аренду; вызов close() возвращает его в пул, а не закрывает по-настоящему. Для любого долго работающего приложения предпочтительнее использовать пулированный DataSource; прибегайте к DriverManager только в небольших утилитах, тестах и примерах.
Всегда закрывайте — используйте try-with-resources
Connection, Statement и ResultSet реализуют AutoCloseable. Объявление их в заголовке try-with-resources гарантирует закрытие в обратном порядке, даже если возникнет исключение — это важнейшая привычка в JDBC:
try (Connection conn = DriverManager.getConnection(url, "app", "secret")) {
// use conn...
} // conn.close() runs here automatically, even on exceptionУтечка соединений (забывание закрыть) истощает пул и в итоге зависает всё приложение — классический сбой в производственной среде. Обратите внимание, что открытие Connection бросает проверяемое исключение SQLException, поэтому вызов всегда находится внутри try (или метода, объявляющего throws SQLException).
Получив соединение, вы используете его для создания Statement и PreparedStatement — они должны находиться в том же заголовке try-with-resources, чтобы закрыться до закрытия соединения:
try (Connection conn = dataSource.getConnection();
PreparedStatement ps = conn.prepareStatement("SELECT name FROM users WHERE id = ?")) {
ps.setInt(1, 42);
// ...read the ResultSet...
} // ps closes first, then conn — reverse declaration orderНастройка соединения
После открытия соединение содержит настройки уровня сеанса:
| Метод | Что делает |
|---|---|
setAutoCommit(false) | Начинает ручную транзакцию; затем вы вызываете commit() или rollback() самостоятельно. См. Транзакции JDBC. |
setTransactionIsolation(...) | Устанавливает уровень изоляции (например, TRANSACTION_READ_COMMITTED). |
setReadOnly(true) | Подсказка о том, что соединение не выполняет записи; некоторые драйверы оптимизируются под это. |
setSchema(...) / setCatalog(...) | Выбирает пространство имён, в котором выполняются запросы. |
isValid(timeout) | Возвращает true, если соединение ещё живо; пулы используют это для отбраковки мёртвых соединений. |
Эти настройки действуют на уровне сеанса, поэтому они сбрасываются при закрытии реального соединения — но в пуле взятое в аренду соединение может сохранять настройки, оставленные предыдущим пользователем. Именно поэтому пулы сбрасывают autoCommit и уровень изоляции при возврате, и именно поэтому следует задавать нужные настройки явно, а не полагаться на значения по умолчанию.
Распространённые ошибки подключения
При сбое подключения вы почти всегда получите SQLException; сообщение указывает, на каком уровне произошёл сбой:
No suitable driver found for ...— класс драйвера никогда не был загружен, или субпротокол в URL написан с ошибкой, поэтому ни один зарегистрированный драйвер не берётся за него. Исправьте зависимость или URL (см. Драйверы JDBC).Connection refused— на этом хосте/порте ничего не прослушивается: база данных недоступна, или хост/порт в URL неверны.- Ошибка аутентификации /
password authentication failed— неверныйuserилиpassword, или у пользователя нет прав на эту базу данных. - Таймаут соединения — хост недостижим (брандмауэр, неправильная сеть). Установите
connectTimeout, чтобы вызов быстро завершился с ошибкой, а не завис.
Разбор примера: анатомия URL подключения
Эта программа разбирает реалистичный URL JDBC на составные части и строит объект Properties, который вы передали бы вместе с ним — два аргумента, нужных getConnection — без необходимости иметь живую базу данных.
Что следует вынести из этого запуска:
- URL не является непрозрачным — это структурированные данные.
getConnectionразбирает именно эти части: субпротокол выбирает драйвер, а хост/порт/база данных указывают этому драйверу, куда подключаться. Прочитать URL вслух — это самый быстрый способ отладить ошибки «неправильная база данных». - Строка запроса (
?ssl=true&applicationName=reports) содержит параметры, специфичные для драйвера. Те же настройки могут передаваться в URL или в объектеProperties— оба варианта доходят до драйвера, и вы комбинируете их по вкусу. - Учётные данные должны находиться в
Properties(или в конфигурацииDataSource), а не жёстко закодированы в строке URL, которую вы логируете. Именно по этой причине пример маскирует пароль на выходе — никогда не логируйте учётные данные. connectTimeout— это реальное свойство драйвера PostgreSQL. Настройка осуществляется через эти пары ключ/значение, именно поэтому вам редко нужно создавать подклассы: конфигурация, а не код, определяет поведение соединения.- Программа работала без базы данных, потому что построение аргументов для
getConnection— это чистая работа со строками. Дорогостоящая часть — сокет и серверный сеанс — происходит только при самом вызовеgetConnection, поэтому его и откладывают и закрывают быстро.