Java+MySQLなWEBアプリにおいて、AUTO_INCREMENTなカラムを持つテーブルにINSERTでレコードを挿入してから、その新規レコードのAUTO_INCREMENTなカラムの値を取得する方法について、ちょっとてこずったのでメモ。
環境は以下。
Java:1.6.0_31
MySQL:5.5.19
本やネットで調べると LAST_INSERT_ID() というMySQLの関数を使う方法もあるようだが、ここはせっかくなのでMySQLのJDBCドライバ(MySQL Connector/J)が提供するAPIを使ってみた。
サンプルコード
final String sql = "INSERT INTO orders (customer_name, address, payment) VALUES (?, ?, ?)";
DataSource dataSource = (DataSource)context.lookup("java:comp/env/jdbc/mysql");
Connection conn = dataSource.getConnection();
PreparedStatement statement = conn.prepareStatement(sql, Statement.RETURN_GENERATED_KEYS);
statement.setString(1, customerName);
statement.setString(2, address);
statement.setInt(3, payment);
statement.executeUpdate();
// 上記の処理で登録したデータのAUTO_INCREMENTで生成されたIDを取得する
PreparedStatement stmt2
= (PreparedStatement)((DelegatingPreparedStatement) statement)
.getInnermostDelegate();
ResultSet rs = ((com.mysql.jdbc.PreparedStatement)stmt2).getGeneratedKeys();
if (rs.next()) {
id = rs.getInt(1);
} else {
throw new SQLException("failure: retrieve new id");
}
上記のコードのWEBアプリでは、JNDIで DataSource を取得し DataSource.getConnection() で java.sql.Connection を取得して変数connに入れている。
そのconnで作成した PreparedStaement を作成し statement に代入(5行目)する。ここで使用する prepareStatement() は引数にSQLとフラグを取るもので、フラグには Statement.RETURN_GENERATED_KEYS をセットする。
java.sql
インタフェース Connection
PreparedStatement prepareStatement(String sql, int autoGeneratedKeys) throws SQLException
自動生成キーを取得する機能を持つデフォルトの PreparedStatement オブジェクトを生成します。
パラメータ:
sql - 1 つ以上の '?' IN パラメータプレースホルダーを含めることができる SQL 文
autoGeneratedKeys - 自動生成キーを返すかどうかを示すフラグ。Statement.RETURN_GENERATED_KEYS または Statement.NO_GENERATED_KEYS
戻り値:
プリコンパイルされた SQL 文を含む新しい PreparedStatement オブジェクト。自動生成キーを返す機能を持つ
例外:
SQLException - データベースアクセスエラーが発生した場合、このメソッドがクローズされた接続に対して呼び出された場合、または指定されたパラメータが自動生成キーを返すかどうかを示す Statement 定数でない場合
SQLFeatureNotSupportedException - JDBC ドライバが定数 Statement.RETURN_GENERATED_KEYS を指定したこのメソッドをサポートしない場合
作成した PreparedStaement は com.mysql.jdbc.PreparedStatement ではないらしい。statement を com.mysql.jdbc.PreparedStatement でキャストしようとすると例外が発生する。
そこで、 statement を org.apache.tomcat.dbcp.dbcp.DelegatingPreparedStatement にキャストし、getInnermostDelegate() で内部に保持されていた PreparedStatement を取得し stmt2 に代入する(12-15行目)。この取得した PreparedStatement は com.mysql.jdbc.PreparedStatement らしいので getGeneratedKeys() で ResultSet を取得し AUTO_INCREMENT なカラムの値を取得する(15,17行目)。
org.apache.commons.dbcp
クラス DelegatingPreparedStatement
public PreparedStatement getInnermostDelegate()
内部に保持する PreparedStatement が DelegatingPreparedStatement でない場合にはその PreparedStatement を返し、それ以外の場合には再帰的に getDelegate() をコールします。
従ってこのメソッドは DelegatingPreparedStatement ではない根本の処理の委託先となる PreparedStatement を返し、 DelegatingPreparedStatement の連鎖の中に処理の委託先が見つからない場合には null を返します。
このメソッドはネストした DelegatingPreparedStatement から 本来の PreparedStatement を取得したい場合に有用です。
JNDIを使用せず下記のように Connection を取得した場合は DelegatingPreparedStatement#getInnermostDelegate() は不要で、直接キャストして getGeneratedKeys() を使えばよい。
Connection conn = DriverManager.getConnection("jdbc:mysql://localhost/java_sample_db", "java", "password");
参考ページ:MySQL :: MySQL 5.1 リファレンスマニュアル :: 24.4.5.1 JDBC の基本コンセプト
Recent Comments