トランザクション

JDBCのデフォルト動作はautocommitである.Connectionオブジェクトのメソッド呼び出しにより,autocommitのon/offやCOMMIT,ROLLBACKができる.また,きれいな方法ではないがJDBCによるDBアクセスプログラムは,基本的にプログラムでSQL文を生成して発行するだけなので,BEGINとかCOMMITというSQL文を発行すればトランザクション処理ができる.
概要
サンプル
Connectionオブジェクトによるトランザクション操作
BEGIN,COMMIT等のSQL文を発行する方法
トランザクションブロック
SELECTにおけるターゲットリスト

概要

JDBCではデフォルトではautocommitとなっている.すなわち1SQL文で1トランザクションとなり自動でCOMMITがかかる.これはautocommitのon/offはConnectionオブジェクトで設定する.すなわち接続単位に設定することになる.さらにConnectionオブジェクトのcommit,rollbackメソッドによりCOMMITとROLLBACKを行う.

サンプル

以下にautocommitをoffとしてトランザクション処理を行うサンプルを示す.

import java.sql.*;

public class TransactionSample
{

  // PostgreSQL JDBCドライバのロード
  private static final String PG_JDBC_DRIVER = "org.postgresql.Driver";

  static
  {
    try
    {
      Class.forName (PG_JDBC_DRIVER);
    }
    catch (ClassNotFoundException e)
    {
      System.err.println ("JDBCドライバ  " + PG_JDBC_DRIVER +
			  " がみつかりません.");
      System.exit (-1);
    }
  }

  public static void main (String args[])
  {
    String url;
    String user;
    String pwd;
    if (args.length != 3)
      {
	System.err.println ("Usage : java JDBCConnectSample url user pwd");
	System.exit (-1);
      }
    url = args[0];
    user = args[1];
    pwd = args[2];

    System.out.println ("Connect to " + url);
    System.out.println ("User : " + user + " Pwd : " + pwd);
    try
    {
      // 接続
      Connection dbConnection = DriverManager.getConnection (url, user, pwd);
      // autocommitの解除
      dbConnection.setAutoCommit (false);

      // 各種クエリの発行
      doCreate (dbConnection);
      doSelect (dbConnection);
      doTransaction (dbConnection);
      doSelect (dbConnection);
      doDrop (dbConnection);

      // 切断
      dbConnection.close ();
    }
    catch (SQLException sqle)
    {
      sqle.printStackTrace ();
      System.exit (-1);
    }

    System.exit (0);
  }

  //テーブル作成
  private static void doCreate (Connection dbConnection) throws SQLException
  {
    String sql =
      "CREATE TABLE jdbcsample (id INTEGER PRIMARY KEY, value TEXT);";
      System.out.println ("Execute SQL : " + sql);
    try
    {
      Statement statement = dbConnection.createStatement ();
        statement.executeUpdate (sql);
        statement.close ();
        dbConnection.commit ();
    }
    catch (SQLException sqle)
    {
      sqle.printStackTrace ();
      dbConnection.rollback ();
    }
  }

  // トランザクション処理
  private static void doTransaction (Connection dbConnection)
    throws SQLException
  {
    try
    {
      for (int i = 0; i < 5; i++)
	{
	  doInsert (dbConnection, i, "Value" + i);
	}
      dbConnection.commit ();
      //      dbConnection.rollback();
    } catch (SQLException sqle)
    {
      sqle.printStackTrace ();
      System.err.println ("ROLLBACK START");
      dbConnection.rollback ();
      System.err.println ("ROLLBACK DONE");
    }
  }

  //レコード投入
  private static void doInsert (Connection dbConnection, int paramId,
				String paramValue)
  {
    String sql =
      "INSERT INTO jdbcsample VALUES(" + paramId + ",'" + paramValue + "');";
    System.out.print ("Execute SQL : " + sql + " ... ");
    try
    {
      Statement statement = dbConnection.createStatement ();
      int insertNum = statement.executeUpdate (sql);
      System.out.println ("Inserted " + insertNum + " record.");
      statement.close ();
    }
    catch (SQLException sqle)
    {
      System.out.println ("");
      sqle.printStackTrace ();
    }
  }

  //検索
  private static void doSelect (Connection dbConnection)
  {
    String sql = "SELECT count(*) AS record_count from jdbcsample ;";
    System.out.println ("Execute SQL : " + sql);
    try
    {
      Statement statement = dbConnection.createStatement ();
      ResultSet resultSet = statement.executeQuery (sql);

      while (resultSet.next ())
	{
	  System.out.println ("Got record : count = " +
			      resultSet.getInt ("record_count"));
	}
    }
    catch (SQLException sqle)
    {
      sqle.printStackTrace ();
    }
  }

  //テーブル削除
  private static void doDrop (Connection dbConnection) throws SQLException
  {
    String sql = "DROP TABLE jdbcsample;";
    System.out.println ("Execute SQL : " + sql);
    try
    {
      Statement statement = dbConnection.createStatement ();
        statement.executeUpdate (sql);
        statement.close ();
        dbConnection.commit ();
    }
    catch (SQLException sqle)
    {
      sqle.printStackTrace ();
      dbConnection.rollback ();
    }
  }
}

このサンプルでは以下を行う.
DB接続
テーブル作成.COMMIT.
作成したテーブルのレコード数を問い合わせる
1トランザクションブロック内でインサート.COMMIT.
作成したテーブルのレコード数を問い合わせる
テーブル削除.COMMIT.
DB切断
これを実行してみる.

$> java  TransactionSample jdbc:postgresql://dbserver/firstdb matsu hogefuga 
Connect to jdbc:postgresql://dbserver/firstdb
User : matsu Pwd : hogefuga
Execute SQL : CREATE TABLE jdbcsample (id INTEGER PRIMARY KEY, value TEXT);
Execute SQL : SELECT count(*) AS record_count from jdbcsample ;
Got record : count = 0
Execute SQL : INSERT INTO jdbcsample VALUES(0,'Value0'); ... Inserted 1 record.
Execute SQL : INSERT INTO jdbcsample VALUES(1,'Value1'); ... Inserted 1 record.
Execute SQL : INSERT INTO jdbcsample VALUES(2,'Value2'); ... Inserted 1 record.
Execute SQL : INSERT INTO jdbcsample VALUES(3,'Value3'); ... Inserted 1 record.
Execute SQL : INSERT INTO jdbcsample VALUES(4,'Value4'); ... Inserted 1 record.
Execute SQL : SELECT count(*) AS record_count from jdbcsample ;
Got record : count = 5
Execute SQL : DROP TABLE jdbcsample;
上の出力はdoTransactionメソッド内でcommitしている場合のものである.二回目のSELECT(5回のINSERT後)の結果count = 5となっている.
dbConnection.commit ();
//      dbConnection.rollback();
ここで上のcommit呼び出しをコメントアウトし,rollback呼び出しを有効にしてみる.
$> java  TransactionSample jdbc:postgresql://dbserver/firstdb matsu hogefuga 
Connect to jdbc:postgresql://dbserver/firstdb
User : matsu Pwd : hogefuga
Execute SQL : CREATE TABLE jdbcsample (id INTEGER PRIMARY KEY, value TEXT);
Execute SQL : SELECT count(*) AS record_count from jdbcsample ;
Got record : count = 0
Execute SQL : INSERT INTO jdbcsample VALUES(0,'Value0'); ... Inserted 1 record.
Execute SQL : INSERT INTO jdbcsample VALUES(1,'Value1'); ... Inserted 1 record.
Execute SQL : INSERT INTO jdbcsample VALUES(2,'Value2'); ... Inserted 1 record.
Execute SQL : INSERT INTO jdbcsample VALUES(3,'Value3'); ... Inserted 1 record.
Execute SQL : INSERT INTO jdbcsample VALUES(4,'Value4'); ... Inserted 1 record.
Execute SQL : SELECT count(*) AS record_count from jdbcsample ;
Got record : count = 0
Execute SQL : DROP TABLE jdbcsample;
doTransactionメソッド内でcommitの代わりにrollbackしているので,二回目のSELECT文によるcountの値が0である.

Connectionオブジェクトによるトランザクション操作

サンプルではautocommitのon/offの設定は以下で行っている.
// 接続
Connection dbConnection = DriverManager.getConnection (url, user, pwd);
// autocommitの解除
dbConnection.setAutoCommit (false);
これでautocommitが無効になる.あとは必要に応じてConnectionオブジェクトに対しCOMMIT,ROLLBACKを指示する.以下はサンプルのdoTransaction内の処理.
try {
  for (int i = 0; i < 5; i++)
    {
      doInsert (dbConnection, i, "Value" + i);
    }
  dbConnection.commit ();
  //      dbConnection.rollback();
} catch (SQLException sqle) {
    sqle.printStackTrace ();
    System.err.println ("ROLLBACK START");
    dbConnection.rollback ();
    System.err.println ("ROLLBACK DONE");
}
tryブロックでINSERTを5回してCOMMITするようにして,catchブロックでROLLBACKするようにしている.commit,rollbackメソッドもSQLExepctionを投げるので,doTransactionメソッドをthrows SQLExceptionとしている.

BEGIN,COMMIT等のSQL文を発行する方法

Connectionオブジェクトのメソッド呼び出しではなく,JDBCの上位プログラムでBEGIN,COMMITなどのSQL文を発行することができた.その方法を以下に示すが推奨されない方法である.さて,JDBCのデフォルトではautocommitなので,トランザクションブロックはSQL文
BEGIN;
で開始し
COMMIT;
あるいは
ROLLBACK;
で終了する必要がある.いずれのSQL文もレコードが選択されることはないので,executeUpdateによって発行する.JDBCを利用してトランザクション処理を行うサンプルを以下に示す.

import java.sql.*;

public class TransactionSampleRawSQL
{

  // PostgreSQL JDBCドライバのロード
  private static final String PG_JDBC_DRIVER = "org.postgresql.Driver";

  static
  {
    try
    {
      Class.forName (PG_JDBC_DRIVER);
    }
    catch (ClassNotFoundException e)
    {
      System.err.println ("JDBCドライバ  " + PG_JDBC_DRIVER +
			  " がみつかりません.");
      System.exit (-1);
    }
  }

  public static void main (String args[])
  {
    String url;
    String user;
    String pwd;
    if (args.length != 3)
      {
	System.err.println ("Usage : java JDBCConnectSample url user pwd");
	System.exit (-1);
      }
    url = args[0];
    user = args[1];
    pwd = args[2];

    System.out.println ("Connect to " + url);
    System.out.println ("User : " + user + " Pwd : " + pwd);
    try
    {
      // 接続
      Connection dbConnection = DriverManager.getConnection (url, user, pwd);
      // 各種クエリの発行
      doCreate (dbConnection);
      doSelect (dbConnection);
      doTransaction (dbConnection);
      doSelect (dbConnection);
      doDrop (dbConnection);

      // 切断
      dbConnection.close ();
    }
    catch (SQLException sqle)
    {
      sqle.printStackTrace ();
      System.exit (-1);
    }

    System.exit (0);
  }

  //テーブル作成
  private static void doCreate (Connection dbConnection)
  {
    String sql =
      "CREATE TABLE jdbcsample (id INTEGER PRIMARY KEY, value TEXT);";
    System.out.println ("Execute SQL : " + sql);
    try
    {
      Statement statement = dbConnection.createStatement ();
      statement.executeUpdate (sql);
      statement.close ();
    }
    catch (SQLException sqle)
    {
      sqle.printStackTrace ();
    }
  }

  // トランザクション処理
  private static void doTransaction (Connection dbConnection)
    throws SQLException
  {
    try
    {
      beginTransaction (dbConnection);
      for (int i = 0; i < 5; i++)
	{
	  doInsert (dbConnection, i, "Value" + i);
	}
      commitTransaction (dbConnection);
      //      rollbackTransaction(dbConnection);
    }
    catch (SQLException sqle)
    {
      sqle.printStackTrace ();
      System.err.println ("ROLLBACK START");
      rollbackTransaction (dbConnection);
      System.err.println ("ROLLBACK DONE");
    }
  }

  // BEGIN
  private static void beginTransaction (Connection dbConnection)
    throws SQLException
  {
    String sql = "BEGIN;";
    System.out.println ("Execute SQL : " + sql);
    Statement statement = dbConnection.createStatement ();
    statement.executeUpdate (sql);
    statement.close ();
  }

  // COMMIT
  private static void commitTransaction (Connection dbConnection)
    throws SQLException
  {
    String sql = "COMMIT;";
    System.out.println ("Execute SQL : " + sql);
    Statement statement = dbConnection.createStatement ();
    statement.executeUpdate (sql);
    statement.close ();
  }

  // ROLLBACK
  private static void rollbackTransaction (Connection dbConnection)
    throws SQLException
  {
    String sql = "ROLLBACK;";
    System.out.println ("Execute SQL : " + sql);
    Statement statement = dbConnection.createStatement ();
    statement.executeUpdate (sql);
    statement.close ();
  }

  //レコード投入
  private static void doInsert (Connection dbConnection, int paramId,
				String paramValue)
  {
    String sql =
      "INSERT INTO jdbcsample VALUES(" + paramId + ",'" + paramValue + "');";
    System.out.print ("Execute SQL : " + sql + " ... ");
    try
    {
      Statement statement = dbConnection.createStatement ();
      int insertNum = statement.executeUpdate (sql);
      System.out.println ("Inserted " + insertNum + " record.");
      statement.close ();
    }
    catch (SQLException sqle)
    {
      System.out.println ("");
      sqle.printStackTrace ();
    }
  }

  //検索
  private static void doSelect (Connection dbConnection)
  {
    String sql = "SELECT count(*) AS record_count from jdbcsample ;";
    System.out.println ("Execute SQL : " + sql);
    try
    {
      Statement statement = dbConnection.createStatement ();
      ResultSet resultSet = statement.executeQuery (sql);

      while (resultSet.next ())
	{
	  System.out.println ("Got record : count = " +
			      resultSet.getInt ("record_count"));
	}
    }
    catch (SQLException sqle)
    {
      sqle.printStackTrace ();
    }
  }

  //テーブル削除
  private static void doDrop (Connection dbConnection)
  {
    String sql = "DROP TABLE jdbcsample;";
    System.out.println ("Execute SQL : " + sql);
    try
    {
      Statement statement = dbConnection.createStatement ();
      statement.executeUpdate (sql);
      statement.close ();
    }
    catch (SQLException sqle)
    {
      sqle.printStackTrace ();
    }
  }
}
このサンプルでは以下を行う.
DB接続
テーブル作成
作成したテーブルのレコード数を問い合わせる
1トランザクションブロック内でインサート
作成したテーブルのレコード数を問い合わせる
テーブル削除
DB切断
以下は実行結果.
$ java TransactionSample jdbc:postgresql://dbserver/firstdb matsu hogefuga
Connect to jdbc:postgresql://dbserver/firstdb
User : matsu Pwd : hogefuga
Execute SQL : CREATE TABLE jdbcsample (id INTEGER PRIMARY KEY, value TEXT);
Execute SQL : SELECT count(*) AS record_count from jdbcsample ;
Got record : count = 0
Execute SQL : BEGIN;
Execute SQL : INSERT INTO jdbcsample VALUES(0,'Value0'); ... Inserted 1 record.
Execute SQL : INSERT INTO jdbcsample VALUES(1,'Value1'); ... Inserted 1 record.
Execute SQL : INSERT INTO jdbcsample VALUES(2,'Value2'); ... Inserted 1 record.
Execute SQL : INSERT INTO jdbcsample VALUES(3,'Value3'); ... Inserted 1 record.
Execute SQL : INSERT INTO jdbcsample VALUES(4,'Value4'); ... Inserted 1 record.
Execute SQL : COMMIT;
Execute SQL : SELECT count(*) AS record_count from jdbcsample ;
Got record : count = 5
Execute SQL : DROP TABLE jdbcsample;
INSERT後COMMITするので,その後のSELECTの結果count = 5となっている.確認までに,サンプル中のcommitTransactionメソッド呼び出しをrollbackTransactionメソッド呼び出しに置き換えて実行してみた.
$ java TransactionSample jdbc:postgresql://dbserver/firstdb matsu hogefuga
Connect to jdbc:postgresql://dbserver/firstdb
User : matsu Pwd : hogefuga
Execute SQL : CREATE TABLE jdbcsample (id INTEGER PRIMARY KEY, value TEXT);
Execute SQL : SELECT count(*) AS record_count from jdbcsample ;
Got record : count = 0
Execute SQL : BEGIN;
Execute SQL : INSERT INTO jdbcsample VALUES(0,'Value0'); ... Inserted 1 record.
Execute SQL : INSERT INTO jdbcsample VALUES(1,'Value1'); ... Inserted 1 record.
Execute SQL : INSERT INTO jdbcsample VALUES(2,'Value2'); ... Inserted 1 record.
Execute SQL : INSERT INTO jdbcsample VALUES(3,'Value3'); ... Inserted 1 record.
Execute SQL : INSERT INTO jdbcsample VALUES(4,'Value4'); ... Inserted 1 record.
Execute SQL : ROLLBACK;
Execute SQL : SELECT count(*) AS record_count from jdbcsample ;
Got record : count = 0
Execute SQL : DROP TABLE jdbcsample;
INSERT後ROLLBACKするので,その後のSELECTでのcountの値は0である.

トランザクションブロック

サンプルではトランザクション処理はdoTransacationメソッドで行っている.すなわちdoTransaction内で明示的にトランザクションの開始と終了(BEGINとCOMMIT)を指定するためにbeginTransactionやcommitTransactionメソッドを呼び出している.
beginTransaction(dbConnection);
for (int i = 0; i < 5; i++)
  {
    doInsert (dbConnection, i, "Value" + i);
  }
commitTransaction(dbConnection);
beginTransactionメソッドにおいて,SQL文BEGINやCOMMITを発行している.
String sql = "BEGIN;";
Statement statement = dbConnection.createStatement ();
statement.executeUpdate (sql);
commitTransactionメソッドにおいてSQL文COMMITを発行している.
String sql = "COMMIT;";
Statement statement = dbConnection.createStatement ();
statement.executeUpdate (sql);
このようにBEGINやCOMMIT,そしてROLLBACKといったSQL文はexecuteUpdateで発行する.また,doTransactionメソッドの全体的な構造は以下のようになっている.
try{
  BEGIN;
  INSERT;
  COMMIT;
} catch (SQLException sqle) {
  ROLLBACK;
}
すなわちtryでトランザクション処理を行い,それに対するcatchとしてROLLBACKを実行する.また,ROLLBACK自体もSQLExceptionを投げ得るので,doTransactionメソッドはSQLExceptionを投げ得るという指定にした(※).
※ catch内でtry-catchとかしたくない.

SELECTにおけるターゲットリスト

トランザクションから話題が離れるが,本頁の二つのサンプルでは以下のようなSELECT文を発行した.
SELECT count(*) AS record_count from jdbcsample ;
このSQL文では集約関数countの列にあえてrecord_countという名前をつけている.そして以下のようにその名前を使用して値を取得している.
System.out.println (“Got record : count = ” + resultSet.getInt (“record_count”));
このようにターゲットリストのフィールドにASで名前をつけると,あとでその名前を指定して値を取り出せる(※).
※ 他にもやりようはあるような気がするが,確認までにやってみた.

This article was written by Fujiko