JDBC — групповые операции
Много запросов с помощью Batch-метода
В реальных проектах часто возникает ситуация, когда вам необходимо сделать очень много однотипных запросов (наиболее частов в этом случае встречается PreparedStatement) — например надо вставить несколько десятков или сотен записей. Если вы будете выполнять каждый запрос отдельно, то это будет достаточно долго и производительность вашего приложения будет невысокой. Для повышения производительности вы можете использовать batch-режим вставки. Он заключается в том, что вы накапливаете некоторый буфер своими запросами, а потом выполняете их сразу. В качестве примера приведу кусочек кода:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
PreparedStatement stmt = con.prepareStatement( "INSERT INTO jc_contact (first_name, last_name, phone, email) VALUES (?, ?, ?, ?)"); for (int i = 0; i < 10; i++) { // Заполняем параметры запроса stmt.setString(1, "FirstName_" + i); stmt.setString(2, "LastNAme_" + i); stmt.setString(3, "phone_" + i); stmt.setString(4, "email_" + i); // Запрос не выполняется, а укладывается в буфер, // который потом выполняется сразу для всех команд stmt.addBatch(); } // Выполняем все запросы разом stmt.executeBatch(); |
В принципе вот и все — ничего сверхсложного там нет. Могу только посоветовать не увлекаться и не вставлять больше нескольких сотен запросов за раз и обратить внимание, что executeBatch возвращает массив целых чисел (попробуйте сами догадаться, что он значит — в качестве подсказки посмотрите, что возвращает executeUpdate).
Также есть смысл посмотреть на работу getGeneratedKeys() — если вы уже забыли, что это такое — посмотрите предыдущую статью.
Много запросов в одном Statement
Вторым достаточно любопытным способом повысить производительность может быть выполнение сразу нескольких разных SQL-запросов. В качестве примера можно посмотреть следующий код.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 |
// Вы можете выполнить сразу несколько запросов внутри одного PreparedStatement stmt = con.prepareStatement( "SELECT * FROM jc_contact; DELETE FROM jc_contact"); // true - первый результат возвращает ResultSet // false - первый результат выполнял update/delete/insert boolean test = stmt.execute(); // Для проверки наличия еще одного выполненного SQL-запроса // можно проверить stmt.getUpdateCount() while (test || stmt.getUpdateCount() > -1) { if (test) { try (ResultSet rs = stmt.getResultSet()) { while (rs.next()) { String str = rs.getString("contact_id") + ":" + rs.getString(2); System.out.println("Contact:" + str); } } } else { System.out.println("Update SQL is executed:" + stmt.getUpdateCount()); } System.out.println("============================="); test = stmt.getMoreResults(); } |
Как видите, мы в одну строку поместили ДВА SQL-запроса — один SELECT и один DELETE. После этого мы выполняем метод execute(), который возвращает true, если первое возвращаемое значение является ResultSet или false — если это был запрос на модификацию.
Для перехода к результату исполнения следующего запроса вызывается метод getMoreResults()он также возвращает true в случае, если следующий результат является типом ResultSet или false — это значит, что либо SQL-запрос выполнял модификацию, либо больше результатов нет. Чтобы выбрать между этими двумя вариантами надо обратиться к методу getUpdateCount. Если возвращается 0 и больше — значит выполнялся запрос на модификацию. Если результат равен -1 — значит больше запросов нет.
И наконец, полный текст примера для обоих случаев — покопайтесь в нем самостоятельно. Обратите внмание на более лаконичную конструкцию для try с ресурсами. Она появилась в java 1.7 = думаю, самое время вам понять, что и как там происходит. (в общем ничего сложного — ресурс автоматически закрывается)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 |
package edu.javacourse.database; import java.sql.Connection; import java.sql.DriverManager; import java.sql.PreparedStatement; import java.sql.ResultSet; import java.sql.SQLException; public class BatchExampleDb { public static void main(String[] args) { try { Class.forName("org.postgresql.Driver"); String url = "jdbc:postgresql://localhost:5432/contactdb"; try (Connection con = DriverManager.getConnection(url, "postgres", "postgres")) { addBatch(con); select(con); } } catch (ClassNotFoundException | SQLException e) { e.printStackTrace(System.out); } } private static void addBatch(Connection con) throws SQLException { try (PreparedStatement stmt = con.prepareStatement( "INSERT INTO jc_contact (first_name, last_name, phone, email) " + "VALUES (?, ?, ?, ?)", new String[] {"contact_id"})) { for (int i = 0; i < 10; i++) { // Заполняем параметры запроса stmt.setString(1, "FirstName_" + i); stmt.setString(2, "LastNAme_" + i); stmt.setString(3, "phone_" + i); stmt.setString(4, "email_" + i); // Запрос не выполняется, а укладывается в буфер, кторый выполняется сразу для всех команд stmt.addBatch(); } // Выполняем все запросы разом stmt.executeBatch(); // Получить список сгенерированных contact_id ResultSet gk = stmt.getGeneratedKeys(); while(gk.next()) { System.out.println("Inserted:" + gk.getString(1)); } } } private static void select(Connection con) throws SQLException { // Вы можете выполнить сразу несколько запросов внутри одного try (PreparedStatement stmt = con.prepareStatement( "SELECT * FROM jc_contact; DELETE FROM jc_contact")) { // true - первый результат возвращает ResultSet // false - первый результат выполнял update/delete/insert boolean test = stmt.execute(); // Для проверки наличия еще одного выgолненного SQL-запроса можно проверить stmt.getUpdateCount() while (test || stmt.getUpdateCount() > -1) { if (test) { try (ResultSet rs = stmt.getResultSet()) { while (rs.next()) { String str = rs.getString("contact_id") + ":" + rs.getString(2); System.out.println("Contact:" + str); } } } else { System.out.println("Update SQL is executed:" + stmt.getUpdateCount()); } System.out.println("============================="); test = stmt.getMoreResults(); } } } } |
Сам проект можно скачать здесь — BatchExampleDb.zip
Что осталось за кадром
Сейчас не могу сказать, будет ли этот раздел по базам данных в Java заключительным, но пока планирую именно так — впереди только наш замечательный пример с контактами. Какие возможности и особенности есть у JDBC еще, вы можете посмотреть сами. Мы еще много чего не разбирали:
- работа с BLOB (binary large object)
- вызов процедур через интерфейс CallableStatement
- модификация ResultSet
- использование RowSet с его расширениями.
- создание и использование собственных типов данных
- работа с массивами
- работа с XML
В общем, я оставляю на вашу собственную голову достаточно широкий спектр вопросов. Весьма неплохое руководство можно посмотреть на сайте Oracle здесь: Trail: JDBC(TM) Database Access.
И теперь нас ждет следующая статья: Список контактов — работаем с БД