Руководство по JDBC. Утверждения (Statements).

В прошлом уроке мы изучили что такое соединение и способы его создания.
После того как соединение было установленно, мы можем начинать взаимодействие с базой данных (далее – БД).
Взаимодействовать с БД мы можем с помощью трёх интерфейсов, которые имплементируются каждым драйвером:

  • Statement
    Этот интерфейс используется для доступа к БД для общих целей. Он крайне полезен, когда мы используем статические SQL – выражения во время работы программы. Этот интерфейс не принимает никаких параметров.
  • PreparedStatement
    Этот интерфейс используется в случае, когда мы планируем использовать SQL – выражения множество раз. Он принимает параметры во время работы программы.
  • CallableStatement
    Этот интерфейс становится полезным в случае, когда мы хотим получить доступ к различным процедурам БД. Он также может принимать параметры во время работы программы.

Создание экземпляра Statement

Прежде, чем мы сможем использовать экземпляр Statement для выполнения SQL – запросов, нам необходимо создать такой экземпляр. Для этого используется метод Connection.createStatement(). В коде это выглядит таким образом:


try {
            statement =connection.createStatement();
        } catch (SQLException e) {
            e.printStackTrace();
        } finally {
            /*Do some job...*/
        }

После этого мы можем использовать наш экземпляр statement для выполнения SQL – запросов.

Для этой цели интерфейс Statement имеет три метода, которые реализуются каждой конкретной реализацией JDBC драйвера:

  • boolean execute (String SQL)
    Этот метод возвращает логическое значение true, если объект ResultSet может быть получен. В противном случае он возвращает false. Он используется для выполнения DDL SQL – запросов ил в случаях, когда мы используем динамический SQL.
  • int executeUpdate (String SQL)
    Этот метода возвращает количество столбцов в таблице, на которое повлиял наш SQL – запрос. Мы используем этот метод для выполнения SQL – запросов, когда хотим получить количество задействованных столбцов, например количество данных по определённому запросу.
  • ResultSet executeQuery (String SQL)
    Этот метод возвращает нам экземпляр ResultSet. Мы используем этот метод в случаях, когда мы рассчитываем получить множество объектов в результате выполнения нашего SQL – запроса. Например, при получении списка элементов, которые удовлетворяют определённым условиям.

Закрытие экземпляра Statement

Когда мы закрываем наше соединение (Connection) для сохранения результатов в БД мы таким же образом закрываем и экземпляр Statement.

Для этого мы используем метод close().

Рассмотрим, как это выглядит в нашем коде:


Connection connection = null;
        Statement statement = null;

        Class.forName(JDBC_DRIVER);
        connection = DriverManager.getConnection(DATABASE_URL, USER, PASSWORD);

        try {
            statement = connection.createStatement();
        } catch (SQLException e) {
            e.printStackTrace();
        } finally {
            if (statement != null) {
                statement.close();
            }
        }

Для понимания того, как это работает на практике, рассмотрим пример простого приложения, в котором мы попытаемся получить данные из БД.

Пример:


import java.sql.*;

public class StatementDemo {
    static final String JDBC_DRIVER = "com.mysql.jdbc.Driver";
    static final String DATABASE_URL = "jdbc:mysql://localhost/PROSELYTE_TUTORIALS";

    static final String USER = "ИМЯ_ПОЛЬЗОВАТЕЛЯ";
    static final String PASSWORD = "ВАШ_ПАРОЛЬ";

    public static void main(String[] args) throws ClassNotFoundException, SQLException {
        Connection connection = null;
        Statement statement = null;


        System.out.println("Registering JDBC driver...");
        Class.forName(JDBC_DRIVER);

        System.out.println("Connecting to database...");
        connection = DriverManager.getConnection(DATABASE_URL, USER, PASSWORD);

        System.out.println("Creating statement...");
        statement = connection.createStatement();

        String sql = "SELECT * FROM developers";

        Boolean isRetrieved = statement.execute(sql);

        System.out.println("Is data retrieved: " + isRetrieved);

        System.out.println("Displaying retrieved data:");
        ResultSet resultSet = statement.executeQuery(sql);

        while (resultSet.next()) {
            int id = resultSet.getInt("id");
            String name = resultSet.getString("name");
            String specialty = resultSet.getString("specialty");
            int salary = resultSet.getInt("salary");

            System.out.println("id: " + id);
            System.out.println("Name: " + name);
            System.out.println("Specialty: " + specialty);
            System.out.println("Salary: " + salary);
            System.out.println("\n===================\n");
        }

        System.out.println("Closing connection and releasing resources...");

        try {
            resultSet.close();
            statement.close();
            connection.close();
        }finally {
            if(statement !=null){
                statement.close();
            }
            if(connection!=null){
                connection.close();
            }
        }
        System.out.println("Thank You.");
    }
}

В результате работы программы мы получим следующий результат:


/*Some System Messages*/

Registering JDBC driver...
Connecting to database...
Creating statement...
Is data retrieved: true
Displaying retrieved data:
id: 1
Name: Proselyte
Specialty: Java
Salary: 2000

===================

id: 2
Name: Peter
Specialty: C++
Salary: 3000

===================

id: 3
Name: AsyaSmile
Specialty: UI/UX
Salary: 2000

===================

Closing connection and releasing resources...
Thank You.

Создание экземпляра PreparedStatement

PreparedStatement наследует интерфейс Statement, что даёт нам определённые преимущества перед обычным Statement. В частности, мы получаем большую гибкость при динамической поддержке аргументов.

Вот как выглядит создание экземпляра PreparedStatement на практике:


 try {
            String SQL = "Update developers SET salary WHERE specialty = ?";
            preparedStatement = connection.prepareStatement(SQL);
        }catch (SQLException e){
            e.printStackTrace();
        }finally {
            /*do some job...*/
        }

Все параметры, которые отмечены символом называются маркерами параметра. Это означает, что они должны быть переданы через параметры метода.

Каждый параметр ссылается на свой порядковый номер в сигнатуре метода. Т.е. первый маркер находится на первом месте, второй – на втором и т.д. В отличие от массивов, здесь отсчёт идёт с 1. Это связано с особенностями реляционной модели, на которой и основана работа реляционных БД.

Для выполнения SQL – запросов используются методы с такими же названиями (execute(), executeQuery, executeUpdate), которые несколько модифицированы.


Закрытие экземпляра PreparedStatement

Когда мы закрываем наше соединение (Connection) для сохранения результатов в БД мы таким же образом закрываем и экземпляр PreparedStatement.

Для этого мы используем метод close().

Рассмотрим, как это выглядит в нашем коде:


try {
            String SQL = "Update developers SET salary WHERE specialty = ?";
            preparedStatement = connection.prepareStatement(SQL);
        } catch (SQLException e) {
            e.printStackTrace();
        } finally {
            if (preparedStatement != null) {
                preparedStatement.close();
            }
        }

Для понимания того, как это работает на практике, рассмотрим пример простого приложения с использованием PreparedStatement.
Пример:


import java.sql.*;

public class PreparedStatementDemo {
    static final String JDBC_DRIVER = "com.mysql.jdbc.Driver";
    static final String DATABASE_URL = "jdbc:mysql://localhost/PROSELYTE_TUTORIALS";

    static final String USER = "ИМЯ_ПОЛЬЗОВАТЕЛЯ";
    static final String PASSWORD = "ВАШ_ПАРОЛЬ";


    public static void main(String[] args) throws ClassNotFoundException, SQLException {
        Connection connection = null;
        PreparedStatement preparedStatement = null;

        System.out.println("Registering JDBC driver...");
        Class.forName(JDBC_DRIVER);

        System.out.println("Creating connection...");
        connection = DriverManager.getConnection(DATABASE_URL, USER, PASSWORD);


        try {
            String SQL = "SELECT * FROM developers";
            preparedStatement = connection.prepareStatement(SQL);
            System.out.println("Initial developers table content:");
            ResultSet resultSet = preparedStatement.executeQuery(SQL);
            while (resultSet.next()) {
                int id = resultSet.getInt("id");
                String name = resultSet.getString("name");
                String specialty = resultSet.getString("specialty");
                int salary = resultSet.getInt("salary");

                System.out.println("id: " + id);
                System.out.println("Name: " + name);
                System.out.println("Specialty: " + specialty);
                System.out.println("Salary: " + salary);
                System.out.println("\n============================\n");
            }

            SQL = "Update developers SET salary=? WHERE specialty=?";
            System.out.println("Creating statement...");
            System.out.println("Executing SQL query...");

            preparedStatement = connection.prepareStatement(SQL);
            preparedStatement.setInt(1, 3000);
            preparedStatement.setString(2, "java");

            System.out.println("Rows impacted: " + preparedStatement.executeUpdate());

            System.out.println("Final developers table content:");
            SQL = "SELECT * FROM developers";
            resultSet = preparedStatement.executeQuery(SQL);
            while (resultSet.next()) {
                int id = resultSet.getInt("id");
                String name = resultSet.getString("name");
                String specialty = resultSet.getString("specialty");
                int salary = resultSet.getInt("salary");

                System.out.println("id: " + id);
                System.out.println("Name: " + name);
                System.out.println("Specialty: " + specialty);
                System.out.println("Salary: " + salary);
                System.out.println("\n============================\n");
            }
        } catch (SQLException e) {
            e.printStackTrace();
        } finally {
            if (preparedStatement != null) {
                preparedStatement.close();
            }
            if(connection!=null){
                connection.close();
            }
        }

        System.out.println("Thank You.");
    }
}

В результате работы программы мы получим, примерно, следующий результат:


/*Some System Messages*/

Registering JDBC driver...
Creating connection...
Initial developers table content:
id: 1
Name: Proselyte
Specialty: Java
Salary: 2000

============================

id: 2
Name: Peter
Specialty: C++
Salary: 3000

============================

id: 3
Name: AsyaSmile
Specialty: UI/UX
Salary: 2000

============================

id: 4
Name: Eugene
Specialty: Java
Salary: 2200

============================

Creating statement...
Executing SQL query...
Rows impacted: 2
Final developers table content:
id: 1
Name: Proselyte
Specialty: Java
Salary: 3000

============================

id: 2
Name: Peter
Specialty: C++
Salary: 3000

============================

id: 3
Name: AsyaSmile
Specialty: UI/UX
Salary: 2000

============================

id: 4
Name: Eugene
Specialty: Java
Salary: 3000

============================

Thank You.

В этом приложении мы сначала получаем и выводим в консоль список всех записей из таблицы developer, после чего устанавливаем salary = 3000 для всех разработчиков, у которых specialty = “Java”.


Создание экземпляра CallableStatement

Экземпляр CallableStatement используется для выполнения процедур, непосредственно в самой БД.

Рассмотрим пример, в котором нам необходимо выполнить такую процедуру в MySQL:


DELIMITER $
DROP PROCEDURE IF EXISTS `developers`.`getDeveloperName` $
CREATE PROCEDURE PROSELYTE_TUTORIALS.`getDeveloperName`
(IN DEVELOPER_ID INT, OUT DEVELOPER_NAME VARCHAR(50))
BEGIN
SELECT first INTO DEVELOPER_NAME
FROM developers
WHERE id = DEVELOPER_ID;

END $

DELIMITER ;

Существует три типа параметров: IN, OUT, INOUT. PreparedStatement использует только IN, а CallableStatement, в свою очередь, использует все три.

Рассмотрим, что же это за параметры:

  • IN
    Параметр, значение которого известно в момент, когда создаётся запрос. Мы назначаем параметр IN с помощью метода типа setXXX().
  • OUT
    Параметр, значение которого возвращается SQL – запросом. Мы получаем значения из  OUT с помощью методов типа getXXX().
  • INOUT
    Параметр, который использует входные и выходные значения. Мы назначаем параметр с помощью метода типа setXXX(), а получаем значения, с помощью метода типа getXXX().

В коде, создание экземпляра CallableStatement выглядит следующим образом:


try {
            String SQL = "{call getDeveloperName (?, ?)}";
            callableStatement = connection.prepareCall(SQL);
        }finally {
            /* do some job */
        }

Строка SQL представляет собой процедуру, с параметрами.

Схожим с PreparedStatement способом, мы, используя экземпляр CallableStatement, должны установить значения параметров.

Когда мы используем параметры типа OUT и INOUT, нам необходимо задействовать дополнительный метод registerOutParameter(). Этот метод устанавливает тип данных JDBC в тип данных процедуры.

После того как мы вызвали процедуру, мы получаем значение из параметра OUT с помощью соответствующего метода getXXX(). Этот метод преобразует полученное значение из типа данных SQL в тип данных Java.


Закрытие экземпляра CallableStatement

Когда мы закрываем наше соединение (Connection) для сохранения результатов в БД мы таким же образом закрываем и экземпляр Statement.

Для этого мы используем метод close().

Рассмотрим, как это выглядит в нашем коде:


try {
            String SQL = "{call getDeveloperName (?, ?)}";
            callableStatement = connection.prepareCall(SQL);
        }finally {
            if(callableStatement!=null){
                callableStatement.close();
            }
        }

Для понимания того, как это работает на практике, рассмотрим пример простого приложения
Пример:

Создаём процедуру в нашей БД:


DELIMITER $
DROP PROCEDURE IF EXISTS PROSELYTE_TUTORIALS.`getDeveloperName` $
CREATE PROCEDURE PROSELYTE_TUTORIALS.`getDeveloperName`
(IN DEVELOPER_ID INT, OUT DEVELOPER_NAME VARCHAR(50))
BEGIN
SELECT name INTO DEVELOPER_NAME
FROM developers
WHERE id = DEVELOPER_ID;

END $

DELIMITER ;

Класс CallableStatementDemo


import java.sql.*;

public class CallableStatementDemo {
    static final String JDBC_DRIVER = "com.mysql.jdbc.Driver";
    static final String DATABASE_URL = "jdbc:mysql://localhost/PROSELYTE_TUTORIALS";

    static final String USER = "ВАШЕ_ИМЯ_ПОЛЬЗОВАТЕЛЯ";
    static final String PASSWORD = "ВАШ_ПАРОЛЬ";


    public static void main(String[] args) throws ClassNotFoundException, SQLException {
        Connection connection = null;
        CallableStatement callableStatement = null;

        System.out.println("Registering JDBC driver...");
        Class.forName(JDBC_DRIVER);

        System.out.println("Creating connection...");
        connection = DriverManager.getConnection(DATABASE_URL, USER, PASSWORD);

        System.out.println("Creating callable statement...");

        try {


            String SQL = "{call getDeveloperName (?, ?)}";
            callableStatement = connection.prepareCall(SQL);

            int developerID = 1;
            callableStatement.setInt(1, developerID);
            callableStatement.registerOutParameter(2, Types.VARCHAR);

            System.out.println("Executing procedure...");
            callableStatement.execute();

            String developerName = callableStatement.getString(2);
            System.out.println("Developer INFO");
            System.out.println("id: " + developerID);
            System.out.println("Name: " + developerName);
        } finally {
            if (callableStatement != null) {
                callableStatement.close();
            }
            if (connection != null) {
                connection.close();
            }
        }
        System.out.println("Thank You.");
    }
}

В результате работы программы мы получим следующий результат:


/*Some System Messages*/

Registering JDBC driver...
Creating connection...
Creating callable statement...
Executing procedure...
Developer INFO
id: 1
Name: Proselyte
Thank You.

В этом уроке мы изучили утверждения (statements) их виды и рассмотрели примеры приложений с их использованием.

В следующем уроке мы изучим такой элемент, как Result Set.