W3docs

Подключение 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
PostgreSQLjdbc:postgresql://localhost:5432/shop
MySQLjdbc: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 — без необходимости иметь живую базу данных.

java— editable, runs on the server

Что следует вынести из этого запуска:

  • URL не является непрозрачным — это структурированные данные. getConnection разбирает именно эти части: субпротокол выбирает драйвер, а хост/порт/база данных указывают этому драйверу, куда подключаться. Прочитать URL вслух — это самый быстрый способ отладить ошибки «неправильная база данных».
  • Строка запроса (?ssl=true&applicationName=reports) содержит параметры, специфичные для драйвера. Те же настройки могут передаваться в URL или в объекте Properties — оба варианта доходят до драйвера, и вы комбинируете их по вкусу.
  • Учётные данные должны находиться в Properties (или в конфигурации DataSource), а не жёстко закодированы в строке URL, которую вы логируете. Именно по этой причине пример маскирует пароль на выходе — никогда не логируйте учётные данные.
  • connectTimeout — это реальное свойство драйвера PostgreSQL. Настройка осуществляется через эти пары ключ/значение, именно поэтому вам редко нужно создавать подклассы: конфигурация, а не код, определяет поведение соединения.
  • Программа работала без базы данных, потому что построение аргументов для getConnection — это чистая работа со строками. Дорогостоящая часть — сокет и серверный сеанс — происходит только при самом вызове getConnection, поэтому его и откладывают и закрывают быстро.

Практика

Практика
Почему JDBC Connection почти всегда следует получать внутри оператора try-with-resources?
Почему JDBC Connection почти всегда следует получать внутри оператора try-with-resources?
Was this page helpful?