Перейти к основному содержимому

Руководство по автоматической фиксации в JDBC

· 8 мин. чтения

1. Введение

Подключения к базе данных , созданные с помощью JDBC API, имеют функцию, называемую режимом автоматической фиксации .

Включение этого режима может помочь избавиться от стандартного кода, необходимого для управления транзакциями . Однако, несмотря на это, его цель и то, как он влияет на обработку транзакций при выполнении операторов SQL , иногда могут быть неясны.

В этой статье мы обсудим, что такое режим автоматической фиксации и как его правильно использовать как для автоматического, так и для явного управления транзакциями. Мы также рассмотрим различные проблемы, которых следует избегать при включенной или выключенной автоматической фиксации.

2. Что такое режим автоматической фиксации JDBC?

Разработчики не обязательно понимают, как эффективно управлять транзакциями базы данных при использовании JDBC. Таким образом, при обработке транзакций вручную разработчики могут не запускать их там, где это необходимо, или не запускать их вообще. Та же проблема применима к выдаче коммитов или откатов , где это необходимо.

Чтобы обойти эту проблему, режим автоматической фиксации в JDBC предоставляет способ выполнения операторов SQL с автоматическим управлением транзакциями драйвером JDBC .

Таким образом, назначение режима автоматической фиксации состоит в том, чтобы снять с разработчиков бремя самостоятельного управления транзакциями. Таким образом, его включение может упростить разработку приложений с помощью JDBC API. Конечно, это помогает только в тех случаях, когда допустимо сохранение обновлений данных сразу после завершения каждого оператора SQL.

3. Автоматическое управление транзакциями, когда Auto-Commit имеет значение True

Драйверы JDBC по умолчанию включают режим автоматической фиксации для новых подключений к базе данных. Когда он включен, они автоматически запускают каждый отдельный оператор SQL внутри своей транзакции.

Помимо использования этого параметра по умолчанию, мы также можем включить автоматическую фиксацию вручную, передав true методу соединения setAutoCommit :

connection.setAutoCommit(true);

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

Теперь, когда мы рассмотрели, как включить автоматическую фиксацию, мы продемонстрируем, что при такой установке драйверы JDBC запускают операторы SQL в своих собственных транзакциях. То есть они автоматически сразу же передают любые обновления данных из каждого оператора в базу данных.

3.1. Настройка примера кода

В этом примере мы будем использовать базу данных H2 в памяти для хранения наших данных. Чтобы использовать его, нам сначала нужно определить зависимость Maven :

<dependency>
<groupId>com.h2database</groupId>
<artifactId>h2</artifactId>
<version>1.4.200</version>
</dependency>

Для начала давайте создадим таблицу базы данных для хранения сведений о людях:

CREATE TABLE Person (
id INTEGER not null,
name VARCHAR(50),
lastName VARCHAR(50),
age INTEGER,PRIMARY KEY (id)
)

Далее мы создадим два подключения к базе данных. Мы будем использовать первый для выполнения наших SQL-запросов и обновлений таблицы. И мы будем использовать второе соединение, чтобы проверить, были ли внесены обновления в эту таблицу:

Connection connection1 = DriverManager.getConnection("jdbc:h2:mem:testdb", "sa", "");
Connection connection2 = DriverManager.getConnection("jdbc:h2:mem:testdb", "sa", "");

Обратите внимание: нам нужно использовать отдельное соединение для проверки зафиксированных данных. Это связано с тем, что если мы запустим какие-либо запросы на выборку при первом соединении, они увидят обновления, которые еще не были зафиксированы.

Теперь мы создадим POJO для представления записи базы данных, содержащей информацию о человеке:

public class Person {

private Integer id;
private String name;
private String lastName;
private Integer age;

// standard constructor, getters, and setters
}

Чтобы вставить запись в нашу таблицу, давайте создадим метод с именем insertPerson :

private static int insertPerson(Connection connection, Person person) throws SQLException {    
try (PreparedStatement preparedStatement = connection.prepareStatement(
"INSERT INTO Person VALUES (?,?,?,?)")) {

preparedStatement.setInt(1, person.getId());
preparedStatement.setString(2, person.getName());
preparedStatement.setString(3, person.getLastName());
preparedStatement.setInt(4, person.getAge());

return preparedStatement.executeUpdate();
}
}

Затем мы добавим метод updatePersonAgeById для обновления определенной записи в таблице:

private static void updatePersonAgeById(Connection connection, int id, int newAge) throws SQLException {
try (PreparedStatement preparedStatement = connection.prepareStatement(
"UPDATE Person SET age = ? WHERE id = ?")) {
preparedStatement.setInt(1, newAge);
preparedStatement.setInt(2, id);

preparedStatement.executeUpdate();
}
}

Наконец, давайте добавим метод selectAllPeople для выбора всех записей из таблицы. Мы будем использовать это для проверки результатов наших операторов вставки и обновления SQL :

private static List selectAllPeople(Connection connection) throws SQLException {

List people = null;

try (Statement statement = connection.createStatement()) {
people = new ArrayList();
ResultSet resultSet = statement.executeQuery("SELECT * FROM Person");

while (resultSet.next()) {
Person person = new Person();
person.setId(resultSet.getInt("id"));
person.setName(resultSet.getString("name"));
person.setLastName(resultSet.getString("lastName"));
person.setAge(resultSet.getInt("age"));

people.add(person);
}
}

return people;
}

Имея эти служебные методы, мы проверим влияние включения автоматической фиксации.

3.2. Запуск тестов

Чтобы протестировать наш пример кода, давайте сначала вставим человека в таблицу. Затем после этого из другого подключения мы проверим, что база данных была обновлена, не выдавая коммит :

Person person = new Person(1, "John", "Doe", 45);
insertPerson(connection1, person);

List people = selectAllPeople(connection2);
assertThat("person record inserted OK into empty table", people.size(), is(equalTo(1)));
Person personInserted = people.iterator().next();
assertThat("id correct", personInserted.getId(), is(equalTo(1)));

Затем, вставив эту новую запись в таблицу, давайте обновим возраст человека. После этого мы проверим из второго подключения, что изменение было сохранено в базе данных без необходимости вызывать commit :

updatePersonAgeById(connection1, 1, 65);

people = selectAllPeople(connection2);
Person personUpdated = people.iterator().next();
assertThat("updated age correct", personUpdated.getAge(), is(equalTo(65)));

Таким образом, с помощью наших тестов мы подтвердили, что при включенном режиме автоматической фиксации драйвер JDBC неявно выполняет каждый оператор SQL в своей собственной транзакции. Таким образом, нам не нужно вызывать commit для сохранения обновлений в базе данных самостоятельно.

4. Явное управление транзакциями, когда автофиксация неверна

Нам нужно отключить режим автоматической фиксации, когда мы хотим сами обрабатывать транзакции и группировать несколько операторов SQL в одну транзакцию.

Мы делаем это, передавая false методу соединения setAutoCommit :

connection.setAutoCommit(false);

Когда режим автоматической фиксации выключен, нам нужно вручную отмечать конец каждой транзакции, вызывая либо фиксацию , либо откат для соединения.

Однако следует отметить, что даже при отключенной автоматической фиксации драйвер JDBC по-прежнему будет автоматически запускать транзакцию для нас, когда это необходимо. Например, это происходит перед тем, как мы запустим нашу первую инструкцию SQL, а также после каждой фиксации или отката.

Давайте продемонстрируем, что когда мы выполняем несколько операторов SQL с отключенной автоматической фиксацией, результирующие обновления будут сохраняться в базе данных только при вызове commit .

4.1. Запуск тестов

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

Person person = new Person(1, "John", "Doe", 45);
insertPerson(connection1, person);

List<Person> people = selectAllPeople(connection2);
assertThat("No people have been inserted into database yet", people.size(), is(equalTo(0)));

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

updatePersonAgeById(connection1, 1, 65);

people = selectAllPeople(connection2);
assertThat("No people have been inserted into database yet", people.size(), is(equalTo(0)));

Чтобы завершить наше тестирование, мы вызовем commit , а затем подтвердим, что теперь мы можем видеть все обновления в базе данных, используя второе соединение:

connection1.commit();

people = selectAllPeople(connection2);
Person personUpdated = people.iterator().next();
assertThat("person's age updated to 65", personUpdated.getAge(), is(equalTo(65)));

Как мы убедились в наших тестах выше, когда режим автоматической фиксации выключен, нам нужно вручную вызвать фиксацию , чтобы сохранить наши изменения в базе данных. Это позволит сохранить любые обновления всех операторов SQL, которые мы выполнили с момента начала нашей текущей транзакции. То есть либо с тех пор, как мы открыли соединение, если это наша первая транзакция, либо, в противном случае, после нашей последней фиксации или отката .

5. Соображения и потенциальные проблемы

Нам может быть удобно запускать операторы SQL с включенной автоматической фиксацией для относительно тривиальных приложений. То есть там, где нет необходимости в ручном управлении транзакциями. Однако в более сложных ситуациях мы должны учитывать, что когда драйвер JDBC обрабатывает транзакции автоматически, это может иногда приводить к нежелательным побочным эффектам или проблемам.

Одна вещь, которую мы должны учитывать, заключается в том, что когда включена автоматическая фиксация, это может привести к потере значительного времени и ресурсов обработки . Это связано с тем, что драйвер запускает каждую инструкцию SQL в своей собственной транзакции, независимо от того, необходимо это или нет.

Например, если мы выполняем один и тот же оператор много раз с разными значениями, каждый вызов включается в собственную транзакцию. Следовательно, это может привести к ненужным накладным расходам на выполнение и управление ресурсами.

Таким образом, в таких случаях нам обычно лучше отключить автоматическую фиксацию и явно объединить несколько вызовов одного и того же оператора SQL в одну транзакцию. При этом мы, вероятно, увидим существенное улучшение производительности приложений.

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

6. Заключение

В этой статье мы обсудили назначение режима автоматической фиксации в JDBC API. Мы также рассмотрели, как включить как неявное, так и явное управление транзакциями, включив или выключив его соответственно. Наконец, мы коснулись различных вопросов или проблем, которые следует учитывать при его использовании.

Как всегда, полный исходный код примеров можно найти на GitHub .