torutkのブログ

ソフトウェア・エンジニアのブログ

データベースを利用するアプリケーション(データベース接続)

はじめに

次の記事の続きです。

torutk.hatenablog.jp

データベースの作成が終わったら、いよいよアプリケーションからデータベースへ接続してデータの読み書きをします。

JDBC API

Java SEではRDBMSを利用する標準APIとしてJDBCJava DataBase Connectivity)が提供されています。これは利用するRDBMS製品には依存しておらず、利用するRDBMSを交換可能とする仕様となっています。

このJDBC APIを使って特定のRDBMS製品へ接続するためには、RDBMS製品固有のJDBCドライバーをSPI(Service Provider Interface)を介して組み込みます。SPIの実現には、これもJava SEの標準APIであるServiceLoaderが使われています。APIとSPIを使った実装提供により、アプリケーションからは標準のAPIのみを使ってプログラミングし、実装の切り替えはSPIで行うということが可能になります。

Microsoft SQL Server JDBCドライバー

Microsoft SQL ServerJDBCで利用する際は、Microsoftから提供されるSQL ServerJDBCドライバーを使います。このJDBCドライバーはJARファイルで提供されており、これをクラスパスまたはモジュールパスに指定することで利用できるようになります。2019年8月現在、SQL ServerJDBCドライバーはバージョン7.4.1が提供され、Java SE 8用、Java SE 11用、Java SE 12用のそれぞれのJARファイルが別々に用意されています。

今回は、Java SE 11でアプリケーションを作成・実行するので、Java SE 11用のJARファイルを使います。

プログラミング

JDBCAPIでは、データベースへ接続するためのクラスが2種類あります。

  1. DataSource
    JNDI(Java Naming and Directory Interface)を使って、外部のディレクトリ・サーバーに置かれたデータベース接続情報・ドライバーを取り出してデータベースへ接続します。アプリケーションサーバー上で動作するプログラム(Java EE環境)では一般的ですが、スタンドアロン・プログラム(Java SE環境)ではディレクトリ・サーバー相当を動かすことがないので利用しづらい方法です。

  2. DriverManager
    データベース接続情報をURL(文字列等)で指定し、ServiceLoaderで取得したJDBCドライバーを使用してデータベースへ接続します。Java SE環境ではこちらを使うことが多いです。

データベース接続の取得

java.sql.DriverManagerクラスのgetConnectionメソッドに接続情報をURL形式で指定して接続(java.sql.Connection)を取得します。

Connection conn;
  :
try {
    conn = DriverManager.getConnection("jdbc:sqlserver://localhost;databaseName=Island;user=foo;password=var");
} catch (SQLException ex) {
    // ...
}

接続情報に記載されたjdbc:sqlserverの情報から、SQL Server用のJDBCドライバーがSPIで利用可能となります。プログラム上では明示的な指定は不要です。

接続情報をハードコーディングするのは実用的ではないので、外部のファイル、環境変数等に定義してプログラムでその情報を取り込みます。DriverManagerのgetConnectionメソッドには、引数のバリエーションがいくつか用意されています。

Connection getConnection(String url) throws SQLException
Connection getConnection(String url, String user, String password) throws SQLException
Connection getConnection(String url, Properties info) throws SQLException

外部ファイルに接続情報を記述して読み込むならば、Javaのプロパティファイル形式で記述し読み込み、3番目のシグニチャでプロパティを指定するのがよさそうです。

ここで取得したConnectionインスタンスは最後にclose()する必要があります。

Spectrumテーブルからidを指定して1件のレコードを取得

Spectrumテーブルから、idを指定して、3つの列(名前、周波数の開始値、周波数の終了値)を取り出します。

public Spectrum getSpectrum(long id) {
    try (PreparedStatement statement = conn.prepareStatement("SELECT name, startFrequency, stopFrequency FROM Spectrums WHERE id=?")) {
        statement.setLong(1, id);
        ResultSet result = statement.executeQuery();
        if (result.next()) {
           String name = result.getNString("name");
           double startFrequency = result.getDouble("startFrequency");
           double stopFrequency = result.getDouble("stopFrequency");
           return new Spectrum(name, startFrequency, stopFrequency);
        }
    } catch (SQLException ex) {
        // ...
    }

安全性から、SQLはPreparedStatementで用意します。プレースホルダーでWHERE句のidの値を設定します。プレースホルダーは複数設けられるので設定に際しては何番目かを示す数値と、埋め込む値を指定します。 PreparedStatementが完成したら、executeQueryで検索系のSQLを実行します。結果はResultSet型のオブジェクトで返ります。 ResultSetの中身を取り出すには、最初にnext()を呼びます。複数レコードが結果として返ってきた場合は、1レコード毎に最初にnext()を呼びます。 ResultSetの中身を取り出すには、列名を指定するか、序数を指定します。序数は、SELECT文の場合、SELECTの次に指定する列名(カンマ区切り)の列挙の順番を示します(最初が1)。最初は序数で指定していましたが、SELECTで取り出す列名を変えたりしておりそのたびに序数を付け直すのが手間でした。そこで、列名で指定するようにしました。列名は定数で一か所に定義しておくのがよいでしょう。

PreparedStatementは実行後にcloseする必要があります。try-with-resource構文で使用すれば自動でcloseを呼び出します。