Рефакторинг jdbc-transaction

This commit is contained in:
Struchkov Mark 2022-11-27 10:17:44 +03:00
parent b9dd311fb5
commit 9d9a4ba556
6 changed files with 121 additions and 85 deletions

View File

@ -14,17 +14,16 @@ public class JdbcSimpleExample {
public static void main(String[] args) {
final JdbcSimpleExample jdbcSimpleExample = new JdbcSimpleExample();
// jdbcExample.runNoTransaction(2L, 1L, 100L);
jdbcSimpleExample.runWithTransaction(2L, 1L, 100L);
jdbcSimpleExample.runNoTransaction(2L, 1L, 100L);
// jdbcSimpleExample.runWithTransaction(2L, 1L, 100L);
}
@SneakyThrows
private void runNoTransaction(Long personIdFrom, Long personIdTo, Long amount) {
final Connection connection = Repository.getConnection();
sendMoney(connection, personIdFrom, personIdTo, amount);
connection.close();
try (final Connection connection = Repository.getConnection()) {
sendMoney(connection, personIdFrom, personIdTo, amount);
} catch (SQLException e) {
System.err.println(e.getMessage());
}
}
@SneakyThrows

View File

@ -1,61 +0,0 @@
package dev.struchkov.example.transaction;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
public class RepeatableReadExample {
public static final String READ = "SELECT person.balance FROM person WHERE id = ?";
public static final String UPDATE = "UPDATE person SET balance = ? WHERE id = ?";
public static void main(String[] args) {
try {
final Connection connectionOne = Repository.getConnection();
final Connection connectionTwo = Repository.getConnection();
connectionOne.setAutoCommit(false);
connectionTwo.setAutoCommit(false);
final int transactionLevel = Connection.TRANSACTION_REPEATABLE_READ;
connectionOne.setTransactionIsolation(transactionLevel);
connectionTwo.setTransactionIsolation(transactionLevel);
final PreparedStatement readOne = connectionOne.prepareStatement(READ);
readOne.setLong(1, 1);
final PreparedStatement readTwo = connectionTwo.prepareStatement(READ);
readTwo.setLong(1, 1);
final ResultSet resultSetOne = readOne.executeQuery();
resultSetOne.next();
final long balanceOne = resultSetOne.getLong(1);
final ResultSet resultSetTwo = readTwo.executeQuery();
resultSetTwo.next();
final long balanceTwo = resultSetTwo.getLong(1);
final PreparedStatement updateOne = connectionOne.prepareStatement(UPDATE);
updateOne.setLong(1, balanceOne + 10);
updateOne.setLong(2, 1);
updateOne.execute();
connectionOne.commit();
connectionOne.close();
final PreparedStatement updateTwo = connectionTwo.prepareStatement(UPDATE);
updateTwo.setLong(1, balanceTwo + 5);
updateTwo.setLong(2, 1);
updateTwo.execute();
connectionTwo.commit();
connectionTwo.close();
} catch (SQLException e) {
System.out.println(e);
}
}
}

View File

@ -7,17 +7,22 @@ import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
/**
* <p><b>«Грязное» чтение (dirty reads)</b> в результатах запроса появляются промежуточные результаты параллельной транзакции, которая ещё не завершилась.</p>
*/
public class DirtyReadExample {
private static final int ISOLATION_LEVEL = Connection.TRANSACTION_READ_UNCOMMITTED;
public static void main(String[] args) throws SQLException, InterruptedException {
try (
final Connection connection = Repository.getConnectionH2();
final Statement statement = connection.createStatement()
) {
connection.setAutoCommit(false);
connection.setTransactionIsolation(Connection.TRANSACTION_READ_UNCOMMITTED);
connection.setTransactionIsolation(ISOLATION_LEVEL);
statement.executeUpdate("UPDATE person SET balance = 100000 WHERE id = 1");
statement.executeUpdate("UPDATE person SET balance = 100000 WHERE id = 1");
new OtherTransaction().start();
Thread.sleep(2000);
@ -34,11 +39,11 @@ public class DirtyReadExample {
final Statement statement = connection.createStatement()
) {
connection.setAutoCommit(false);
connection.setTransactionIsolation(Connection.TRANSACTION_READ_UNCOMMITTED);
connection.setTransactionIsolation(ISOLATION_LEVEL);
final ResultSet resultSet = statement.executeQuery("SELECT * FROM person WHERE id = 1");
while (resultSet.next()) {
System.out.println(resultSet.getString("balance"));
System.out.println("Balance: " + resultSet.getString("balance"));
}
} catch (SQLException e) {
System.out.println(e.getMessage());

View File

@ -0,0 +1,71 @@
package dev.struchkov.example.transaction.problems;
import dev.struchkov.example.transaction.Repository;
import lombok.SneakyThrows;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
/**
* <p><b>Потерянное обновление (lost update)</b> две параллельные транзакции меняют одни и те же данные, при этом итоговый результат обновления предсказать невозможно.</p>
*/
public class LostUpdateExample {
public static final String READ = "SELECT person.balance FROM person WHERE id = ?";
public static final String UPDATE = "UPDATE person SET balance = ? WHERE id = ?";
@SneakyThrows
public static void main(String[] args) {
// Начинаем две транзакции.
final Connection connectionOne = getNewConnection();
final Connection connectionTwo = getNewConnection();
// Первая и вторая транзакция запрашивают баланс пользователя.
// balance = 1000
final long balanceOne = getBalance(connectionOne);
final long balanceTwo = getBalance(connectionTwo);
// Первая транзакция готовится обновить баланс пользователю.
final PreparedStatement updateOne = connectionOne.prepareStatement(UPDATE);
updateOne.setLong(1, balanceOne + 10);
updateOne.setLong(2, 1);
updateOne.execute();
// Первая транзакция фиксирует изменения и завершается.
// Значение balance в базе в этот момент = 1010.
connectionOne.commit();
connectionOne.close();
// Но вторая транзакция ничего не знает про изменения в БД.
// Значение balanceTwo все еще равно 1000, к этому значению мы добавляем 5.
final PreparedStatement updateTwo = connectionTwo.prepareStatement(UPDATE);
updateTwo.setLong(1, balanceTwo + 5);
updateTwo.setLong(2, 1);
updateTwo.execute();
// Вторая транзакция фиксирует свои изменения и завершается.
// В итоге в БД остается значение 1005, а не 1015, как хотелось бы нам.
connectionTwo.commit();
connectionTwo.close();
}
private static Connection getNewConnection() throws SQLException {
final Connection connection = Repository.getConnection();
connection.setAutoCommit(false);
connection.setTransactionIsolation(Connection.TRANSACTION_REPEATABLE_READ);
return connection;
}
private static long getBalance(Connection connectionOne) throws SQLException {
final PreparedStatement preparedStatement = connectionOne.prepareStatement(READ);
preparedStatement.setLong(1, 1);
final ResultSet resultSet = preparedStatement.executeQuery();
resultSet.next();
final long balanceOne = resultSet.getLong(1);
return balanceOne;
}
}

View File

@ -7,19 +7,25 @@ import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
/**
* <p><b>Неповторяющееся чтение (non-repeatable reads)</b> запрос с одними и теми же условиями даёт неодинаковые результаты в рамках транзакции.</p>
*/
public class NonRepeatableRead {
private static final int ISOLATION_LEVEL = Connection.TRANSACTION_READ_COMMITTED;
public static void main(String[] args) {
try(
try (
final Connection connection = Repository.getConnection();
final Statement statement = connection.createStatement()
) {
connection.setAutoCommit(false);
connection.setTransactionIsolation(Connection.TRANSACTION_READ_COMMITTED);
connection.setTransactionIsolation(ISOLATION_LEVEL);
final ResultSet resultSet = statement.executeQuery("SELECT * FROM person WHERE id = 1");
while (resultSet.next()) {
System.out.println(resultSet.getString("balance"));
final ResultSet resultSetOne = statement.executeQuery("SELECT * FROM person WHERE id = 1");
while (resultSetOne.next()) {
final String balance = resultSetOne.getString("balance");
System.out.println("[one] Balance: " + balance);
}
new OtherTransaction().start();
@ -27,7 +33,8 @@ public class NonRepeatableRead {
final ResultSet resultSetTwo = statement.executeQuery("SELECT * FROM person WHERE id = 1");
while (resultSetTwo.next()) {
System.out.println(resultSetTwo.getString("balance"));
final String balance = resultSetTwo.getString("balance");
System.out.println("[one] Balance: " + balance);
}
} catch (SQLException | InterruptedException e) {
@ -43,10 +50,18 @@ public class NonRepeatableRead {
final Statement statement = connection.createStatement()
) {
connection.setAutoCommit(false);
connection.setTransactionIsolation(Connection.TRANSACTION_READ_COMMITTED);
connection.setTransactionIsolation(ISOLATION_LEVEL);
statement.executeUpdate("UPDATE person SET balance = 100000 WHERE id = 1");
connection.commit();
final ResultSet resultSetTwo = statement.executeQuery("SELECT * FROM person WHERE id = 1");
while (resultSetTwo.next()) {
final String balance = resultSetTwo.getString("balance");
System.out.println("[two] Balance: " + balance);
}
connection.commit();
} catch (SQLException e) {
System.out.println(e.getMessage());
}

View File

@ -7,19 +7,25 @@ import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
/**
* <p><b>Фантомное чтение (phantom reads)</b> в результатах повторяющегося запроса появляются и исчезают строки, которые в данный момент модифицирует параллельная транзакция.</p>
*/
public class PhantomRead {
private static final int ISOLATION_LEVEL = Connection.TRANSACTION_READ_COMMITTED;
public static void main(String[] args) {
try(
final Connection connection = Repository.getConnection();
final Statement statement = connection.createStatement()
) {
connection.setAutoCommit(false);
connection.setTransactionIsolation(Connection.TRANSACTION_READ_COMMITTED);
connection.setTransactionIsolation(ISOLATION_LEVEL);
final ResultSet resultSet = statement.executeQuery("SELECT count(*) FROM person");
while (resultSet.next()) {
System.out.println(resultSet.getInt(1));
final int count = resultSet.getInt(1);
System.out.println("Count: " + count);
}
new OtherTransaction().start();
@ -27,7 +33,8 @@ public class PhantomRead {
final ResultSet resultSetTwo = statement.executeQuery("SELECT count(*) FROM person");
while (resultSetTwo.next()) {
System.out.println(resultSetTwo.getInt(1));
final int count = resultSetTwo.getInt(1);
System.out.println("Count: " + count);
}
} catch (SQLException | InterruptedException e) {
@ -43,9 +50,9 @@ public class PhantomRead {
final Statement statement = connection.createStatement()
) {
connection.setAutoCommit(false);
connection.setTransactionIsolation(Connection.TRANSACTION_READ_COMMITTED);
connection.setTransactionIsolation(ISOLATION_LEVEL);
statement.executeUpdate("INSERT INTO person(name, balance) values ('test', 100)");
statement.executeUpdate("INSERT INTO person(id, balance) values (3, 1000)");
connection.commit();
} catch (SQLException e) {
System.out.println(e.getMessage());