Автор оригинала: Pankaj Kumar.
Управление транзакциями на java требуется, когда мы имеем дело с реляционными базами данных. Мы используем API JDBC для операций с базами данных, и сегодня мы узнаем, как использовать управление транзакциями JDBC. В учебнике по JDBC мы узнали, как использовать API JDBC для подключения к базе данных и выполнения SQL-запросов. Мы также рассмотрели различные типы драйверов и то, как мы можем свободно писать пару программ JDBC, которые помогают нам легко переключаться с одного сервера базы данных на другой.
Управление транзакциями в Java JDBC
Этот учебник предназначен для предоставления подробной информации об управлении транзакциями JDBC и использовании Точки сохранения JDBC для частичного отката.
По умолчанию, когда мы создаем соединение с базой данных, оно выполняется в режиме автоматическая регистрация . Это означает, что всякий раз, когда мы выполняем запрос и он завершен, фиксация запускается автоматически. Таким образом, каждый SQL-запрос, который мы запускаем, является транзакцией, и если мы выполняем некоторые запросы DML или DDL, изменения сохраняются в базе данных после завершения каждой инструкции SQL.
Иногда мы хотим, чтобы группа SQL-запросов была частью транзакции, чтобы мы могли зафиксировать их, когда все запросы будут выполняться нормально. Если мы получим какое-либо исключение, у нас есть выбор отката всех запросов, выполненных в рамках транзакции.
Давайте разберемся на простом примере, где мы хотим использовать поддержку управления транзакциями JDBC для обеспечения целостности данных. Допустим, у нас есть база данных UserDB, и информация о сотрудниках сохраняется в двух таблицах. Для моего примера я использую базу данных MySQL, но она будет отлично работать и в других реляционных базах данных, таких как Oracle и PostgreSQL.
В таблицах хранится информация о сотрудниках с подробными адресами в таблицах, сценарии DDL этих таблиц, как показано ниже.
CREATE TABLE `Employee` ( `empId` int(11) unsigned NOT NULL, `name` varchar(20) DEFAULT NULL, PRIMARY KEY (`empId`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8; CREATE TABLE `Address` ( `empId` int(11) unsigned NOT NULL, `address` varchar(20) DEFAULT NULL, `city` varchar(5) DEFAULT NULL, `country` varchar(20) DEFAULT NULL, PRIMARY KEY (`empId`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8;
Наш окончательный проект выглядит так, как показано на рисунке ниже, мы рассмотрим каждый из классов по очереди.
Как вы можете видеть, у меня есть jar MySQL JDBC в пути сборки проекта, так что мы можем подключиться к базе данных MySQL.
Как вы можете видеть, у меня есть jar MySQL JDBC в пути сборки проекта, так что мы можем подключиться к базе данных MySQL.
package com.journaldev.jdbc.transaction; import java.sql.Connection; import java.sql.DriverManager; import java.sql.SQLException; public class DBConnection { public final static String DB_DRIVER_CLASS = "com.mysql.jdbc.Driver"; public final static String DB_URL = "jdbc:mysql://localhost:3306/UserDB"; public final static String DB_USERNAME = "pankaj"; public final static String DB_PASSWORD = "pankaj123"; public static Connection getConnection() throws ClassNotFoundException, SQLException { Connection con = null; // load the Driver Class Class.forName(DB_DRIVER_CLASS); // create the connection now con = DriverManager.getConnection(DB_URL, DB_USERNAME, DB_PASSWORD); System.out.println("DB Connection created successfully"); return con; } }
Подключение к БД
– это класс, в котором мы создаем соединение с базой данных MySQL для использования другими классами.
Подключение к БД || – это класс, в котором мы создаем соединение с базой данных MySQL для использования другими классами.
package com.journaldev.jdbc.transaction; import java.sql.Connection; import java.sql.PreparedStatement; import java.sql.SQLException; public class EmployeeJDBCInsertExample { public static final String INSERT_EMPLOYEE_QUERY = "insert into Employee (empId, name) values (?,?)"; public static final String INSERT_ADDRESS_QUERY = "insert into Address (empId, address, city, country) values (?,?,?,?)"; public static void main(String[] args) { Connection con = null; try { con = DBConnection.getConnection(); insertEmployeeData(con, 1, "Pankaj"); insertAddressData(con, 1, "Albany Dr", "San Jose", "USA"); } catch (SQLException | ClassNotFoundException e) { e.printStackTrace(); } finally { try { if (con != null) con.close(); } catch (SQLException e) { e.printStackTrace(); } } } public static void insertAddressData(Connection con, int id, String address, String city, String country) throws SQLException { PreparedStatement stmt = con.prepareStatement(INSERT_ADDRESS_QUERY); stmt.setInt(1, id); stmt.setString(2, address); stmt.setString(3, city); stmt.setString(4, country); stmt.executeUpdate(); System.out.println("Address Data inserted successfully for ID=" + id); stmt.close(); } public static void insertEmployeeData(Connection con, int id, String name) throws SQLException { PreparedStatement stmt = con.prepareStatement(INSERT_EMPLOYEE_QUERY); stmt.setInt(1, id); stmt.setString(2, name); stmt.executeUpdate(); System.out.println("Employee Data inserted successfully for ID=" + id); stmt.close(); } }
Это простая программа JDBC, в которой мы вставляем предоставленные пользователем значения как в таблицы сотрудников, так и в таблицы адресов, созданные выше. Теперь, когда мы запустим эту программу, мы получим следующий вывод.
DB Connection created successfully Employee Data inserted successfully for ID=1 com.mysql.jdbc.MysqlDataTruncation: Data truncation: Data too long for column 'city' at row 1 at com.mysql.jdbc.MysqlIO.checkErrorPacket(MysqlIO.java:2939) at com.mysql.jdbc.MysqlIO.sendCommand(MysqlIO.java:1623) at com.mysql.jdbc.MysqlIO.sqlQueryDirect(MysqlIO.java:1715) at com.mysql.jdbc.Connection.execSQL(Connection.java:3249) at com.mysql.jdbc.PreparedStatement.executeInternal(PreparedStatement.java:1268) at com.mysql.jdbc.PreparedStatement.executeUpdate(PreparedStatement.java:1541) at com.mysql.jdbc.PreparedStatement.executeUpdate(PreparedStatement.java:1455) at com.mysql.jdbc.PreparedStatement.executeUpdate(PreparedStatement.java:1440) at com.journaldev.jdbc.transaction.EmployeeJDBCInsertExample.insertAddressData(EmployeeJDBCInsertExample.java:45) at com.journaldev.jdbc.transaction.EmployeeJDBCInsertExample.main(EmployeeJDBCInsertExample.java:23)
Как вы можете видеть, исключение SQLException возникает, когда мы пытаемся вставить данные в таблицу адресов, потому что значение больше, чем размер столбца.
Если вы посмотрите на содержимое таблиц Сотрудников и адресов, вы заметите, что данные присутствуют в таблице сотрудников, но не в таблице адресов. Это становится серьезной проблемой, потому что только часть данных вставлена правильно, и если мы снова запустим программу, она снова попытается вставить в таблицу сотрудников и выдаст исключение ниже.
com.mysql.jdbc.exceptions.MySQLIntegrityConstraintViolationException: Duplicate entry '1' for key 'PRIMARY' at com.mysql.jdbc.SQLError.createSQLException(SQLError.java:931) at com.mysql.jdbc.MysqlIO.checkErrorPacket(MysqlIO.java:2941) at com.mysql.jdbc.MysqlIO.sendCommand(MysqlIO.java:1623) at com.mysql.jdbc.MysqlIO.sqlQueryDirect(MysqlIO.java:1715) at com.mysql.jdbc.Connection.execSQL(Connection.java:3249) at com.mysql.jdbc.PreparedStatement.executeInternal(PreparedStatement.java:1268) at com.mysql.jdbc.PreparedStatement.executeUpdate(PreparedStatement.java:1541) at com.mysql.jdbc.PreparedStatement.executeUpdate(PreparedStatement.java:1455) at com.mysql.jdbc.PreparedStatement.executeUpdate(PreparedStatement.java:1440) at com.journaldev.jdbc.transaction.EmployeeJDBCInsertExample.insertEmployeeData(EmployeeJDBCInsertExample.java:57) at com.journaldev.jdbc.transaction.EmployeeJDBCInsertExample.main(EmployeeJDBCInsertExample.java:21)
Таким образом, сейчас мы не можем сохранить данные в адресной таблице для сотрудника. Таким образом, эта программа приводит к проблемам с целостностью данных, и именно поэтому нам необходимо управление транзакциями для успешной вставки в обе таблицы или отката всего, если возникнет какое-либо исключение.
Управление транзакциями JDBC
API JDBC предоставляет метод setAutoCommit (), с помощью которого мы можем отключить функцию автоматической фиксации соединения. Мы должны отключить автоматическую фиксацию только тогда, когда это необходимо, потому что транзакция не будет зафиксирована, если мы не вызовем метод commit() при подключении. Серверы баз данных используют блокировки таблиц для управления транзакциями и это ресурсоемкий процесс. Поэтому мы должны совершить транзакцию, как только закончим с ней. Давайте напишем другую программу, в которой мы будем использовать функцию управления транзакциями JDBC, чтобы убедиться, что целостность данных не нарушена.
API JDBC предоставляет метод setAutoCommit (), с помощью которого мы можем отключить функцию автоматической фиксации соединения. Мы должны отключить автоматическую фиксацию только тогда, когда это необходимо, потому что транзакция не будет зафиксирована, если мы не вызовем метод commit() при подключении. Серверы баз данных используют блокировки таблиц для управления транзакциями и это ресурсоемкий процесс. Поэтому мы должны совершить транзакцию, как только закончим с ней. Давайте напишем другую программу, в которой мы будем использовать функцию управления транзакциями JDBC, чтобы убедиться, что целостность данных не нарушена.
package com.journaldev.jdbc.transaction; import java.sql.Connection; import java.sql.SQLException; public class EmployeeJDBCTransactionExample { public static void main(String[] args) { Connection con = null; try { con = DBConnection.getConnection(); //set auto commit to false con.setAutoCommit(false); EmployeeJDBCInsertExample.insertEmployeeData(con, 1, "Pankaj"); EmployeeJDBCInsertExample.insertAddressData(con, 1, "Albany Dr", "San Jose", "USA"); //now commit transaction con.commit(); } catch (SQLException e) { e.printStackTrace(); try { con.rollback(); System.out.println("JDBC Transaction rolled back successfully"); } catch (SQLException e1) { System.out.println("SQLException in rollback"+e.getMessage()); } } catch (ClassNotFoundException e) { e.printStackTrace(); } finally { try { if (con != null) con.close(); } catch (SQLException e) { e.printStackTrace(); } } } }
Пожалуйста, убедитесь, что вы удалили ранее вставленные данные перед запуском этой программы. Когда вы запустите эту программу, вы получите следующий вывод.
DB Connection created successfully Employee Data inserted successfully for ID=1 com.mysql.jdbc.MysqlDataTruncation: Data truncation: Data too long for column 'city' at row 1 at com.mysql.jdbc.MysqlIO.checkErrorPacket(MysqlIO.java:2939) at com.mysql.jdbc.MysqlIO.sendCommand(MysqlIO.java:1623) at com.mysql.jdbc.MysqlIO.sqlQueryDirect(MysqlIO.java:1715) at com.mysql.jdbc.Connection.execSQL(Connection.java:3249) at com.mysql.jdbc.PreparedStatement.executeInternal(PreparedStatement.java:1268) at com.mysql.jdbc.PreparedStatement.executeUpdate(PreparedStatement.java:1541) at com.mysql.jdbc.PreparedStatement.executeUpdate(PreparedStatement.java:1455) at com.mysql.jdbc.PreparedStatement.executeUpdate(PreparedStatement.java:1440) at com.journaldev.jdbc.transaction.EmployeeJDBCInsertExample.insertAddressData(EmployeeJDBCInsertExample.java:45) at com.journaldev.jdbc.transaction.EmployeeJDBCTransactionExample.main(EmployeeJDBCTransactionExample.java:19) JDBC Transaction rolled back successfully
Вывод аналогичен предыдущей программе, но если вы посмотрите в таблицы базы данных, вы заметите, что данные не вставляются в таблицу сотрудников. Теперь мы можем изменить значение города, чтобы оно помещалось в столбец, и повторно запустить программу для вставки данных в обе таблицы. Обратите внимание, что соединение фиксируется только тогда, когда обе вставки выполняются нормально, и если какая-либо из них вызывает исключение, мы откатываем завершенную транзакцию.
Точка сохранения JDBC
Иногда транзакция может состоять из нескольких операторов, и мы хотели бы выполнить откат к определенной точке транзакции. Точка сохранения JDBC помогает нам создавать контрольные точки в транзакции, и мы можем вернуться к этой конкретной контрольной точке. Любая точка сохранения, созданная для транзакции, автоматически освобождается и становится недействительной при фиксации транзакции или при откате всей транзакции. Откат транзакции обратно в точку сохранения автоматически освобождает и делает недействительными любые другие точки сохранения, созданные после указанной точки сохранения.
Допустим, у нас есть таблица журналов, в которой мы хотим регистрировать сообщения об успешном сохранении информации о сотрудниках. Но так как это только для ведения журнала, если при вставке в таблицу журналов будут какие-либо исключения, мы не хотим откатывать всю транзакцию. Давайте посмотрим, как мы можем достичь этого с помощью точки сохранения JDBC.
CREATE TABLE `Logs` ( `id` int(3) unsigned NOT NULL AUTO_INCREMENT, `message` varchar(10) DEFAULT NULL, PRIMARY KEY (`id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8;
Допустим, у нас есть таблица журналов, в которой мы хотим регистрировать сообщения об успешном сохранении информации о сотрудниках. Но так как это только для ведения журнала, если при вставке в таблицу журналов будут какие-либо исключения, мы не хотим откатывать всю транзакцию. Давайте посмотрим, как мы можем достичь этого с помощью точки сохранения JDBC.
package com.journaldev.jdbc.transaction; import java.sql.Connection; import java.sql.PreparedStatement; import java.sql.SQLException; import java.sql.Savepoint; public class EmployeeJDBCSavePointExample { public static final String INSERT_LOGS_QUERY = "insert into Logs (message) values (?)"; public static void main(String[] args) { Connection con = null; Savepoint savepoint = null; try { con = DBConnection.getConnection(); // set auto commit to false con.setAutoCommit(false); EmployeeJDBCInsertExample.insertEmployeeData(con, 2, "Pankaj"); EmployeeJDBCInsertExample.insertAddressData(con, 2, "Albany Dr", "SFO", "USA"); // if code reached here, means main work is done successfully savepoint = con.setSavepoint("EmployeeSavePoint"); insertLogData(con, 2); // now commit transaction con.commit(); } catch (SQLException e) { e.printStackTrace(); try { if (savepoint == null) { // SQLException occurred in saving into Employee or Address tables con.rollback(); System.out .println("JDBC Transaction rolled back successfully"); } else { // exception occurred in inserting into Logs table // we can ignore it by rollback to the savepoint con.rollback(savepoint); //lets commit now con.commit(); } } catch (SQLException e1) { System.out.println("SQLException in rollback" + e.getMessage()); } } catch (ClassNotFoundException e) { e.printStackTrace(); } finally { try { if (con != null) con.close(); } catch (SQLException e) { e.printStackTrace(); } } } private static void insertLogData(Connection con, int i) throws SQLException { PreparedStatement stmt = con.prepareStatement(INSERT_LOGS_QUERY); //message is very long, will throw SQLException stmt.setString(1, "Employee information saved successfully for ID" + i); stmt.executeUpdate(); System.out.println("Logs Data inserted successfully for ID=" + i); stmt.close(); } }
Программа очень проста для понимания. Как вы можете видеть, я создаю точку сохранения после успешной вставки данных в таблицы сотрудников и адресов. Если возникает исключение SQLException и точка сохранения равна нулю, это означает, что исключение возникает при выполнении запросов вставки для таблицы сотрудников или адресов, и, следовательно, я откатываю полную транзакцию.
Если точка сохранения не равна нулю, это означает, что при вставке данных в таблицу журналов возникает исключение SQLException, поэтому я откатываю транзакцию только до точки сохранения и фиксирую ее.
Если вы запустите вышеуказанную программу, вы увидите результат ниже.
DB Connection created successfully Employee Data inserted successfully for ID=2 Address Data inserted successfully for ID=2 com.mysql.jdbc.MysqlDataTruncation: Data truncation: Data too long for column 'message' at row 1 at com.mysql.jdbc.MysqlIO.checkErrorPacket(MysqlIO.java:2939) at com.mysql.jdbc.MysqlIO.sendCommand(MysqlIO.java:1623) at com.mysql.jdbc.MysqlIO.sqlQueryDirect(MysqlIO.java:1715) at com.mysql.jdbc.Connection.execSQL(Connection.java:3249) at com.mysql.jdbc.PreparedStatement.executeInternal(PreparedStatement.java:1268) at com.mysql.jdbc.PreparedStatement.executeUpdate(PreparedStatement.java:1541) at com.mysql.jdbc.PreparedStatement.executeUpdate(PreparedStatement.java:1455) at com.mysql.jdbc.PreparedStatement.executeUpdate(PreparedStatement.java:1440) at com.journaldev.jdbc.transaction.EmployeeJDBCSavePointExample.insertLogData(EmployeeJDBCSavePointExample.java:73) at com.journaldev.jdbc.transaction.EmployeeJDBCSavePointExample.main(EmployeeJDBCSavePointExample.java:30)
Если вы проверите таблицы базы данных, вы заметите, что данные успешно вставлены в таблицы сотрудников и адресов. Обратите внимание, что мы могли бы легко добиться этого, зафиксировав транзакцию, когда данные успешно вставлены в таблицы сотрудников и адресов, и использовали другую транзакцию для вставки в таблицу журналов. Это всего лишь пример, показывающий использование точки сохранения JDBC в программах Java.
Загрузите проект по ссылке выше и поиграйте с ним, попробуйте использовать несколько точек сохранения и API транзакций JDBC, чтобы узнать больше об этом.