1. 序章

この記事では、jdbiを使用してリレーショナルデータベースをクエリする方法を見ていきます。

JdbiはオープンソースのJavaライブラリ(Apacheライセンス)であり、ラムダ式リフレクションを使用して、JDBCよりも使いやすく高レベルのインターフェイスを提供してデータベースにアクセスします。

ただし、JdbiはORMではありません。オプションのSQLオブジェクトマッピングモジュールがありますが、アタッチされたオブジェクト、データベース独立レイヤー、およびその他のベルやホイッスルとのセッションはありません。典型的なORM。

2. Jdbiセットアップ

Jdbiは、コアモジュールといくつかのオプションモジュールで構成されています。

開始するには、依存関係にコアモジュールを含める必要があります。

<dependencies>
    <dependency>
        <groupId>org.jdbi</groupId>
        <artifactId>jdbi3-core</artifactId>
        <version>3.1.0</version>
    </dependency>
</dependencies>

この記事では、HSQLデータベースを使用した例を示します。

<dependency>
    <groupId>org.hsqldb</groupId>
    <artifactId>hsqldb</artifactId>
    <version>2.4.0</version>
    <scope>test</scope>
</dependency>

最新バージョンのjdbi3-core HSQLDB 、およびその他のJdbiモジュールはMavenCentralにあります。

3. データベースへの接続

まず、データベースに接続する必要があります。 そのためには、接続パラメータを指定する必要があります。

出発点はJdbiクラスです。

Jdbi jdbi = Jdbi.create("jdbc:hsqldb:mem:testDB", "sa", "");

ここでは、接続URL、ユーザー名、そしてもちろんパスワードを指定しています。

3.1. 追加パラメータ

他のパラメータを提供する必要がある場合は、Propertiesオブジェクトを受け入れるオーバーロードされたメソッドを使用します。

Properties properties = new Properties();
properties.setProperty("username", "sa");
properties.setProperty("password", "");
Jdbi jdbi = Jdbi.create("jdbc:hsqldb:mem:testDB", properties);

これらの例では、Jdbiインスタンスをローカル変数に保存しています。 これは、データベースにステートメントとクエリを送信するために使用するためです。

実際、 create を呼び出すだけでは、DBへの接続は確立されません。 後で使用するために接続パラメータを保存するだけです。

3.2. DataSourceを使用する

通常の場合のように、 DataSource を使用してデータベースに接続する場合は、適切な createoverloadを使用できます。

Jdbi jdbi = Jdbi.create(datasource);

3.3. ハンドルの操作

データベースへの実際の接続は、Handleクラスのインスタンスによって表されます。

ハンドルを操作して自動的に閉じる最も簡単な方法は、ラムダ式を使用することです。

jdbi.useHandle(handle -> {
    doStuffWith(handle);
});

値を返す必要がない場合は、useHandleを呼び出します。

それ以外の場合は、withHandleを使用します。

jdbi.withHandle(handle -> {
    return computeValue(handle);
});

推奨されていませんが、接続ハンドルを手動で開くこともできます。 その場合、完了したら閉じる必要があります。

Jdbi jdbi = Jdbi.create("jdbc:hsqldb:mem:testDB", "sa", "");
try (Handle handle = jdbi.open()) {
    doStuffWith(handle);
}

幸いなことに、ご覧のとおり、 HandleCloseableを実装しているため、try-with-resourcesで使用できます。

4. 簡単なステートメント

接続を取得する方法がわかったので、それを使用する方法を見てみましょう。

このセクションでは、記事全体で使用する簡単なテーブルを作成します。

create table などのステートメントをデータベースに送信するには、executeメソッドを使用します。

handle.execute(
  "create table project "
  + "(id integer identity, name varchar(50), url varchar(100))");

execute は、ステートメントの影響を受けた行の数を返します。

int updateCount = handle.execute(
  "insert into project values "
  + "(1, 'tutorials', 'github.com/eugenp/tutorials')");

assertEquals(1, updateCount);

実際、executeは単なる便利な方法です。

後のセクションでより複雑なユースケースを見ていきますが、その前に、データベースから結果を抽出する方法を学ぶ必要があります。

5. データベースのクエリ

DBから結果を生成する最も簡単な式は、SQLクエリです。

Jdbiハンドルを使用してクエリを発行するには、少なくとも次のことを行う必要があります。

  1. クエリを作成する
  2. 各行の表現方法を選択してください
  3. 結果を繰り返します

ここで、上記の各ポイントを見ていきます。

5.1. クエリの作成

当然のことながら、JdbiはクエリをQueryクラスのインスタンスとして表します。

ハンドルから取得できます。

Query query = handle.createQuery("select * from project");

5.2. 結果のマッピング

Jdbiは、非常に面倒なAPIを持つJDBC ResultSetから抽象化します。

したがって、クエリまたは結果を返すその他のステートメントの結果の列にアクセスするためのいくつかの可能性があります。 最も単純なものを見ていきます。

各行をマップとして表すことができます。

query.mapToMap();

マップのキーは、選択した列名になります。

または、クエリが単一の列を返す場合、それを目的のJavaタイプにマップできます。

handle.createQuery("select name from project").mapTo(String.class);

Jdbiには、多くの一般的なクラス用の組み込みマッパーがあります。一部のライブラリまたはデータベースシステムに固有のものは、個別のモジュールで提供されます。

もちろん、マッパーを定義して登録することもできます。 これについては、後のセクションで説明します。

最後に、行をBeanまたはその他のカスタムクラスにマップできます。 繰り返しになりますが、専用のセクションでより高度なオプションを確認できます。

5.3. 結果を反復処理する

適切なメソッドを呼び出して結果をマッピングする方法を決定すると、ResultIterableオブジェクトを受け取ります。

次に、それを使用して、一度に1行ずつ結果を反復処理できます。

ここでは、最も一般的なオプションを見ていきます。

結果をリストに蓄積するだけです。

List<Map<String, Object>> results = query.mapToMap().list();

または別のコレクションタイプへ:

List<String> results = query.mapTo(String.class).collect(Collectors.toSet());

または、結果をストリームとして繰り返すことができます。

query.mapTo(String.class).useStream((Stream<String> stream) -> {
    doStuffWith(stream)
});

ここでは、わかりやすくするために stream 変数を明示的に入力しましたが、必ずしもそうする必要はありません。

5.4. 単一の結果を取得する

特別な場合として、1つの行だけを期待または関心がある場合は、いくつかの専用の方法を使用できます。

多くても1つの結果が必要な場合は、findFirstを使用できます。

Optional<Map<String, Object>> first = query.mapToMap().findFirst();

ご覧のとおり、オプションの値を返します。これは、クエリが少なくとも1つの結果を返した場合にのみ存在します。

クエリが複数の行を返す場合は、最初の行のみが返されます。

代わりに、 1つだけの結果が必要な場合は、findOnlyを使用します。

Date onlyResult = query.mapTo(Date.class).findOnly();

最後に、結果が0または複数ある場合、findOnlyIllegalStateExceptionをスローします。

6. バインディングパラメータ

多くの場合、クエリには固定部分とパラメータ化された部分があります。これには、次のようないくつかの利点があります。

  • セキュリティ:文字列の連結を回避することで、SQLインジェクションを防止します
  • 使いやすさ:タイムスタンプなどの複雑なデータ型の正確な構文を覚えておく必要はありません
  • パフォーマンス:クエリの静的部分を1回解析して、キャッシュすることができます

Jdbiは、位置パラメーターと名前付きパラメーターの両方をサポートします。

クエリまたはステートメントに疑問符として位置パラメータを挿入します。

Query positionalParamsQuery =
  handle.createQuery("select * from project where name = ?");

代わりに、名前付きパラメーターはコロンで始まります。

Query namedParamsQuery =
  handle.createQuery("select * from project where url like :pattern");

いずれの場合も、パラメーターの値を設定するには、bindメソッドのバリエーションの1つを使用します。

positionalParamsQuery.bind(0, "tutorials");
namedParamsQuery.bind("pattern", "%github.com/eugenp/%");

JDBCとは異なり、インデックスは0から始まることに注意してください。

6.1. 複数の名前付きパラメーターを一度にバインドする

オブジェクトを使用して、複数の名前付きパラメーターをバインドすることもできます。

この単純なクエリがあるとしましょう:

Query query = handle.createQuery(
  "select id from project where name = :name and url = :url");
Map<String, String> params = new HashMap<>();
params.put("name", "REST with Spring");
params.put("url", "github.com/eugenp/REST-With-Spring");

次に、たとえば、マップを使用できます。

query.bindMap(params);

または、オブジェクトをさまざまな方法で使用できます。 ここでは、たとえば、JavaBeanの規則に従うオブジェクトをバインドします。

query.bindBean(paramsBean);

ただし、オブジェクトのフィールドまたはメソッドをバインドすることもできます。 サポートされているすべてのオプションについては、Jdbiのドキュメントを参照してください。

7. より複雑なステートメントの発行

クエリ、値、およびパラメータを確認したので、ステートメントに戻って同じ知識を適用できます。

前に見たexecuteメソッドは便利なショートカットであることを思い出してください。

実際、クエリと同様に、DDLおよびDMLステートメントはクラスUpdateのインスタンスとして表されます。

ハンドルでメソッドcreateUpdateを呼び出すことで取得できます。

Update update = handle.createUpdate(
  "INSERT INTO PROJECT (NAME, URL) VALUES (:name, :url)");

次に、 Update で、 Query にあるすべてのバインディングメソッドがあるので、セクション6です。 更新にも適用されます。url

execute を呼び出すと、ステートメントが実行されます。

int rows = update.execute();

すでに見てきたように、影響を受ける行の数を返します。

7.1. 自動インクリメント列値の抽出

特別な場合として、自動生成された列(通常は自動インクリメントまたはシーケンス)を含む挿入ステートメントがある場合、生成された値を取得したい場合があります。

次に、 execute ではなく、executeAndReturnGeneratedKeysを呼び出します。

Update update = handle.createUpdate(
  "INSERT INTO PROJECT (NAME, URL) "
  + "VALUES ('tutorials', 'github.com/eugenp/tutorials')");
ResultBearing generatedKeys = update.executeAndReturnGeneratedKeys();

ResultBearingは、以前に見たQueryクラスによって実装されたものと同じインターフェースであるため、その使用方法はすでにわかっています。

generatedKeys.mapToMap()
  .findOnly().get("id");

8. トランザクション

複数のステートメントを単一の不可分操作として実行する必要がある場合は常に、トランザクションが必要です。

接続ハンドルと同様に、クロージャーを使用してメソッドを呼び出すことにより、トランザクションを導入します。

handle.useTransaction((Handle h) -> {
    haveFunWith(h);
});

また、ハンドルと同様に、クロージャが戻るとトランザクションは自動的に閉じられます。

ただし、返す前にトランザクションをコミットまたはロールバックする必要があります。

handle.useTransaction((Handle h) -> {
    h.execute("...");
    h.commit();
});

ただし、クロージャから例外がスローされた場合、Jdbiはトランザクションを自動的にロールバックします。

ハンドルと同様に、クロージャーから何かを返したい場合は、専用のメソッドinTransactionがあります。

handle.inTransaction((Handle h) -> {
    h.execute("...");
    h.commit();
    return true;
});

8.1. 手動トランザクション管理

一般的なケースではお勧めしませんが、トランザクションを手動で開始およびクローズすることもできます。

handle.begin();
// ...
handle.commit();
handle.close();

9. 結論と参考文献

このチュートリアルでは、Jdbiのコアであるクエリ、ステートメント、およびトランザクションを紹介しました。

カスタムの行と列のマッピングやバッチ処理など、いくつかの高度な機能は省略しています。

また、オプションのモジュール、特にSQLオブジェクト拡張についても説明していません。

すべてはJdbiドキュメントに詳細に示されています。

これらすべての例とコードスニペットの実装は、 GitHubプロジェクトにあります。これはMavenプロジェクトであるため、そのままインポートして実行するのは簡単です。