6.ResultSetを用いた更新処理 (Oracle8i)
この章では、ResultSetを用いた更新処理について説明します。OracleのThinドライバで説明します。
またこの機能を日本語のデータを対象に扱う場合には、Oracle8iのJDBCドライバの代わりにOracle 9iのJDBCドライバ(http://otn.oracle.co.jp/software/tech/java/jdbc/index.htmlからダウンロード可能)を使用するようにして下さい。ThinドライバについてはOracle7以上の大半のDBをサポートしていますのでOracle8iでも利用可能です。ただしOCIドライバについてはOracle Clientのバージョンに依存しますので、Oracle8iで利用する事はできません。注意してください。
6.1.カーソルの種類
「java.sql.Result」の実体は、クエリー文の結果セットを指し示すカーソルです。このカーソルの種類によって「ResultSet」で行える機能が異なってきます。カーソルの種類の指定は、「Connection」から「createStatement」「prepareStatement」を実行する際に行います。
Connection conn=DriverManager.getConnection("jdbc:oracle:thin:@dbserver:1521:oracle", "scott","tiger"); Statement statement=conn.createStatement(ResultSet.TYPE_FORWARD, ResultSet.CONCUR_READ_ONLY);
1つ目の引数はカーソルのタイプを指定します。ここで指定できる引数の値は以下の3つです。
ResultSet.TYPE_FORWARD_ONLY | カーソルは最初から最後まで順方向にしか移動できません。 |
ResultSet.TYPE_SCROLL_INSENSITIVE | カーソルは順方向・逆方向いずれにも移動可能です。ただし他による変更を反映しません。 |
ResultSet.TYPE_SCROLL_SENSITIVE | カーソルは順方向・逆方向いずれにも移動可能です。また他による変更も反映します。 |
この引数は、結果セットの取得の際に使用できるメソッドに影響が出ます。「ResultSet.TYPE_FORWARD_ONLY」の場合、「next()」メソッドしか使用できません。それ以外の場合には、以下のメソッドも使用可能となります。
absolute(int) | 引数で指定された行番号に移動します。1行目が1、2行目が2です。負の数の場合には、最終行から逆順に数えた行に移動します。 |
afterLast() | 最終行の1つ後ろに移動します。 |
beforeFirst() | 先頭行の1つ前に移動します。 |
first() | 先頭行に移動します。 |
last() | 最終行に移動します。 |
previous() | 1つ前の行に移動します。 |
relative(int) | 引数で指定された数だけ、カーソルと移動します。1を指定した場合はnext()と、-1を指定した場合はprevious()と同じです。 |
「createStatement」の2つ目の引数は、変更可能性を指定します。
ResultSet.CONCUR_READ_ONLY | カーソルはデータの読み出ししかサポートしません。 |
ResultSet.CONCUR_UPDATABLE | カーソルは変更可能です。カーソルを用いたデータの挿入・変更・削除がサポートされます。 |
「ResultSet.CONCUR_UPDATABLE」を指定すると、ResultSetを用いた更新処理が可能となります。
「createStatement」で引数を指定しない場合、「ResultSet.TYPE_FORWARD_ONLY」「ResultSet.CONCUR_READ_ONLY」を指定した場合と同じになります。ちなみにPostgreSQLのJDBCドライバは「ResultSet.TYPE_INSENSITIVE」を指定した場合と同じになるので注意してください。
6.2.ResultSetを用いたupdate
ResultSetを用いたupdateについて説明します。
conn.setAutoCommit(false); final String sql="select DEPTNO,DNAME,LOC from DEPT"; PreparedStatement statement=conn.prepareStatement(sql,ResultSet.TYPE_SCROLL_INSENSITIVE,ResultSet.CONCUR_UPDATABLE); ResultSet result=statement.executeQuery(); while(result.next()){ String loc=result.getString(3); if(loc.equals("CHICAGO"){ loc="SEATTLE"; result.updateString(3,loc); result.updateRow(); } } conn.commit();
まず「ResultSet.CONCUR_UPDATABLE」を指定して「Statement」を作成します。サンプルでは「PreparedStatement」を使用するので、「prepareStatement」の3つ目の引数に値を指定しています(3行目)。
通常と同じような方法でデータを取得しますが(5・6行目)、もし該当する行のデータを変更したい場合には「updateXXX」メソッドを使用します。サンプルでは、Stringのデータを更新するので「updateString」メソッドを使用します(9行目)。1番目の引数は更新する列番号、2番目の引数は更新後の値です。その他の「updateXXX」メソッドもこれとほぼ同じです。「updateXXX」メソッドを呼び出した後は、「updateRow」メソッドを呼び出します(10行目)。
サンプルでは最後にコミットしています。コネクションが「AutoCommit」モードである場合には、「updateXXX」メソッドが実行されるたびに、更新がデータベースに反映されます。
ResultSetを用いたupdate処理は、update文をSQLで実行するのと大差ないように思われます。しかしデータの取得を行いながらデータの更新を行いたい場合や、条件によって処理を変えたい場合(例えば1000以上は10引いて、100以下の場合は10上乗せする等)にはこの方法は有用です。
最後に1つ注意があります。ResultSetを用いた更新処理を行う場合、実行するSQL文に「*」を使用することはできません。また複数のテーブルを結合するようなSQL文も使用することができません。
(実習課題1)
以下のコンソールアプリケーションを作成しなさい。
- まず「DEPT」テーブルの値を表示する。
- 表示しつつ「DEPTNO」が「10」である値の「LOC」を「WASHINGTON」にし、「LOC」が「DALLAS」のものの値を「SEATTLE」に変更すること。
- プログラム実行後、データベースの値が更新されている事を確認する事。
6.3.ResultSetを用いたinsert
次にinsertについて説明します。insertはinsert用の行があり、そこに移動して処理を行います。サンプルでは前半部を省略しています。
ResultSet result=statement.executeQuery(); result.moveToInsertRow(); result.updateInt(1,50); result.updateString(2,"FINANCE"); result.updateString(3,"NEW YORK"); result.insertRow(); result.moveToCurrentRow(); conn.commit();
5行目の「moveToInsertRow」がinsert用の行にカーソルを移動させるメソッドです。insert用の行に移動してからは、updateの際と同じく「updateXXX」メソッドで値を設定します(6~8行目)。最後に「insertRow」メソッドが呼び出された時点で、設定された値がDBに挿入されます(9行目)。データが確定するのは11行目のcommitされたときです。
10行目の「moveToCurrentRow」は「moveToInsertRow」を呼び出す時点でカーソルがいた行に戻るメソッドです。これにより、結果セットからの値の取り出しを引き続いて行う事ができます。このとき、挿入された行が結果セットに含まれるかどうかはJDBCドライバによります。これについては以降の節で説明します。
(実習課題2)
以下のコンソールアプリケーションを作成しなさい。
- まず「DEPT」テーブルの値を表示する。
- 表示後、新しいデータの挿入を行う。挿入する値は、コンソールからの入力によって指定するようにする事。
- プログラム実行後、入力したデータがテーブルに挿入されている事を確認する事。
6.4.ResultSetを用いたdelete
最後にdeleteについて説明します。
ResultSet result=statement.executeQuery(); while(result.next()){ if(result.getInt(1)==20){ result.deleteRow(); } } conn.commit();
deleteは削除したい行にあるときに、「deleteRow」メソッドを呼び出すだけです(7行目)。実際にデータベースに反映されるのは、commitを行った段階です。
(実習課題3)
以下のコンソールアプリケーションを作成しなさい。
- まず「DEPT」テーブルの値を表示する。
- 表示後、削除したい行の「DEPTNO」をコンソールから入力し、その行をResultSetを用いて削除する事。
- プログラム実行後、入力したデータがテーブルから削除されている事を確認する事。
6.5.ResultSetで更新処理を行った場合の可視性
自分自身または他のResultSet(他のセッションではない)によって行われた変更が、ResultSetから見えるかどうかはJDBCドライバやデータベースに依存します。ResultSetのタイプによっても異なります。
それを調査するためのメソッドが、「java.sql.DatabaseMetadata」インタフェースで定義されています。3.3節「トランザクションの隔離レベル」でも使用したかと思います。以下がResultSetによる更新処理の可視性を調査するメソッドです。引数は全てResultSetのタイプ(「ResultSet.TYPE_FORWARD_ONLY」「ResultSet.TYPE_SCROLL_INSENSITIVE」「Resultset.TYPE_SCROLL_SENSITIVE」のいずれか)で指定し、結果は全て「true」または「false」で返されます。「true」の場合はサポートしている事を意味します。
ownInsertsAreVisible(int) | 自身のResultSetによって挿入された行を参照できるか |
ownUpdatesAreVisible(int) | 自身のResultSetによって変更された行を参照できるか |
ownDeletesAreVisible(int) | 自身のResultSetによって削除された行が判別できるか |
othersInsertsAreVisible(int) | 他のResultSetによって挿入された行を参照できるか |
othersUpdatesAreVisible(int) | 他のResultSetによって変更された行を参照できるか |
othersDeletesAreVisible(int) | 他のResultSetによって削除された行が判別できるか |
insertsAreDetected(int) | ResultSetの「rowInserted」メソッドで、対象行が挿入されたものである事を検出できるか |
updatesAreDetected(int) |
ResultSetの「rowUpdated」メソッドで、対象行に変更があったことを検出できるか |
deletesAreDetected(int) |
ResultSetの「rowDeleted」メソッドで、対象行が削除された事を検出できるか |
(実習課題4)
Oracle ThinドライバおよびOracle OCIドライバの可視性を調べなさい。