1. Обзор
В этой статье мы рассмотрим JDBC (Java Database Connectivity), который представляет собой API для подключения и выполнения запросов к базе данных.
JDBC может работать с любой базой данных, если предоставляются надлежащие драйверы.
2. Драйверы JDBC
Драйвер JDBC - это реализация API JDBC, используемая для подключения к базе данных определенного типа. Существует несколько типов драйверов JDBC:
-
Тип 1 - содержит отображение на другой API доступа к данным; пример
это драйвер JDBC-ODBC ** Тип 2 - это реализация, которая использует клиентские библиотеки
целевая база данных; также называется драйвером native-API ** Тип 3 - использует промежуточное ПО для преобразования вызовов JDBC в базу данных
звонки; также известный как драйвер сетевого протокола ** Тип 4 - подключение напрямую к базе данных путем преобразования вызовов JDBC в
специфичные для базы данных звонки; известные как драйверы протокола базы данных или тонкие драйверы,
Наиболее часто используемый тип - это тип 4, так как он имеет преимущество в том, что не зависит от платформы . Подключение напрямую к серверу базы данных обеспечивает лучшую производительность по сравнению с другими типами. Недостатком этого типа драйвера является то, что он зависит от базы данных - учитывая, что каждая база данных имеет свой собственный определенный протокол.
3. Подключение к базе данных
Чтобы подключиться к базе данных, нам просто нужно инициализировать драйвер и открыть соединение с базой данных.
3.1. Регистрация водителя
В нашем примере мы будем использовать драйвер протокола базы данных типа 4.
Поскольку мы используем базу данных MySQL, нам нужен https://search.maven.org/classic/#search%7Cga%7C1%7Ca%3A%22mysql-connector-java%22%20AND%20g%3A%22mysql Зависимость% 22[ mysql-connector-java ]:
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>6.0.6</version>
</dependency>
Далее, давайте зарегистрируем драйвер, используя метод Class.forName () , который динамически загружает класс драйвера:
Class.forName("com.mysql.cj.jdbc.Driver");
3.2. Создание соединения
Чтобы открыть соединение, мы можем использовать метод getConnection () класса DriverManager . Этот метод требует подключения URL String параметр:
Connection con = DriverManager
.getConnection("jdbc:mysql://localhost:3306/myDb", "user1", "pass");
Синтаксис 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. Утверждение
Интерфейс Statement содержит основные функции для выполнения команд SQL.
Сначала давайте создадим объект Statement :
Statement stmt = con.createStatement();
Выполнение инструкций SQL может быть выполнено с использованием трех методов:
-
executeQuery () для инструкций SELECT
-
executeUpdate () для обновления данных или структуры базы данных
-
execute () может использоваться в обоих вышеупомянутых случаях, когда результат
неизвестный
Давайте используем метод execute () , чтобы добавить таблицу students в нашу базу данных:
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);
-
Если для обновления данных используется метод execute () , то метод 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";
ResultSet resultSet = stmt.executeQuery(selectSql);
4.2. Подготовленное заявление
Объекты PreparedStatement содержат предварительно скомпилированные последовательности SQL. Они могут иметь один или несколько параметров, обозначенных знаком вопроса.
Давайте создадим PreparedStatement , который обновляет записи в таблице employees на основе заданных параметров:
String updatePositionSql = "UPDATE employees SET position=? WHERE emp__id=?";
PreparedStatement pstmt = con.prepareStatement(updatePositionSql);
Чтобы добавить параметры в 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 позволяет вызывать хранимые процедуры.
Для создания объекта CallableStatement мы можем использовать метод prepareCall () для Connection :
String preparedSql = "{call insertEmployee(?,?,?,?)}";
CallableStatement cstmt = con.prepareCall(preparedSql);
Установка значений входных параметров для хранимой процедуры выполняется аналогично интерфейсу 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 вставит новую запись в таблицу employees с использованием заданных параметров и вернет идентификатор новой записи в параметре 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");
Это сообщит JDBC API, что у пользователя нет прав на чтение метаданных процедуры, поэтому он создаст все параметры как параметры INOUT String .
5. Анализ результатов запроса
После выполнения запроса результат представляется объектом ResultSet , структура которого похожа на таблицу со строками и столбцами.
5.1. ResultSet Интерфейс
ResultSet использует метод next () для перехода к следующей строке.
Давайте сначала создадим класс Employee для хранения наших найденных записей:
public class Employee {
private int id;
private String name;
private String position;
private double salary;
//standard constructor, getters, setters
}
Далее, давайте пройдемся по ResultSet и создадим объект Employee для каждой записи:
String selectSql = "SELECT ** FROM employees";
ResultSet resultSet = stmt.executeQuery(selectSql);
List<Employee> 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);
}
Получить значение для каждой ячейки таблицы можно с помощью методов типа getX ( ), где X представляет тип данных ячейки.
Методы getX () можно использовать с параметром int , представляющим порядок ячейки, или параметром String , представляющим имя столбца. Последний вариант предпочтителен в случае, если мы изменим порядок столбцов в запросе.
5.2. Обновляемый ResultSet
Неявно, объект ResultSet может быть пройден только вперед и не может быть изменен.
Если мы хотим использовать ResultSet для обновления данных и их перемещения в обоих направлениях, нам нужно создать объект Statement с дополнительными параметрами:
stmt = con.createStatement(
ResultSet.TYPE__SCROLL__INSENSITIVE,
ResultSet.CONCUR__UPDATABLE
);
Для навигации по этому типу ResultSet мы можем использовать один из методов:
-
first (), last (), beforeFirst (), beforeLast () - перейти к первому
или последняя строка ResultSet или строка перед этими ** next (), previous () - для перемещения вперед и назад в
ResultSet ** getRow () – , чтобы получить текущий номер строки
-
moveToInsertRow (), moveToCurrentRow () - перейти к новой пустой строке
вставить и вернуться к текущему, если в новой строке ** absolute (int row) – , чтобы перейти к указанной строке
-
relative (int nrRows) - для перемещения курсора на указанное количество строк
Обновление ResultSet может быть выполнено с использованием методов в формате updateX () , где X - тип данных ячейки. Эти методы обновляют только объект ResultSet , а не таблицы базы данных.
Чтобы сохранить изменения ResultSet в базе данных, мы должны дополнительно использовать один из методов:
-
updateRow () - сохранить изменения в текущей строке в
база данных ** insertRow (), deleteRow () - добавить новую строку или удалить текущую
один из базы данных ** refreshRow () - обновить ResultSet при любых изменениях
база данных ** cancelRowUpdates () - отменить изменения, внесенные в текущую строку
Давайте рассмотрим пример использования некоторых из этих методов путем обновления данных в таблице employee’s :
Statement updatableStmt = con.createStatement(
ResultSet.TYPE__SCROLL__INSENSITIVE, ResultSet.CONCUR__UPDATABLE);
ResultSet updatableResultSet = updatableStmt.executeQuery(selectSql);
updatableResultSet.moveToInsertRow();
updatableResultSet.updateString("name", "mark");
updatableResultSet.updateString("position", "analyst");
updatableResultSet.updateDouble("salary", 2000);
updatableResultSet.insertRow();
6. Разбор метаданных
JDBC API позволяет искать информацию о базе данных, которая называется метаданными.
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
Этот интерфейс может использоваться для поиска информации об определенном ResultSet , такой как номер и имя его столбцов:
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 Connection значение false , затем использовать методы commit () и rollback () для управления транзакцией.
Давайте добавим второй оператор обновления для столбца salary после обновления столбца position сотрудника и обернем их оба в транзакцию.
Таким образом, зарплата будет обновляться только в том случае, если должность была успешно обновлена:
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);
}
8. Закрытие соединения
Когда мы больше не используем, необходимо закрыть соединение, чтобы освободить ресурсы базы данных.
Это можно сделать с помощью API close () :
con.close();
9. Заключение
В этом уроке мы рассмотрели основы работы с JDBC API.
Как всегда, полный исходный код примеров можно найти over на GitHub .