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) {
|
public static void main(String[] args) {
|
||||||
final JdbcSimpleExample jdbcSimpleExample = new JdbcSimpleExample();
|
final JdbcSimpleExample jdbcSimpleExample = new JdbcSimpleExample();
|
||||||
// jdbcExample.runNoTransaction(2L, 1L, 100L);
|
jdbcSimpleExample.runNoTransaction(2L, 1L, 100L);
|
||||||
jdbcSimpleExample.runWithTransaction(2L, 1L, 100L);
|
// jdbcSimpleExample.runWithTransaction(2L, 1L, 100L);
|
||||||
}
|
}
|
||||||
|
|
||||||
@SneakyThrows
|
|
||||||
private void runNoTransaction(Long personIdFrom, Long personIdTo, Long amount) {
|
private void runNoTransaction(Long personIdFrom, Long personIdTo, Long amount) {
|
||||||
final Connection connection = Repository.getConnection();
|
try (final Connection connection = Repository.getConnection()) {
|
||||||
|
sendMoney(connection, personIdFrom, personIdTo, amount);
|
||||||
sendMoney(connection, personIdFrom, personIdTo, amount);
|
} catch (SQLException e) {
|
||||||
|
System.err.println(e.getMessage());
|
||||||
connection.close();
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@SneakyThrows
|
@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.SQLException;
|
||||||
import java.sql.Statement;
|
import java.sql.Statement;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* <p><b>«Грязное» чтение (dirty reads)</b> — в результатах запроса появляются промежуточные результаты параллельной транзакции, которая ещё не завершилась.</p>
|
||||||
|
*/
|
||||||
public class DirtyReadExample {
|
public class DirtyReadExample {
|
||||||
|
|
||||||
|
private static final int ISOLATION_LEVEL = Connection.TRANSACTION_READ_UNCOMMITTED;
|
||||||
|
|
||||||
public static void main(String[] args) throws SQLException, InterruptedException {
|
public static void main(String[] args) throws SQLException, InterruptedException {
|
||||||
try (
|
try (
|
||||||
final Connection connection = Repository.getConnectionH2();
|
final Connection connection = Repository.getConnectionH2();
|
||||||
final Statement statement = connection.createStatement()
|
final Statement statement = connection.createStatement()
|
||||||
) {
|
) {
|
||||||
connection.setAutoCommit(false);
|
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();
|
new OtherTransaction().start();
|
||||||
Thread.sleep(2000);
|
Thread.sleep(2000);
|
||||||
@ -34,11 +39,11 @@ public class DirtyReadExample {
|
|||||||
final Statement statement = connection.createStatement()
|
final Statement statement = connection.createStatement()
|
||||||
) {
|
) {
|
||||||
connection.setAutoCommit(false);
|
connection.setAutoCommit(false);
|
||||||
connection.setTransactionIsolation(Connection.TRANSACTION_READ_UNCOMMITTED);
|
connection.setTransactionIsolation(ISOLATION_LEVEL);
|
||||||
|
|
||||||
final ResultSet resultSet = statement.executeQuery("SELECT * FROM person WHERE id = 1");
|
final ResultSet resultSet = statement.executeQuery("SELECT * FROM person WHERE id = 1");
|
||||||
while (resultSet.next()) {
|
while (resultSet.next()) {
|
||||||
System.out.println(resultSet.getString("balance"));
|
System.out.println("Balance: " + resultSet.getString("balance"));
|
||||||
}
|
}
|
||||||
} catch (SQLException e) {
|
} catch (SQLException e) {
|
||||||
System.out.println(e.getMessage());
|
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.SQLException;
|
||||||
import java.sql.Statement;
|
import java.sql.Statement;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* <p><b>Неповторяющееся чтение (non-repeatable reads)</b> — запрос с одними и теми же условиями даёт неодинаковые результаты в рамках транзакции.</p>
|
||||||
|
*/
|
||||||
public class NonRepeatableRead {
|
public class NonRepeatableRead {
|
||||||
|
|
||||||
|
private static final int ISOLATION_LEVEL = Connection.TRANSACTION_READ_COMMITTED;
|
||||||
|
|
||||||
public static void main(String[] args) {
|
public static void main(String[] args) {
|
||||||
try(
|
try (
|
||||||
final Connection connection = Repository.getConnection();
|
final Connection connection = Repository.getConnection();
|
||||||
final Statement statement = connection.createStatement()
|
final Statement statement = connection.createStatement()
|
||||||
) {
|
) {
|
||||||
connection.setAutoCommit(false);
|
connection.setAutoCommit(false);
|
||||||
connection.setTransactionIsolation(Connection.TRANSACTION_READ_COMMITTED);
|
connection.setTransactionIsolation(ISOLATION_LEVEL);
|
||||||
|
|
||||||
final ResultSet resultSet = statement.executeQuery("SELECT * FROM person WHERE id = 1");
|
final ResultSet resultSetOne = statement.executeQuery("SELECT * FROM person WHERE id = 1");
|
||||||
while (resultSet.next()) {
|
while (resultSetOne.next()) {
|
||||||
System.out.println(resultSet.getString("balance"));
|
final String balance = resultSetOne.getString("balance");
|
||||||
|
System.out.println("[one] Balance: " + balance);
|
||||||
}
|
}
|
||||||
|
|
||||||
new OtherTransaction().start();
|
new OtherTransaction().start();
|
||||||
@ -27,7 +33,8 @@ public class NonRepeatableRead {
|
|||||||
|
|
||||||
final ResultSet resultSetTwo = statement.executeQuery("SELECT * FROM person WHERE id = 1");
|
final ResultSet resultSetTwo = statement.executeQuery("SELECT * FROM person WHERE id = 1");
|
||||||
while (resultSetTwo.next()) {
|
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) {
|
} catch (SQLException | InterruptedException e) {
|
||||||
@ -43,10 +50,18 @@ public class NonRepeatableRead {
|
|||||||
final Statement statement = connection.createStatement()
|
final Statement statement = connection.createStatement()
|
||||||
) {
|
) {
|
||||||
connection.setAutoCommit(false);
|
connection.setAutoCommit(false);
|
||||||
connection.setTransactionIsolation(Connection.TRANSACTION_READ_COMMITTED);
|
connection.setTransactionIsolation(ISOLATION_LEVEL);
|
||||||
|
|
||||||
statement.executeUpdate("UPDATE person SET balance = 100000 WHERE id = 1");
|
statement.executeUpdate("UPDATE person SET balance = 100000 WHERE id = 1");
|
||||||
connection.commit();
|
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) {
|
} catch (SQLException e) {
|
||||||
System.out.println(e.getMessage());
|
System.out.println(e.getMessage());
|
||||||
}
|
}
|
||||||
|
@ -7,19 +7,25 @@ import java.sql.ResultSet;
|
|||||||
import java.sql.SQLException;
|
import java.sql.SQLException;
|
||||||
import java.sql.Statement;
|
import java.sql.Statement;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* <p><b>Фантомное чтение (phantom reads)</b> — в результатах повторяющегося запроса появляются и исчезают строки, которые в данный момент модифицирует параллельная транзакция.</p>
|
||||||
|
*/
|
||||||
public class PhantomRead {
|
public class PhantomRead {
|
||||||
|
|
||||||
|
private static final int ISOLATION_LEVEL = Connection.TRANSACTION_READ_COMMITTED;
|
||||||
|
|
||||||
public static void main(String[] args) {
|
public static void main(String[] args) {
|
||||||
try(
|
try(
|
||||||
final Connection connection = Repository.getConnection();
|
final Connection connection = Repository.getConnection();
|
||||||
final Statement statement = connection.createStatement()
|
final Statement statement = connection.createStatement()
|
||||||
) {
|
) {
|
||||||
connection.setAutoCommit(false);
|
connection.setAutoCommit(false);
|
||||||
connection.setTransactionIsolation(Connection.TRANSACTION_READ_COMMITTED);
|
connection.setTransactionIsolation(ISOLATION_LEVEL);
|
||||||
|
|
||||||
final ResultSet resultSet = statement.executeQuery("SELECT count(*) FROM person");
|
final ResultSet resultSet = statement.executeQuery("SELECT count(*) FROM person");
|
||||||
while (resultSet.next()) {
|
while (resultSet.next()) {
|
||||||
System.out.println(resultSet.getInt(1));
|
final int count = resultSet.getInt(1);
|
||||||
|
System.out.println("Count: " + count);
|
||||||
}
|
}
|
||||||
|
|
||||||
new OtherTransaction().start();
|
new OtherTransaction().start();
|
||||||
@ -27,7 +33,8 @@ public class PhantomRead {
|
|||||||
|
|
||||||
final ResultSet resultSetTwo = statement.executeQuery("SELECT count(*) FROM person");
|
final ResultSet resultSetTwo = statement.executeQuery("SELECT count(*) FROM person");
|
||||||
while (resultSetTwo.next()) {
|
while (resultSetTwo.next()) {
|
||||||
System.out.println(resultSetTwo.getInt(1));
|
final int count = resultSetTwo.getInt(1);
|
||||||
|
System.out.println("Count: " + count);
|
||||||
}
|
}
|
||||||
|
|
||||||
} catch (SQLException | InterruptedException e) {
|
} catch (SQLException | InterruptedException e) {
|
||||||
@ -43,9 +50,9 @@ public class PhantomRead {
|
|||||||
final Statement statement = connection.createStatement()
|
final Statement statement = connection.createStatement()
|
||||||
) {
|
) {
|
||||||
connection.setAutoCommit(false);
|
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();
|
connection.commit();
|
||||||
} catch (SQLException e) {
|
} catch (SQLException e) {
|
||||||
System.out.println(e.getMessage());
|
System.out.println(e.getMessage());
|
||||||
|
Loading…
x
Reference in New Issue
Block a user