mirror of
https://github.com/Example-uPagge/transactional.git
synced 2024-06-14 11:52:55 +03:00
Рефакторинг jdbc-transaction
This commit is contained in:
parent
b9dd311fb5
commit
9d9a4ba556
@ -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
|
||||
|
@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
@ -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());
|
||||
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
@ -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());
|
||||
}
|
||||
|
@ -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());
|
||||
|
Loading…
Reference in New Issue
Block a user