Jdbiへのガイド

Jdbiへのガイド

1. 前書き

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

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

Jdbi, however, isn’t an ORM;には、オプションのSQLオブジェクトマッピングモジュールがありますが、アタッチされたオブジェクト、データベース独立レイヤー、および一般的なORMの他のベルやホイッスルとのセッションはありません。

2. Jdbiセットアップ

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

開始するには、コアモジュールを依存関係に含めるだけです。


    
        org.jdbi
        jdbi3-core
        3.1.0
    

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


    org.hsqldb
    hsqldb
    2.4.0
    test

Maven Centralで、最新バージョンのjdbi3-coreHSQLDB、およびその他のJdbiモジュールを見つけることができます。

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を使用してデータベースに接続する場合、適切なcreateオーバーロードを使用できます。

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 represents queries as instances of the Query class.

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

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

5.2. 結果のマッピング

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

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

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

query.mapToMap();

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

または、クエリが単一の列を返す場合、目的のJava型にマッピングできます。

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

Jdbi has built-in mappers for many common classes.一部のライブラリまたはデータベースシステムに固有のものは、個別のモジュールで提供されます。

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

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

5.3. 結果を繰り返す

適切なメソッドwe receive a ResultIterable object.を呼び出して結果をマッピングする方法を決定したら、

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

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

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

List> results = query.mapToMap().list();

または別のCollectionタイプに:

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

または、結果をストリームとして反復できます。

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

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

5.4. 単一の結果を得る

特別なケースとして、1つの行だけを期待している場合、または1つの行のみに関心がある場合、専用のメソッドをいくつか使用できます。

at most one resultが必要な場合は、findFirstを使用できます。

Optional> first = query.mapToMap().findFirst();

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

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

代わりに、one and only one resultが必要な場合は、findOnlyを使用します。

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

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

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

多くの場合、queries have a fixed portion and a parameterized portion.これには次のようないくつかの利点があります。

  • セキュリティ:文字列の連結を避けることで、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 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);

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

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

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

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

実際、クエリと同様に、DDL and DML statements are represented as instances of the class 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. 自動インクリメント列値の抽出

特別なケースとして、自動生成された列(通常は自動インクリメントまたはシーケンス)を持つinsertステートメントがある場合、生成された値を取得することができます。

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

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

以前に見たResultBearing is the same interface implemented by the Query classなので、使用方法はすでにわかっています。

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

8. トランザクション

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

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

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

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

戻る前のHowever, we must commit or rollback the transaction

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

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

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

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

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

一般的な場合はお勧めしませんが、トランザクションを手動でbeginおよびcloseすることもできます。

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

9. 結論と参考資料

このチュートリアルでは、Jdbiのコアを紹介しました:queries, statements, and transactions.

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

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

すべてがJdbi documentationに詳細に表示されます。

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