Jdbiへのガイド

1前書き

この記事では、http://jdbi.org/[jdbi]を使用してリレーショナルデータベースをクエリする方法について説明します。

Jdbiは、 lambda式 リフレクション を使用して、linkよりも親しみやすく、より高レベルのインターフェースを提供するオープンソースのJavaライブラリ(Apacheライセンス)です。/java-jdbc[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>

最新版の__https://search.maven.org/classic/#search%7C1%7Cg%3A%22org.jdbi%22%20AND%20a%3A%22jdbi3-core%22[jdbi3-] HSQLDB およびその他のJdbiモジュールMaven Centralで。

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);
}

幸いなことに、ご覧のとおり、 Handle Closeable を実装しているので、 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はJDBC ResultSet を抽象化したもので、これはかなり面倒なAPIを持っています。

したがって、クエリや結果を返す他のステートメントから生じる列にアクセスするためのいくつかの可能性があります。

最も単純なものが表示されます。

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

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();

または他の Collection タイプに:

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

あるいは、結果をストリームとして反復することもできます。

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

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

5.4. 単一の結果を得る

特別な場合として、1行だけを期待したり興味を持ったりするときには、2つの専用メソッドを利用できます。

  • せいぜい1つの結果** が必要な場合は、 findFirst を使用できます。

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

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

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

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

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

最後に、結果が0個または1個以上の場合、 findOnly IllegalStateException をスローします。

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

**

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

  • セキュリティ:文字列の連結を避けることで、SQLインジェクションを防ぐ

簡単:複雑なデータ型の正確な構文を覚えておく必要はありません。

タイムスタンプなど パフォーマンス:クエリの静的部分は一度解析でき

キャッシュされた

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);

しかし、オブジェクトのフィールドやメソッドをバインドすることもできます。サポートされているすべてのオプションについては、http://jdbi.org/# binding arguments[the Jdbi documentation]を参照してください。

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

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

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

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

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

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

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

私たちが 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 は、以前に見た Query クラス** によって実装されているものと同じインターフェースです。そのため、使用方法はすでにわかっています。

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

8トランザクション

単一のアトミック操作として複数のステートメントを実行しなければならない場合は常にトランザクションが必要です

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

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

そして、handleと同様に、クロージャが戻るとトランザクションは自動的に閉じられます。

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

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オブジェクト拡張についても説明していません。

詳細はすべてhttp://jdbi.org/[Jdbiのドキュメント]に記載されています。

これらすべての例とコードスニペットの実装はhttps://github.com/eugenp/tutorials/tree/master/persistence-modules/java-jdbi[GitHubプロジェクト]にあります。インポートしてそのまま実行するのは簡単なはずです。