JDBCを勉強してみた。

最近、javaのormであるActiveObjectsを勉強しています。このActiveObjectsですが、javaからDBへのアクセス部分のライブラリであるため、結局最終的に使用されるのはJDBCです。
そこで、今日は5年ぐらい前に買ったJDBCの本をちゃんと読んでみました。

JDBCによるJavaデータベースプログラミング 第2版

JDBCによるJavaデータベースプログラミング 第2版

この本の発売日を見ると2001-09と古い気もしますが、javaのDB接続の基礎が書かれているので今でも十分役に立つと思います。JDBCを利用についてはこの本があれば、大体足りるのではないでしょうか。
以下、今日勉強した内容を書いていきます。

今回勉強した内容一覧

java.sql.ResultSet(結果セット)で重要なメソッド

列の値を取得する。

get型( 列の番号 | 列名 ) => 列の値(型はメソッド名の型)

// SELECT文を実行して、結果セットを取得する。
ResultSet rs = stmt.executeQuery("SELECT a, b FROM TABLE");

// a列(1番目の列)を、String型で取得する。
// ※列の番号は1から始まる。
String a = rs.getString( 1 );

// b列の値を、int型で取得する。
int b = rs.getInt( "b" );

// 結果セットを閉じる。
rs.close();
次の行を取得する。

next() => true : 残りの行があった/ false : 残りの行がない

// SELECT文を実行して、結果セットを取得する。
ResultSet rs = stmt.executeQuery("SELECT a, b FROM TABLE");

// 行がなくなるまで繰り返す。
while ( rs.next() ) {
	// a列とb列の値を出力する。
	System.out.println( rs.getString( "a" ) + " : " + rs.getInt( "b" ) );
}

// 結果セットを閉じる。
rs.close();
直前に(get型()で)取得した列がNULLかどうか確認する。

wasNull()

// SELECT文を実行して、結果セットを取得する。
ResultSet rs = stmt.executeQuery("SELECT a, b FROM TABLE");

String a = rs.getString( "a" );

// b列を取得する。
int b = rs.getInt( "b" );

// b列がNULLでないことを確認する。
if ( rs.wasNull() ) {
	// b列がNULLである:
	
	// -1におきかえる。
	b = -1;
}

(Connection|Statement|ResultSet|...)をclose()するべき理由

JDBCはSunによって開発されたAPIですが、JDBCの実装であるJDBCドライバは様々なベンダによって作成されています。そのため、JDBC実装の中には、close()が呼び出されるまでJDBCドライバ内で使用したリソースを解放しないまま保持し続けるようなものもあるそうです。したがって、このようなJDBCドライバの実装の違いに振り回されないためにも、使用し終わったオブジェクトに対しては、利用者側で必ずclose()するべきのようです。

トランザクションアイソレーション(トランザクションの遮断レベル)

ポイント: トランザクション内で読み取る内容が、他のトランザクションの更新の影響をどれぐらい受けるか?

ダーティ読み取り

他のトランザクションコミット前の更新値を読み取ってしまいます。そうすると、他のトランザクションがコミットされずにロールバックされた場合、誤った値を読み取ってしまったこととなります。

上図では、トランザクションAのコミット前の「1.UPDATE」での更新値「B」を、トランザクションBの「2.SELECT」が読み取っている。しかし、トランザクションAは「3.ROLLBACK」を行ったため、「B」の読み取りは誤りだったことになります。実際に、ロールバック後には、トランザクションAでの「4.SELECT」で「A」が読み取られます。

繰り返し不可の読み取り

あるトランザクションが、他のトランザクションによるコミットの前と後において、それぞれをコミット前の値とコミット後の値を読み取ります。それにより、他のトランザクションのコミットの前後でトランザクションが読み取るデータが異なる、つまり、繰り返して同じ値を読み取ることができなくなってしまいます。

上図では、トランザクションAのコミット前は、トランザクションBは値「A」を読み取ります。しかし、トランザクションAのコミット後は、トランザクションBは値「B」を読み取るようになります。トランザクションのAのコミットの前後で、トランザクションBは同じ値を読み取ることができないことになります。

ファントム読み取り

あるトランザクションが、他のトランザクションのコミット後に他のトランザクションが挿入した行を読み取ります。それにより、他のトランザクションのコミット前には見えなかった行が、コミット後には見えるようになります。

上図では、トランザクションAのコミット前は、トランザクションBは行「B」を読み取ることはできません。しかし、トランザクションAのコミット後は、トランザクションBが行「B」を読み取れるようになります。

JDBCにおけるトランザクションアイソレーション

java.sql.Connection#setTransactionIsolation(int level)を呼び出すことで、トランザクションアイソレーションの設定の変更を試みることが可能です。試みることが可能と言っているのは、JDBC実装によって設定が変更されないことがあるためです。
引数のlevelには、java.sql.Connectionで定義されている、定数を指定します。以下、定数を表にまとめました。

  ダーティ読み取りが起こる 繰り返し不可読み取りが起こる ファントム読み取りが起こる
TRANSACTION_READ_COMMITTED
TRANSACTION_READ_UNCOMMITTED  
TRANSACTION_REPEATABLE_READ    
TRANSACTION_SERIALIZABLE      

実際のデータ(インタフェース)とメタデータの対応関係

JDBCでは、いくつかのデータ(インタフェース)に対して、それに付随するメタデータがそれぞれ別のインタフェース(またはクラス)として定義されています。この実際のデータとメタデータの関係を、表にまとめました。似たような関連性があるにもかかわらず、名前の付け方には規則性がありません。

  実際のデータ(インタフェース) メタデータ(インタフェース または クラス)
結果セット ResultSet ResultSetMetaData
JDBC接続 Connection DatabaseMetaData
JDBCドライバ Driver DriverPropertyInfo

ネーミング・サービス/ディレクトリ・サービス

  • ネーミング・サービス -- 「プログラムの要素」と「名前」を対応付ける仕組み
  • ディレクトリ・サービス -- ネーミング・サービスの「対応付け」に、「属性」を設定できるようにした仕組み
一般的なディレクトリ・サービス
LDAP準拠のディレクトリサービス-NetscapeLDAPサービス

【用語】ファクトリ

〜ファクトリ = 〜を作成するモジュール

(例)
JDBCの接続ファクトリ = JDBCの接続(Connectionインスタンス)を作成するモジュール

JDBCのデータソース

JDBCのデータソース(java.sql.DataSource) = JDBCの接続ファクトリ

  • データソースを使用すると、データベースを名前で参照できます。
  • データソースは、ディレクトリサービス内に格納できます。
データソースを使用して、データベースを名前で参照する。
// ディレクトリサービスの初期コンテキストを作成する。
Context context = new InitialContext();
// 初期コンテキストから、名前「jdbc/ora」でJDBCのデータソース(JDBCの接続ファクトリ)を取得する。
DataSource dataSource = (DataSource) context.lookup( "jdbc/ora" );
// データソースを使用して、JDBCの接続を作成する。
Connection connection = dataSource.getConnection( "user", "password" );
データソースを、ディレクトリサービス内に格納する。
// MySqlデータソースを作成する。
MysqlDataSource dataSource = new MysqlDataSource();
// データソースにサーバ名を設定する。
dataSource.setServerName( "host_name" );
// データソースにDB名を設定する。
dataSource.setDatabaseName( "db_name" );
// データソースを、ディレクトリサービスのコンテキストに名前「jdbc/mysql」で格納(=バインド)する。
context.bind( "jdbc/mysql", dataSource );
参考
MysqlDataSource: http://www.docjar.com/docs/api/com/mysql/jdbc/jdbc2/optional/MysqlDataSource.html

接続プール

多くのデータベースエンジンでは、データベースのオープンやクローズの処理には時間がかかります。そのため、短い時間に多数のユーザが接続するシステムでは、このオープンやクローズの処理の回数が非常に多くなるため、ボトルネックになります
それを避けるために、接続プールが使用されます。以下にようになります。

  • 接続プールが、オープンされたDB接続をキャッシュします。
  • アプリケーションでは、キャッシュされたDB接続を利用します。

開発者から見ると、通常の接続と接続プールで取得された接続には、ほとんど違いはありません。

オブジェクト関連図

手順(1) 接続を取得する

手順(2) 接続を使用する

手順(3) 接続を返却する

行セット

読んだけど、自分の中でまとまってない&使わない気がするので、パスします。

分散トランザクション

  • 二つ以上のデータソースを同時に扱うトランザクション。すべてのデータソース間で整合性を保つ必要があります。
  • コミットとロールバックは、中間層サーバの複合トランザクションモニタが行われます。
  • commit()、rollback()、setAutoCommit(true)を呼び出すと、SQLExceptionが発生します。

とりあえず今日はここまで