トランザクション処理

3.1 トランザクション処理

 今までのプログラムでは、SQLの処理をひとつひとつ実行してきましたが、関連する複数の処理を大きな一つの処理として扱う、トランザクションと呼ばれる処理を利用することもできます。本節では、JDBCプログラムでのトランザクション処理について学習していきます。

3.1.1 トランザクション処理の準備

 トランザクション処理とは、関連する複数の処理を大きな1つの処理として扱うことです。関連した複数の処理がトラブル等により途中で止まってしまった場合に、データの不整合を防ぐことができます。トランザクション処理の概念については、当スクールのオリジナルテキスト「MySQLデータベースとSQL基礎Ⅰ」をご確認下さい。
 今までのプログラムではデータベースに対して更新処理を実行した場合、すぐに更新内容がデータベースに反映していました。これはデータベースのオートコミットと呼ばれる機能が有効になっているためです。コミットとは「処理を確定すること」です。そのため、オートコミットが有効な場合は自動で処理を確定する(自動でデータに反映する)ことになります。
 もし、データを1つ1つの処理では確定させず、任意のタイミングで確定させたい場合には、オートコミット機能を無効にし、一連の処理に問題がないと判断したタイミングで処理を確定する必要があります。

図 3.1.1 オートコミットとデータの反映

 JDBC(java.sqlパッケージ)内のConnectionクラスには、オートコミットの設定を変更するメソッドや、処理を確定するメソッドなど、トランザクションに関連するメソッドが定義されています。
 Connectionクラスに定義されたトランザクションに関連するメソッドには以下のようなメソッドがあります。


表 3.1.1 : Connectionクラスのトランザクション関連のメソッド

3.1.2 JDBCを利用したトランザクションの基本構文

 JDBCを利用したトランザクション処理を行う場合、以下の流れで処理を実行します。

 ① JDBC(java.sqlパッケージ)をインポートする。
 ② JDBCドライバを読み込む。
 ③ データベースと接続する。
 ④ オートコミット機能を無効にする。
   con.setAutoCommit(false);
 ⑤ SQL文をデータベースへ送信するための準備を行う。
 ⑥ SQL文をデータベースへ送信し、更新された件数を受け取る。
 ⑦ コミット、またはロールバックを実行する。
   con.commit();
   con.rollback();

 ⑧ リソースを解放する。

 トランザクション処理を行うために追加された処理は④と⑦の処理になります。
 データベースと接続した段階で、オートコミット機能を無効にします。また、一連のSQL文を実行した後に、データに問題がなければコミット処理、問題があればロールバック処理を行います。

 では、実際にプログラムを作成してみましょう。まずは、トランザクションを行なっていない今まで通りのプログラムを作成します。

トランザクション処理を行わないプログラム

 このプログラムは、トランザクション処理を行わずにSQL文を実行するプログラムです。SQL文を実行するとすぐにデータに反映することを確認しましょう。

① ソース・フォルダー      :myjdbc_kanda/src
② パッケージ          :jp.co.f1.jdbc.ch03
③ 名前             :SampleNotTransaction
④ 作成するメソッド・スタブの選択:public static void main(String[] args) にチェックを入れる

➢ SampleNotTransaction.java
package jp.co.f1.jdbc.ch03;

import java.sql.*;

public class SampleNotTransaction {

	//接続用の情報をフィールドに定数として定義
	private static String RDB_DRIVE="com.mysql.jdbc.Driver";
	private static String URL="jdbc:mysql://localhost/mybookdb";
 	private static String USER="bms";
 	private static String PASSWD="bms123";
 
 	public static void main(String[] args) {
 
 		String	sql = null;
 		int		num	= 0 ;
 
 		Connection con = null;
 		Statement  smt   = null;
 
 		try {
 			Class.forName(RDB_DRIVE);
 			con = DriverManager.getConnection(URL,USER,PASSWD);
 
 			smt = con.createStatement();
 
 			System.out.println("■登録SQL発行前の書籍一覧表示");
 			selectAll();
 
 			sql = "INSERT INTO bookinfo (isbn, title, price) values('00006', 'Object-C入門テキスト', 3500)";
 			num = smt.executeUpdate(sql);
 			System.out.println("\nSQL発行1回目:" + num + "件の新規レコードを登録しました。");
 
 			sql = "INSERT INTO bookinfo (isbn, title, price) values('00007', 'C++入門テキスト', 3000)";
 			num = smt.executeUpdate(sql);
 			System.out.println("\nSQL発行2回目:" + num + "件の新規レコードを登録しました。");
 
 			System.out.println("\n■登録SQL発行後の書籍一覧表示");
 			selectAll();
 
 		} catch (Exception e) {
 			System.out.println("JDBCデータベース接続エラー" + e);
 
 		} finally {
 			try {
 				if (smt != null) {
 					smt.close();
 				}
 				if (con != null) {
 					con.close();
 				}
 
 			} catch (SQLException ignore) {
 				//例外処理の無視
 			}
 		}
 	}
 
 	private static void selectAll(){
 
 		Connection con = null;
 		Statement  smt = null;
 
 		try{
 			Class.forName(RDB_DRIVE);
 			con = DriverManager.getConnection(URL,USER,PASSWD);
 
 			smt = con.createStatement();
 
 			//データベースへ発行するSQL文
 			String sql = "SELECT * FROM bookinfo";
 
 			ResultSet rs = smt.executeQuery(sql);
 
 			while (rs.next()) {
 				System.out.println("isbn -> "   + rs.getString("isbn") +
 						"\t title -> " + rs.getString("title") +
 						"\t price-> "  + rs.getString("price"));
 			}
 
 		}catch (Exception e) {
 			System.out.println("JDBCデータベース接続エラー");
 
 		}finally{
 			try {
 				if (smt != null) {
 					smt.close();
 				}
 				if (con != null) {
 					con.close();
 				}
 
 			} catch (SQLException ignore) {
 				//例外処理の無視
 			}
 		}
 	}
 }

実行結果

解説

 大きな構成として、13行目から57行目でデータの登録処理を記述したmain()メソッドを、59行目から97行目でデータを全て表示するselectAll()メソッドを定義しています。
 main()メソッドではデータの登録処理を2回行なっていて、処理の前後にselectAll()メソッドを呼び出しデータを表示しています。
 このプログラムでは、今まで作成したプログラムと同様にトランザクション処理を行なっていないため、SQL文が実行されると即データベースに処理の結果が反映します。実行結果を確認すると2回目のSQL文を実行した直後の表示結果にデータが反映しているのが確認できます。

図 3.1.2 登録されたデータ

トランザクション処理を行う前に

 トランザクション処理を行うプログラムと行わないプログラムと違いを確認するため、プログラムを作成する前に、以下のDELETE文を実行しデータを初期状態に戻しておきましょう。    DELETE FROM bookinfo WHERE isbn = '00006' OR isbn = '00007';

トランザクション処理を行うプログラム

 SampleNotTransaction.javaにトランザクション処理を追加したプログラムです。
 このプログラムは、2つのSQL文をまとめて、トランザクション処理を実行します。明示的にコミットして処理を確定させない限り、処理の結果がデータベースに反映されないことを確認します。

 ① ソース・フォルダー      :myjdbc_kanda/src
 ② パッケージ          :jp.co.f1.jdbc.ch03
 ③ 名前             :SampleTransaction
 ④ 作成するメソッド・スタブの選択:public static void main(String[] args) にチェックを入れる

➢ SampleTransaction.java

package jp.co.f1.jdbc.ch03;

import java.sql.*;

public class SampleTransaction {

	//接続用の情報をフィールドに定数として定義
	private static String RDB_DRIVE="com.mysql.jdbc.Driver";
	private static String URL="jdbc:mysql://localhost/mybookdb";
 	private static String USER="bms";
 	private static String PASSWD="bms123";
 
 	public static void main(String[] args) {
 
 		String sql = null;
 		int num = 0 ;
 
 		Connection con = null;
 		Statement smt = null;
 
 		try {
 			Class.forName(RDB_DRIVE);
 			con = DriverManager.getConnection(URL,USER,PASSWD);
 
 			con.setAutoCommit(false);
 
 			smt = con.createStatement();
 
 			System.out.println("■登録SQL発行前の書籍一覧表示");
 			selectAll();
 
 			sql = "INSERT INTO bookinfo (isbn, title, price) VALUES('00006', 'Object-C入門テキスト', 3500)";
 			num = smt.executeUpdate(sql);
 			System.out.println("\nSQL発行1回目:" + num + "件の新規レコードを登録しました。");
 
 			sql = "INSERT INTO bookinfo (isbn, title, price) VALUES('00007', 'C++入門テキスト', 3000)";
 			num = smt.executeUpdate(sql);
 			System.out.println("\nSQL発行2回目:" + num + "件の新規レコードを登録しました。");
 
 			System.out.println("\n■登録SQL発行後の書籍一覧表示");
 			selectAll();
 
 			System.out.println("\nコミット処理を実施しました。");
 			con.commit();
 
 			System.out.println("\n●コミット後の書籍一覧表示");
 			selectAll();
 
 		} catch (Exception e) {
 			System.out.println("JDBCデータベース接続エラー" + e);
 			if (con != null) {
 				try {
 					System.out.println("\nロールバック処理を実施しました。");
 					con.rollback();
 					System.out.println("\n★ロールバック後の書籍一覧表示");
 					selectAll();
 				} catch (SQLException ignore) {
 					//例外処理の無視
 				}
 			}
 		} finally {
 			try {
 				if (smt != null) {
 					smt.close();
 				}
 				if (con != null) {
 					con.close();
 				}
 			} catch (SQLException ignore) {
 				//例外処理の無視
 			}
 		}
 	}
 
 	private static void selectAll()	{
 
 		Connection con = null;
 		Statement  smt = null;
 
 		try{
 			Class.forName(RDB_DRIVE);
 			con = DriverManager.getConnection(URL,USER,PASSWD);
 
 			smt = con.createStatement();
 
 			//データベースへ発行するSQL文
 			String sql = "SELECT * FROM bookinfo";
 
 			ResultSet rs = smt.executeQuery(sql);
 
 			while (rs.next()) {
 				System.out.println("isbn -> "   + rs.getString("isbn") +
 						"\t title -> " + rs.getString("title") +
 						"\t price-> "  + rs.getInt("price"));
 			}
 
 		}catch (Exception e) {
 			System.out.println("JDBCデータベース接続エラー");
 
  		}finally{
  			try {
  				if (smt != null) {
  					smt.close();
  				}
  				if (con != null) {
  					con.close();
  				}
  
  			} catch (SQLException ignore) {
  				//例外処理の無視
  			}
  		}
  	}
  }

実行結果

解説

 このプログラムは、SampleNotTransaction.javaにトランザクション処理を追加したプログラムです。
 大きな構成は変わりませんが、トランザクション処理に必要な幾つかの処理を追加しています。

 まず1つ目は25行目の処理です。デフォルトでは有効になっているオートコミット機能を無効化しています。これによりトランザクションを使ったデータの操作が行えます。
   25: con.setAutoCommit(false);

 32行目から38行目で2つのINSERT文をまとめて、トランザクション処理を行なっています。
   32: sql = "INSERT INTO bookinfo (isbn, title, price) values('00006', 'Object-C入門テキスト', 3500)";
   33: num = smt.executeUpdate(sql);
   34: System.out.println("\nSQL発行1回目:" + num + "件の新規レコードを登録しました。");
   36: sql = "INSERT INTO bookinfo (isbn, title, price) values('00007', 'C++入門テキスト', 3000)";
   37: num = smt.executeUpdate(sql);
   38: System.out.println("\nSQL発行2回目:" + num + "件の新規レコードを登録しました。");

 2つ目は44行目の処理です。この処理ではコミットが行われています。仮登録状態になっているUPDATE文の操作を確定する処理が行われデータが更新されます。
   44: con.commit();

 3つ目は54行目の処理です。この処理は例外が発生した場合に、ロールバックを行うための処理です。このプログラムでは実行されることはありませんが、コミットと対になる処理として記述しています。
   54: con.rollback();

 では、実行結果を見て行きましょう。
下の図は、INSERT文を実行する前と後のデータの状態です。トランザクションが行われていないプログラムSampleNotTransaction.javaではSQL文が実行されると即データベースに処理の結果が反映されていましたが、このプログラムの実行結果ではINSERT文の実行前と実行後で値が全く同じ内容になっています。

図 3.1.3初期データとSQL実行後のデータ

 これは、トランザクション処理によって、実行されたUPDATE文の結果が、コミットしていないため、データベースに反映されずに仮登録された状態となっているためです。この状態であれば、rollback()メソッドを実行することで、実行されたINSERT文をなかったことにすることができます。
 次にINSERT文を実行した後とコミット処理を行った後のデータです。コミットを実行することで仮登録された状態になっていた実行結果が確定され、データベースに反映していることが確認できます。

図 3.1.4 SQL実行後のデータとコミット後のデータ

トランザクション処理の注意

 トランザクション処理ではcommit()メソッドを行うことで、任意のタイミングでDBに反映することが可能です。これは、コミット処理を行わないと、SQLをいくら発行しようとDBに反映されないというこでもあるため注意が必要です。

トランザクション処理の注意
  • setAutoCommit()メソッドにfalseを渡すことでオートコミットを無効化できる。
  • commit()メソッドを実行することでSQL文をコミットすることができる。
  • commit()メソッドを実行するまでのSQL文は仮登録状態となるためデータベースに反映されない。
データのロールバック

 本テキストではデータのロールバックは実行していません。もしもロールバック処理の確認を行いたい場合には、SampleTransaction.javaプログラムの36行目にあるINSERT文を、実行できないSQL文に変更しエラーを発生させることで確認することができます。

 

NEXT>> 3.2 PreparedStatement