Spring BootでJDBIを使用する

1. 前書き

link:/jdbi [前のチュートリアル]で、http://jdbi.org/ [JDBI]の基本について説明しました。 JDBCの直接使用に関連する定型コード。
*今回は、Spring BootアプリケーションでJDBIを使用する方法について説明します*。 また、いくつかのシナリオでSpring Data JPAの優れた代替となるこのライブラリのいくつかの側面についても説明します。

2. プロジェクトのセットアップ

まず、プロジェクトに適切なJDBI依存関係を追加しましょう。 *今回は、JDBIのSpring統合プラグインを使用します。これは、必要なすべてのコア依存関係をもたらします*。 また、サンプルで使用するベースJDBIにいくつかの追加機能を追加するSqlObjectプラグインも導入します。
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-jdbc</artifactId>
    <version>2.1.8.RELEASE</version>
</dependency>
<dependency>
    <groupId>org.jdbi</groupId>
    <artifactId>jdbi3-spring4</artifactId>
    <version>3.9.1</version>
</dependency>
<dependency>
    <groupId>org.jdbi</groupId>
    <artifactId>jdbi3-sqlobject</artifactId>
    <version>3.9.1</version>
</dependency>
これらのアーティファクトの最新バージョンは、Maven Centralにあります。
  • Spring
    ブート開始JDBC

  • JDBI
    春の統合

  • JDBI
    SqlObjectプラグイン

    データベースにアクセスするには、適切なJDBCドライバーも必要です。 この記事では、https://search.maven.org/classic/#search%7Cga%7C1%7Cg%3A%22com.h2database%22%20AND%20a%3A%22h2%22 [H2]を使用します。ドライバーを依存関係リストにも追加する必要があります。
<dependency>
    <groupId>com.h2database</groupId>
    <artifactId>h2</artifactId>
    <version>1.4.199</version>
    <scope>runtime</scope>
</dependency>

3. JDBIのインスタンス化と構成

前の記事で、JDBIのAPIにアクセスするためのエントリポイントとして_Jdbi_インスタンスが必要であることを既に見てきました。 Springの世界にいるように、このクラスのインスタンスをBeanとして利用できるようにするのは理にかなっています。
Spring Bootの自動構成機能を活用して_DataSource_を初期化し、_ @ Bean_-annotatedメソッドに渡して、グローバル_Jdbi_インスタンスを作成します。
また、検出されたプラグインと_RowMapper_インスタンスをこのメソッドに渡し、それらが事前に登録されるようにします。
@Configuration
public class JdbiConfiguration {
    @Bean
    public Jdbi jdbi(DataSource ds, List<JdbiPlugin> jdbiPlugins, List<RowMapper<?>> rowMappers) {
        TransactionAwareDataSourceProxy proxy = new TransactionAwareDataSourceProxy(ds);
        Jdbi jdbi = Jdbi.create(proxy);
        jdbiPlugins.forEach(plugin -> jdbi.installPlugin(plugin));
        rowMappers.forEach(mapper -> jdbi.registerRowMapper(mapper));
        return jdbi;
    }
}
ここでは、利用可能な_DataSource_を使用し、_TransactionAwareDataSourceProxy_にラップしています。 * Springが管理するトランザクションをJDBIと統合するには、このラッパーが必要です*。これについては後で説明します。
プラグインとRowMapperインスタンスの登録は簡単です。 私たちがしなければならないことは、利用可能なすべてのJ_JdbiPlugin_と__RowMapper、__それぞれに対して_installPlugin_と_installRowMapper_を呼び出すことです。 その後、アプリケーションで使用できる完全に構成された_Jdbi_インスタンスが作成されます。

4. サンプルドメイン

この例では、_CarMaker_と_CarModel_の2つのクラスのみで構成される非常に単純なドメインモデルを使用します。 JDBIはドメインクラスに注釈を必要としないため、単純なPOJOを使用できます。
public class CarMaker {
    private Long id;
    private String name;
    private List<CarModel> models;
    // getters and setters ...
}

public class CarModel {
    private Long id;
    private String name;
    private Integer year;
    private String sku;
    private Long makerId;
    // getters and setters ...
}

5. DAOを作成する

それでは、ドメインクラスのデータアクセスオブジェクト(DAO)を作成しましょう。 JDBI SqlObjectプラグインは、これらのクラスを実装する簡単な方法を提供します。これは、この主題を扱うSpring Dataの方法に似ています。
いくつかのアノテーションを使用してインターフェイスを定義するだけで、* JDBIはJDBC接続の処理やステートメントと__ResultSet__s *の作成/破棄など、すべての低レベルのものを自動的に処理します。
@UseClasspathSqlLocator
public interface CarMakerDao {
    @SqlUpdate
    @GetGeneratedKeys
    Long insert(@BindBean CarMaker carMaker);

    @SqlBatch("insert")
    @GetGeneratedKeys
    List<Long> bulkInsert(@BindBean List<CarMaker> carMakers);

    @SqlQuery
    CarMaker findById(Long id);
}

@UseClasspathSqlLocator
public interface CarModelDao {
    @SqlUpdate
    @GetGeneratedKeys
    Long insert(@BindBean CarModel carModel);

    @SqlBatch("insert")
    @GetGeneratedKeys
    List<Long> bulkInsert(@BindBean List<CarModel> models);

    @SqlQuery
    CarModel findByMakerIdAndSku(@Bind("makerId") Long makerId, @Bind("sku") String sku );
}
これらのインターフェースには多くの注釈が付けられているため、それぞれを簡単に見てみましょう。

5.1. _ @ UseClasspathSqlLocator_

  • @ _ UseClasspathSqlLocator_アノテーションは、各メソッドに関連付けられた実際のSQLステートメントが外部リソースファイルにあることをJDBIに伝えます*。 デフォルトでは、JDBIはインターフェースの完全修飾名とメソッドを使用してリソースを検索します。 たとえば、_findById()_メソッドで_a.b.c.Foo_のインターフェイスのFQNを指定すると、JDBIは_a / b / c / Foo / findById.sql._という名前のリソースを探します。

    このデフォルトの動作は、リソース名を_ @ SqlXXX_アノテーションの値として渡すことにより、特定のメソッドでオーバーライドできます。

5.2。 @ SqlUpdate / @ SqlBatch / @ SqlQuery

  • _ @ SqlUpdate_、_ @ SqlBatch_、および_ @ SqlQuery_注釈を使用して、指定されたパラメーターを使用して実行されるデータアクセスメソッドをマークします*。 これらの注釈は、オプションの文字列値を取ることができます。この文字列値は、名前付きパラメーターを含む実行するリテラルSQLステートメントになります。

    _ @ SqlBatch_-annotatedメソッドは、コレクションのような引数を持ち、1つのバッチステートメントで使用可能なすべてのアイテムに対して同じSQLステートメントを実行できます。 上記の各DAOクラスには、使用方法を示す__bulkInsert __methodがあります。 バッチステートメントを使用する主な利点は、大きなデータセットを処理するときにパフォーマンスを向上できることです。

5.3。 @GetGeneratedKeys

名前が示すように、* _ @ GetGeneratedKeys_アノテーションを使用すると、正常に実行された結果として生成されたキーを回復できます*。 データベースが新しい識別子を自動生成する_insert_ステートメントで主に使用され、コードでそれらを回復する必要があります。

5.4。 @ BindBean / @ Bind

  • _ @ BindBean_および_ @ Bind_注釈を使用して、SQLステートメントの名前付きパラメーターをメソッドパラメーターにバインドします*。 _ @ BindBean_は標準のBean規則を使用して、ネストされたものを含むPOJOからプロパティを抽出します。 _ @ Bind_は、パラメーター名または指定された値を使用して、その値を名前付きパラメーターにマップします。

6. DAOを使用する

アプリケーションでこれらのDAOを使用するには、JDBIで利用可能なファクトリメソッドの1つを使用してインスタンス化する必要があります。
Springコンテキストで最も簡単な方法は、_onDemand_メソッドを使用してすべてのDAOにBeanを作成することです。
@Bean
public CarMakerDao carMakerDao(Jdbi jdbi) {
    return jdbi.onDemand(CarMakerDao.class);
}

@Bean
public CarModelDao carModelDao(Jdbi jdbi) {
    return jdbi.onDemand(CarModelDao.class);
}
  • onDemand-createdインスタンスはスレッドセーフであり、メソッド呼び出し中にのみデータベース接続を使用します*。 JDBIから、提供されている_TransactionAwareDataSourceProxy、_を使用します*これは、Springが管理するトランザクションでシームレスに使用できることを意味します

    単純ですが、ここで使用したアプローチは、数個のテーブルよりも多くを処理する必要がある場合、理想からはほど遠いものです。 この種の定型コードを記述しないようにする1つの方法は、カスタム__BeanFactoryを作成することです。 ただし、このようなコンポーネントの実装方法を説明することは、このチュートリアルの範囲外です。

7. トランザクションサービス

モデルが設定された_CarMaker_が与えられた少数の_CarModel_インスタンスを作成する単純なサービスクラスでDAOクラスを使用してみましょう。 最初に、指定された_CarMaker_が以前に保存されたかどうかを確認し、必要に応じてデータベースに保存します。 次に、すべての_CarModel_を1つずつ挿入します。
*任意の時点で一意のキー違反(またはその他のエラー)が発生した場合、操作全体が失敗し、完全なロールバックを実行する必要があります*。
JDBIは_ @ Transaction_アノテーションを提供しますが、同じビジネストランザクションに参加している可能性のある他のリソースを認識しないため、ここでは使用できません*。 代わりに、サービスメソッドでSpringの_ @ Transactional_アノテーションを使用します。
@Service
public class CarMakerService {

    private CarMakerDao carMakerDao;
    private CarModelDao carModelDao;

    public CarMakerService(CarMakerDao carMakerDao,CarModelDao carModelDao) {
        this.carMakerDao = carMakerDao;
        this.carModelDao = carModelDao;
    }

    @Transactional
    public int bulkInsert(CarMaker carMaker) {
        Long carMakerId;
        if (carMaker.getId() == null ) {
            carMakerId = carMakerDao.insert(carMaker);
            carMaker.setId(carMakerId);
        }
        carMaker.getModels().forEach(m -> {
            m.setMakerId(carMaker.getId());
            carModelDao.insert(m);
        });
        return carMaker.getModels().size();
    }
}
操作の実装自体は非常に単純です。_id_フィールドの_null_値は、このエンティティがまだデータベースに永続化されていないことを示すという標準的な規則を使用しています。 この場合、コンストラクターに挿入された_CarMakerDao_インスタンスを使用して、データベースに新しいレコードを挿入し、生成された_id._を取得します
_CarMaker_ ’のIDを取得したら、モデルを反復処理して、データベースに保存する前に各モデルに__makerId ___fieldを設定します。
*これらのデータベース操作はすべて、同じ基になる接続を使用して行われ、同じトランザクションの一部になります*。 ここでのトリックは、_TransactionAwareDataSourceProxy_を使用してJDBIをSpringに結び付け、_onDemand_ DAOを作成する方法にあります。 JDBIが新しい_Connection_を要求すると、現在のトランザクションに関連付けられている既存の_Connection_が取得されるため、そのライフサイクルが登録される可能性のある他のリソースに統合されます。

8. 結論

*この記事では、JDBIをSpring Bootアプリケーションにすばやく統合する方法を示しました*。 これは、何らかの理由でSpring Data JPAを使用できないが、トランザクション管理、統合などの他のすべての機能を使用したいシナリオでの強力な組み合わせです。
いつものように、すべてのコードはhttps://github.com/eugenp/tutorials/tree/master/persistence-modules/spring-boot-jdbi[GitHubで入手可能]から入手できます。