なぜトランザクションが必要なのか

データベースを使用する場合、多くの異なるアクションを実行する必要があるが、それらが一緒になって初めて意味をなすという状況がよく起こります。

たとえば、私たちは次の 3 つのことを実行する銀行ソフトウェアを作成しています。

  • 顧客の口座からお金を引き出す
  • 受取人の口座にお金を追加する
  • 投稿データを「投稿ログ」に記録する

これらのアクションのいずれかの実行中にエラーが発生した場合は、他の 2 つのアクションもキャンセルする必要があります。クライアントからのお金を償却し、受取人に追加しないことは不可能ですか? それとも、受信者には追加しますが、クライアントからは控除されませんか?

したがって、さまざまなアクションを 1 つに論理的にグループ化したものをトランザクションと呼びます。言い換えれば、トランザクションは、すべて同時に実行する必要があるアクションのグループです。いずれかのアクションが失敗した場合、またはエラーで実行された場合は、他のすべてのアクションをキャンセルする必要があります。

通常、トランザクションには次の 3 つの状態があります。

  • 初期状態 - 一連のアクションを実行する前のシステムの状態
  • 成功状態 - アクショングループが完了した後の状態
  • 失敗した状態 - 何か問題が発生しました

この場合、通常は次の 3 つのコマンドがあります。

  • begin/start - アクションの論理グループの開始前に実行されます。
  • commit - トランザクションアクショングループの後に実行されます。
  • ロールバック- システムを障害状態から初期状態に戻すプロセスを開始します。

それはこのように動作します。

まず、トランザクションを開く必要があります。begin ()またはstart()メソッドを呼び出します。このメソッドを呼び出すと、何か問題が発生した場合に戻ろうとするシステムの状態が示されます。

次に、すべてのアクションが実行され、それらは論理グループ (トランザクション) に結合されます。

次にcommit()メソッドが呼び出されます。その呼び出しは、アクションの論理グループの終了を示し、通常はこれらのアクションを実践するプロセスを開始します。

FileWriter でどのように何かを書いたかを思い出してください。まず、書いたものはすべてメモリに保存され、その後、flush ()メソッドが呼び出されるときに、メモリ内のバッファのすべてのデータがディスクに書き込まれます。このflush()はトランザクションのコミットです。

トランザクションの操作中にエラーが発生した場合は、開始状態に戻るプロセスを開始する必要があります。このプロセスはrollback()と呼ばれ、通常は同じ名前のメソッドが担当します。

大まかに言えば、トランザクションを完了するには 2 つの方法があります。

  • COMMIT - 行われたすべての変更を確認します
  • ROLLBACK - 行われたすべての変更をロールバックします

JDBC でのトランザクション

ほぼすべての DBMS はトランザクションを処理できます。したがって、JDBC もこのケースをサポートしています。すべてが非常に簡単に実装されています。

まず、Statement オブジェクトのexecute()メソッドへの各呼び出しは、別個のトランザクションで実行されます。これを行うために、Connection にはAutoCommitパラメータがあります。trueに設定すると、execute()メソッドを呼び出すたびにcommit()が呼び出されます。

次に、1 つのトランザクションで複数のコマンドを実行する場合は、次のように実行できます。

  • 自動コミットを無効にする
  • コマンドを呼び出す
  • commit()メソッドを明示的に呼び出す

とてもシンプルに見えます:

connection.setAutoCommit(false);

Statement statement = connection.createStatement();
int rowsCount1 = statement.executeUpdate("UPDATE  employee SET salary = salary+1000");
int rowsCount2 = statement.executeUpdate("UPDATE  employee SET salary = salary+1000");
int rowsCount3 = statement.executeUpdate("UPDATE  employee SET salary = salary+1000");

connection.commit();

commit()メソッドの実行中にサーバーでエラーが発生した場合、SQL サーバーは 3 つのアクションをすべてキャンセルします。

しかし、クライアント側でエラーが依然として発生し、commit()メソッド呼び出しに到達しない状況があります。

connection.setAutoCommit(false);

Statement statement = connection.createStatement();
int rowsCount1 = statement.executeUpdate("UPDATE  employee SET salary = salary+1000");
int rowsCount2 = statement.executeUpdate("UPDATE  employee SET salary = salary+1000");
int rowsCount3 = statement.executeUpdate("UPDATE multiple typos will result in an exception");

connection.commit();

1 つのuseUpdate()の実行中にエラーが発生した場合、commit()メソッドは呼び出されません。実行されたすべてのアクションをロールバックするには、 rollback()メソッドを呼び出す必要があります。通常は次のようになります。

try{
  	connection.setAutoCommit(false);

  	Statement statement = connection.createStatement();
  	int rowsCount1 = statement.executeUpdate("UPDATE  employee SET salary = salary+1000");
  	int rowsCount2 = statement.executeUpdate("UPDATE  employee SET salary = salary+1000");
  	int rowsCount3 = statement.executeUpdate("UPDATE multiple typos will result in an exception");

	  connection.commit();
 }
 catch (Exception e) {
   connection.rollback();
}

セーブポイント

JDBC 3.0 の登場により、トランザクション ロールバックをより効率的に操作できるようになりました。セーブ ポイントを設定できるようになりました。セーブ ポイントを設定し、 rollback()操作を呼び出すと、特定のセーブ ポイントにロールバックできます。

保存するには、セーブポイントを作成する必要があります。これは次のコマンドで行います。

Savepoint save = connection.setSavepoint();

セーブポイントに戻すには、次のコマンドを使用します。

connection.rollback(save);

問題のあるコマンドの前にセーブポイントを追加してみましょう。

try{
  	connection.setAutoCommit(false);

  	Statement statement = connection.createStatement();
  	int rowsCount1 = statement.executeUpdate("UPDATE  employee SET salary = salary+1000");
  	int rowsCount2 = statement.executeUpdate("UPDATE  employee SET salary = salary+1000");

  	Savepoint save = connection.setSavepoint();
 	 try{
      	int rowsCount3 = statement.executeUpdate("UPDATE multiple typos will result in an exception");
 	 }
 	 catch (Exception e) {
    	   connection.rollback(save);
 	 }

	  connection.commit();
 }
 catch (Exception e) {
   connection.rollback();
}

問題のあるメソッドを呼び出す前にセーブポイントを追加し、 rollback(save)メソッドを呼び出して保存された状態に戻すことで、ネストされたトランザクションを整理しました。

はい、ゲームのセーブ/ロードとよく似ています。