Время прочтения: 6 мин.

При работе с БД вы наверняка прибегали к использованию Python и ODBC. Это хорошие инструменты для работы с большими данными, но они сталкиваются с ограничением производительности при работе с ОЧЕНЬ большими объёмами данных. Например, обработка таблицы с несколькими миллионами строк может занять несколько десятков часов, что может быть критично. В такой ситуации разумно посмотреть в сторону Java и JDBC – обёртку вокруг интерфейса ODBC для Java. Данное решение будет работать ощутимо быстрее, чем аналогичное на python с использованием ODBC, что в некоторых ситуациях является крайне необходимым. Конкретный пример обработки данных мы рассмотрим в следующей статье. А сейчас рассмотрим работу с JDBC, а также напишем модуль для упрощения работы с ним.

Создадим новый проект с использованием maven и включим в pom.xml интересующие нас пакеты для подключения к MS SQL: jdbc, mssql-jdbc, mssql-jdbc_auth.

Дождёмся загрузки пакетов и начнём писать код.

import java.sql.*; // Импорт пакета для работы с базой

public class MainClass {
    public static void main(String[] args){
		String server = "localhost"; // Адрес сервера БД
		String dbName = "testDB"; // имя БД
		String user = "user"; // Имя пользователя БД
		String password = "password"; // пароль пользователя БД
		
        Connection conn = null; // Объявление переменной подключения
		
		// Формирование строки подключения и переменной с используемым драйвером
        String connString = "jdbc:sqlserver://" + server + ";databaseName=" + dbName + ";"; 
        String driver = "com.microsoft.sqlserver.jdbc.SQLServerDriver";

		// Объявление переменной для выполнения запросов
        Statement stmt;

        try {
			// Загрузка драйвера и инициализация подключения
            Class.forName(driver);
            conn = DriverManager.getConnection(connString, user, password);

			// Формирование строки запроса
            String query = "SELECT * FROM [testTable]";

			// Инициализация обработчика запросов и указание ограничения на количество строк, 
			// которые одновременно находятся в ОЗУ
            stmt = conn.createStatement();
			stmt.setFetchSize(100);

			// Выполнение запроса
            ResultSet responce = stmt.executeQuery(query);
			
			while(responce.next()) {
				// Перебор всех строк в таблице и вывод первого столбца каждой строки
				
				System.out.println(responce.getString(1));
			}

            conn.close(); // Закрытие подключения
        } catch (Exception e) {
            e.printStackTrace();
        }

    }
}

Для использования внутренней проверки безопасности (подключение по УЗ вместо логина и пароля), необходимо добавить в строку подключения параметр integratedSecurity со значением true и при инициализации подключения убрать из параметров имя пользователя и пароль.

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

Создадим новый класс DBC, конструктор которого должен выполнять следующие задачи:

 — получать необходимые для подключения данные;

 — инициализировать соответствующие поля;

 — проверять возможность подключения по переданным данным;

 — при необходимости сообщать о неверно введённых данных и/или недоступности сервера. А также конструктор необходимо перегрузить для возможности обрабатывать все возможные варианты подключения.

package DataBaseTools;

import java.sql.*;

public class DBC {
    private final String serverUrl; // Адрес сервера баз данных
    private final String dbName; // Имя базы данных
    private String driver = "com.microsoft.sqlserver.jdbc.SQLServerDriver"; // Используемый драйвер
    private String user = ""; // Имя пользователя базы данных
    private String password = ""; // Пароль пользователя базы данных
    private final boolean readyToWork; // Флаг готовности класса к работе с базы данных

    /**
     * Конструктор для работы со встроенной системой проверки безопасности.
     *
     * @param serverUrl Адрес сервера баз данных
     * @param dbName    Имя базы данных
     */
    DBC(String serverUrl, String dbName) {
        this.serverUrl = serverUrl;
        this.dbName = dbName;

        readyToWork = checkConnection();
    }

    /**
     * Конструктор для работы со встроенной системой проверки безопасности и с указанным драйвером.
     *
     * @param serverUrl Адрес сервера баз данных
     * @param dbName    Имя базы данных
     * @param driver    Используемый драйвер. По умолчанию <code>com.microsoft.sqlserver.jdbc.SQLServerDriver</code>
     */
    DBC(String serverUrl, String dbName, String driver) {
        this.serverUrl = serverUrl;
        this.dbName = dbName;
        this.driver = driver;

        readyToWork = checkConnection();
    }

    /**
     * Конструктор для работы с использованием имени и пароля пользователя базы данных.
     *
     * @param serverUrl Адрес сервера баз данных
     * @param dbName    Имя базы данных
     * @param user      Имя пользователя
     * @param password  Пароль
     */
    DBC(String serverUrl, String dbName, String user, String password) {
        this.serverUrl = serverUrl;
        this.dbName = dbName;
        this.user = user;
        this.password = password;

        readyToWork = checkConnection();
    }

    /**
     * Конструктор для работы с использованием имени и пароля пользователя базы данных и указанием драйвера.
     *
     * @param serverUrl Адрес сервера баз данных
     * @param dbName    Имя базы данных
     * @param user      Имя пользователя
     * @param password  Пароль
     * @param driver    Используемый драйвер. По умолчанию <code>com.microsoft.sqlserver.jdbc.SQLServerDriver</code>
     */
    DBC(String serverUrl, String dbName, String user, String password, String driver) {
        this.serverUrl = serverUrl;
        this.dbName = dbName;
        this.user = user;
        this.password = password;
        this.driver = driver;

        readyToWork = checkConnection();
    }
}

Обратите внимание, что практически все поля класса имеют модификатор final, что сделано для возможности работы одного экземпляра только с одной базой. Далее напишем приватный метод checkConnection, который будет создавать тестовое подключение и при отсутствии ошибок возвращать true (иначе false), которое записывается в поле readyToWork, выступающее флагом успешности/не успешности инициализации экземпляра класса. Также напишем ещё один приватный метод createConnection, создающий и возвращающий новое подключение. Он нужен для избегания ненужного дублирования кода.

/**
 * Создаёт объект подключения.
 *
 * @return Объект подключения.
 */
private Connection createConnection() {

    // Формирование строки подключения
    String connStr = "jdbc:sqlserver://" + this.serverUrl + ";databaseName=" + this.dbName + ";";

    try {
        // Установка драйвера
        Class.forName(driver);
    } catch (ClassNotFoundException e) {
        e.printStackTrace();
        return null;
    }

    Connection conn;

    if (this.user.equals("") && this.password.equals("")) {
        // Подключение с встроенной проверкой безопасности

        connStr += "integratedSecurity=true;";

        try {
            conn = DriverManager.getConnection(connStr);
        } catch (SQLException e) {
            e.printStackTrace();
            return null;
        }
    } else {
        // Подключение по имени и паролю пользователя

        try {
            conn = DriverManager.getConnection(connStr, user, password);
        } catch (SQLException e) {
            e.printStackTrace();
            return null;
        }
    }

    return conn;

}

/**
 * Проверяет возможность установления подключения с сервером баз данных.
 *
 * @return true - подключение успешно.
 */
private boolean checkConnection() {
    Connection testConnection = createConnection();

    if (testConnection == null) {
        System.out.println("Can't connect to server. Check throws & url/dbName/username/password.\n" +
                "*IntegratedSecurity option require sqljdbc_auth.dll in java.library.path.");
        return false;
    } else {
        try {
            testConnection.close();
        } catch (SQLException e) {
            e.printStackTrace();
            return false;
        }
        return true;
    }
}

Теперь перейдём к выполнению запросов. Создадим публичный класс execQuery с единственным параметром – текстом запроса. Возвращать метод будет экземпляр ResultSet, содержащий результаты выполнения запроса.

/**
 * Выполняет любой запрос переданный в параметре
 *
 * @param query Текст запроса
 * @return ResultSet с результатами работы запроса
 */
public ResultSet execQuery(String query) {
    if (!readyToWork) {
        System.out.println("DBC not ready to work! Abort:execQuerySelected");
        return null;
    }

    Connection conn = createConnection();

    Statement stmt;

    try {
        assert conn != null;
        stmt = conn.createStatement();
        stmt.setFetchSize(100);

        return stmt.executeQuery(query);

    } catch (SQLException e) {
        e.printStackTrace();
        System.out.println("Something wrong... Check your query text.");
        return null;
    }
}

На этом моменте можно считать модуль минимально завершённым. Заменим первоначальный код работы с базой из начала статьи на следующий:

package DataBaseTools;

import java.sql.ResultSet;
import java.sql.SQLException;

public class MainClass {

    public static void main(String[] args) throws SQLException {
        // Создание нового экземпляра
        DBC dbc = new DBC("localhost", "testDB");

        // Выполнение запроса
        ResultSet response = dbc.execQuery ("select * from testTable");

        // Вывод результата работы запроса на экран
        if (response == null) {
            System.out.println("NULL");
        } else {
            while (response.next()) {
                System.out.println(response.getString(1));
            }
        }

    }
}

Как видим, подключение и выполнение запроса сократились до 2х строк, что упрощает дальнейшее использование модуля.

В заключении хочется сказать: нет ничего плохого в быстром написании небольшого скрипта на Python, который обработает небольшие объёмы данных из БД, но при критичности времени, затрачиваемого на эту работу, более оптимальное решение – Java с JDBC.