Рубрики
Без рубрики

Введение в JDBC

Краткое и практическое введение в JDBC на Java.

Автор оригинала: baeldung.

1. Обзор

В этой статье мы рассмотрим JDBC (Подключение к базе данных Java), который представляет собой API для подключения и выполнения запросов к базе данных.

JDBC может работать с любой базой данных при условии наличия надлежащих драйверов.

2. Драйверы JDBC

Драйвер JDBC-это реализация API JDBC, используемая для подключения к определенному типу базы данных. Существует несколько типов драйверов JDBC:

  • Тип 1 – содержит сопоставление с другим API доступа к данным; примером этого является драйвер JDBC-ODBC
  • Тип 2 – это реализация, которая использует клиентские библиотеки целевой базы данных; также называется драйвером собственного API
  • Тип 3 – использует промежуточное программное обеспечение для преобразования вызовов JDBC в вызовы, относящиеся к конкретной базе данных; также известен как драйвер сетевого протокола
  • Тип 4 – прямое подключение к базе данных путем преобразования вызовов JDBC в вызовы, относящиеся к конкретной базе данных; известные как драйверы протокола базы данных или тонкие драйверы,

Наиболее часто используемый тип-тип 4, так как он имеет то преимущество, что он не зависит от платформы . Прямое подключение к серверу баз данных обеспечивает более высокую производительность по сравнению с другими типами. Недостатком этого типа драйверов является то, что он зависит от конкретной базы данных-учитывая, что каждая база данных имеет свой собственный конкретный протокол.

3. Подключение к базе данных

Чтобы подключиться к базе данных, нам просто нужно инициализировать драйвер и открыть соединение с базой данных.

3.1. Регистрация водителя

Для нашего примера мы будем использовать драйвер протокола базы данных типа 4.

Поскольку мы используем базу данных MySQL, нам нужна зависимость mysql-connector-java :


    mysql
    mysql-connector-java
    6.0.6

Далее давайте зарегистрируем драйвер с помощью метода Class.forName () , который динамически загружает класс драйвера:

Class.forName("com.mysql.cj.jdbc.Driver");

В более старых версиях JDBC перед получением соединения нам сначала нужно было инициализировать драйвер JDBC, вызвав метод Class.forName . Начиная с JDBC 4.0 , все драйверы, найденные в пути к классам, автоматически загружаются . Поэтому нам не понадобится эта Class.forName часть в современных средах.

3.2. Создание соединения

Чтобы открыть соединение, мы можем использовать метод getConnection() класса DriverManager . Для этого метода требуется URL-адрес подключения Строка параметр:

try (Connection con = DriverManager
  .getConnection("jdbc:mysql://localhost:3306/myDb", "user1", "pass")) {
    // use con here
}

Поскольку Соединение является автоклавируемым ресурсом, мы должны использовать его внутри попробуйте с ресурсами |/блок .

Синтаксис URL-адреса подключения зависит от типа используемой базы данных. Давайте рассмотрим несколько примеров:

jdbc:mysql://localhost:3306/myDb?user=user1&password=pass
jdbc:postgresql://localhost/myDb
jdbc:hsqldb:mem:myDb

Чтобы подключиться к указанной myDb базе данных, нам нужно будет создать базу данных и пользователя, а также добавить необходимый доступ:

CREATE DATABASE myDb;
CREATE USER 'user1' IDENTIFIED BY 'pass';
GRANT ALL on myDb.* TO 'user1';

4. Выполнение инструкций SQL

В инструкциях отправки SQL в базу данных мы можем использовать экземпляры типа Statement , PreparedStatement, или CallableStatement, которые мы можем получить с помощью объекта Connection .

4.1. Заявление

Интерфейс Оператор содержит основные функции для выполнения команд SQL.

Во-первых, давайте создадим Оператор объект:

try (Statement stmt = con.createStatement()) {
    // use stmt here
}

Опять же, мы должны работать с Оператором s внутри блока try-with-resources для автоматического управления ресурсами.

В любом случае, выполнение инструкций SQL может быть выполнено с помощью трех методов:

  • ExecuteQuery() для ВЫБОРА инструкций
  • executeUpdate() для обновления данных или структуры базы данных
  • execute() может использоваться в обоих случаях, описанных выше, когда результат неизвестен

Давайте используем метод execute() для добавления таблицы студенты в нашу базу данных:

String tableSql = "CREATE TABLE IF NOT EXISTS employees" 
  + "(emp_id int PRIMARY KEY AUTO_INCREMENT, name varchar(30),"
  + "position varchar(30), salary double)";
stmt.execute(tableSql);

При использовании выполнить() способ обновления данных, затем stmt.getUpdateCount() метод возвращает количество затронутых строк.

Если результат равен 0, то либо строки не были затронуты, либо это была команда обновления структуры базы данных.

Если значение равно -1, то команда была запросом SELECT; затем мы можем получить результат с помощью stmt.getResultSet() .

Далее давайте добавим запись в нашу таблицу с помощью метода executeUpdate() :

String insertSql = "INSERT INTO employees(name, position, salary)"
  + " VALUES('john', 'developer', 2000)";
stmt.executeUpdate(insertSql);

Метод возвращает количество затронутых строк для команды, обновляющей строки, или 0 для команды, обновляющей структуру базы данных.

Мы можем извлечь записи из таблицы с помощью метода ExecuteQuery () , который возвращает объект типа ResultSet :

String selectSql = "SELECT * FROM employees"; 
try (ResultSet resultSet = stmt.executeQuery(selectSql)) {
    // use resultSet here
}

Мы должны обязательно закрыть экземпляры ResultSet после использования. В противном случае мы можем держать базовый курсор открытым в течение гораздо более длительного периода, чем ожидалось. Для этого рекомендуется использовать блок try-with-resources , как в нашем примере выше.

4.2. Подготовленное Заявление

Объекты PreparedStatement содержат предварительно скомпилированные последовательности SQL. Они могут иметь один или несколько параметров, обозначенных знаком вопроса.

Давайте создадим Подготовленное заявление , которое обновляет записи в таблице сотрудники на основе заданных параметров:

String updatePositionSql = "UPDATE employees SET position=? WHERE emp_id=?";
try (PreparedStatement pstmt = con.prepareStatement(updatePositionSql)) {
    // use pstmt here
}

Чтобы добавить параметры в PreparedStatement , мы можем использовать простые задатчики – setX() – где X-тип параметра, а аргументы метода-порядок и значение параметра:

pstmt.setString(1, "lead developer");
pstmt.setInt(2, 1);

Оператор выполняется одним из тех же трех методов, описанных ранее: ExecuteQuery (), executeUpdate (), execute() без параметра SQL String :

int rowsAffected = pstmt.executeUpdate();

4.3. Вызываемое утверждение

Интерфейс CallableStatement позволяет вызывать хранимые процедуры.

Для создания объекта CallableStatement мы можем использовать prepareCall() метод Подключения :

String preparedSql = "{call insertEmployee(?,?,?,?)}";
try (CallableStatement cstmt = con.prepareCall(preparedSql)) {
    // use cstmt here
}

Установка значений входных параметров для хранимой процедуры выполняется так же, как в интерфейсе PreparedStatement , с использованием методов setX() :

cstmt.setString(2, "ana");
cstmt.setString(3, "tester");
cstmt.setDouble(4, 2000);

Если хранимая процедура имеет выходные параметры, нам нужно добавить их с помощью метода registerOutParameter() :

cstmt.registerOutParameter(1, Types.INTEGER);

Затем давайте выполним инструкцию и получим возвращенное значение, используя соответствующий метод getX() :

cstmt.execute();
int new_id = cstmt.getInt(1);

Например, для работы нам нужно создать хранимую процедуру в нашей базе данных MySQL:

delimiter //
CREATE PROCEDURE insertEmployee(OUT emp_id int, 
  IN emp_name varchar(30), IN position varchar(30), IN salary double) 
BEGIN
INSERT INTO employees(name, position,salary) VALUES (emp_name,position,salary);
SET emp_id = LAST_INSERT_ID();
END //
delimiter ;

Описанная выше процедура insertEmployee вставит новую запись в таблицу сотрудники с использованием заданных параметров и вернет идентификатор новой записи в параметре emp_id out.

Чтобы иметь возможность запускать хранимую процедуру с Java, пользователь подключения должен иметь доступ к метаданным хранимых процедур. Этого можно достичь, предоставив пользователю права на все хранимые процедуры во всех базах данных:

GRANT ALL ON mysql.proc TO 'user1';

В качестве альтернативы мы можем открыть соединение со свойством noAccessToProcedureBodies , установленным в true :

con = DriverManager.getConnection(
  "jdbc:mysql://localhost:3306/myDb?noAccessToProcedureBodies=true", 
  "user1", "pass");

Это проинформирует API JDBC о том, что у пользователя нет прав на чтение метаданных процедуры, чтобы он создал все параметры в качестве параметров INOUT String .

5. Анализ Результатов Запроса

После выполнения запроса результат представляется объектом ResultSet , который имеет структуру, аналогичную таблице, со строками и столбцами.

5.1. Интерфейс набора результатов

Результирующий набор использует метод next() для перехода к следующей строке.

Давайте сначала создадим класс Employee для хранения полученных записей:

public class Employee {
    private int id;
    private String name;
    private String position;
    private double salary;
 
    // standard constructor, getters, setters
}

Далее давайте пройдемся по Набору результатов и создадим объект Сотрудник для каждой записи:

String selectSql = "SELECT * FROM employees"; 
try (ResultSet resultSet = stmt.executeQuery(selectSql)) {
    List employees = new ArrayList<>(); 
    while (resultSet.next()) { 
        Employee emp = new Employee(); 
        emp.setId(resultSet.getInt("emp_id")); 
        emp.setName(resultSet.getString("name")); 
        emp.setPosition(resultSet.getString("position")); 
        emp.setSalary(resultSet.getDouble("salary")); 
        employees.add(emp); 
    }
}

Получение значения для каждой ячейки таблицы можно выполнить с помощью методов типа get X ( ), где X представляет тип данных ячейки.

Методы getX() могут использоваться с параметром int , представляющим порядок ячейки, или параметром String , представляющим имя столбца. Последний вариант предпочтительнее в случае, если мы изменим порядок столбцов в запросе.

5.2. Обновляемый набор результатов

Неявно объект ResultSet может быть пройден только вперед и не может быть изменен.

Если мы хотим использовать Набор результатов для обновления данных и перемещения их в обоих направлениях, нам необходимо создать Оператор объект с дополнительными параметрами:

stmt = con.createStatement(
  ResultSet.TYPE_SCROLL_INSENSITIVE, 
  ResultSet.CONCUR_UPDATABLE
);

Для навигации по этому типу Результирующего набора мы можем использовать один из методов:

  • first(), last(), beforeFirst(), beforeLast() – для перехода к первой или последней строке набора результатов или к строке перед этими следующий(), предыдущий()
  • – для перемещения вперед и назад в наборе результатов GetRow() –
  • для получения текущего номера строки moveToInsertRow(), moveToCurrentRow()
  • – для перехода к новой пустой строке для вставки и возврата к текущей, если в новой строке абсолютный(int строка) –
  • для перехода в указанную строку относительный(int nrRows)
  • – для перемещения курсора на заданное количество строк

Обновление набора результатов может быть выполнено с использованием методов с форматом update X () , где X-тип данных ячейки. Эти методы обновляют только объект ResultSet , а не таблицы базы данных.

Чтобы сохранить Результирующий набор изменений в базе данных, мы должны дополнительно использовать один из методов:

  • updateRow() – для сохранения изменений текущей строки в базе данных
  • insertRow(), deleteRow() – для добавления новой строки или удаления текущей из базы данных
  • refreshRow() – для обновления набора результатов с любыми изменениями в базе данных
  • cancelRowUpdates() – для отмены изменений, внесенных в текущую строку

Давайте рассмотрим пример использования некоторых из этих методов путем обновления данных в таблице сотрудник :

try (Statement updatableStmt = con.createStatement(
  ResultSet.TYPE_SCROLL_INSENSITIVE, ResultSet.CONCUR_UPDATABLE)) {
    try (ResultSet updatableResultSet = updatableStmt.executeQuery(selectSql)) {
        updatableResultSet.moveToInsertRow();
        updatableResultSet.updateString("name", "mark");
        updatableResultSet.updateString("position", "analyst");
        updatableResultSet.updateDouble("salary", 2000);
        updatableResultSet.insertRow();
    }
}

6. Анализ Метаданных

API JDBC позволяет просматривать информацию о базе данных, называемую метаданными.

6.1. DatabaseMetaData

Интерфейс DatabaseMetaData может использоваться для получения общей информации о базе данных, такой как таблицы, хранимые процедуры или диалект SQL.

Давайте быстро рассмотрим, как мы можем извлекать информацию из таблиц базы данных:

DatabaseMetaData dbmd = con.getMetaData();
ResultSet tablesResultSet = dbmd.getTables(null, null, "%", null);
while (tablesResultSet.next()) {
    LOG.info(tablesResultSet.getString("TABLE_NAME"));
}

6.2. ResultSetMetaData

Этот интерфейс можно использовать для поиска информации об определенном Результирующем наборе , такой как количество и название его столбцов:

ResultSetMetaData rsmd = rs.getMetaData();
int nrColumns = rsmd.getColumnCount();

IntStream.range(1, nrColumns).forEach(i -> {
    try {
        LOG.info(rsmd.getColumnName(i));
    } catch (SQLException e) {
        e.printStackTrace();
    }
});

7. Обработка Транзакций

По умолчанию каждая инструкция SQL фиксируется сразу после ее завершения. Однако также возможно управлять транзакциями программно .

Это может быть необходимо в тех случаях, когда мы хотим сохранить согласованность данных, например, когда мы хотим совершить транзакцию только в том случае, если предыдущая транзакция была успешно завершена.

Сначала нам нужно установить свойство autoCommit для Подключения в false , затем использовать методы commit() и rollback() для управления транзакцией.

Давайте добавим второе заявление об обновлении для столбца зарплата после обновления столбца сотрудник должность и заключим их в транзакцию. Таким образом, зарплата будет обновлена только в том случае, если должность была успешно обновлена:

String updatePositionSql = "UPDATE employees SET position=? WHERE emp_id=?";
PreparedStatement pstmt = con.prepareStatement(updatePositionSql);
pstmt.setString(1, "lead developer");
pstmt.setInt(2, 1);

String updateSalarySql = "UPDATE employees SET salary=? WHERE emp_id=?";
PreparedStatement pstmt2 = con.prepareStatement(updateSalarySql);
pstmt.setDouble(1, 3000);
pstmt.setInt(2, 1);

boolean autoCommit = con.getAutoCommit();
try {
    con.setAutoCommit(false);
    pstmt.executeUpdate();
    pstmt2.executeUpdate();
    con.commit();
} catch (SQLException exc) {
    con.rollback();
} finally {
    con.setAutoCommit(autoCommit);
}

Для краткости мы опускаем здесь блоки try-with-resources .

8. Закрытие ресурсов

Когда мы больше не используем его, нам нужно закрыть соединение, чтобы освободить ресурсы базы данных .

Мы можем сделать это с помощью close() API:

con.close();

Однако, если мы используем ресурс в блоке try-with-resources , нам не нужно явно вызывать метод close () , так как блок try-with-resources делает это за нас автоматически.

То же самое верно и для Заявление s, Подготовленное Заявление s, Вызываемое утверждение s, и Набор результатов s.

9. Заключение

В этом уроке мы рассмотрели основы работы с API JDBC.

Как всегда, полный исходный код примеров можно найти на GitHub .