При работе с БД вы наверняка прибегали к использованию 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.