1. 序章

前のチュートリアルでは、 JDBC リレーショナルデータベースアクセス用のオープンソースライブラリの基本について説明しました。これにより、直接JDBCに関連するボイラープレートコードの多くが削除されます。利用方法。

今回は、Spring BootアプリケーションでJDBIを使用する方法を説明します。 また、いくつかのシナリオでSpring DataJPAの優れた代替となるこのライブラリのいくつかの側面についても説明します。

2. プロジェクトの設定

まず、プロジェクトに適切なJDBI依存関係を追加しましょう。今回は、必要なすべてのコア依存関係をもたらすJDBIのSpring統合プラグインを使用します。 また、SqlObjectプラグインを導入します。これにより、例で使用するベースJDBIにいくつかの追加機能が追加されます。

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

これらのアーティファクトの最新バージョンは、MavenCentralにあります。

データベースにアクセスするには、適切なJDBCドライバーも必要です。 この記事では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 アノテーション付きメソッドに渡して、グローバル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インスタンスの登録は簡単です。 使用可能なすべてのJdbiPluginおよびRowMapper、に対して、それぞれinstallPluginおよびinstallRowMapperを呼び出すだけです。 その後、アプリケーションで使用できる完全に構成されたJdbiインスタンスができました。

4. サンプルドメイン

この例では、CarMakerCarModelの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接続の処理、ステートメントとResultSetsの作成/破棄などのすべての低レベルのものを処理します

@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はインターフェースの完全修飾名とメソッドを使用してリソースを検索します。 たとえば、 abcFooのインターフェイスのFQNがfindById()メソッドを使用している場合、JDBIは a / b / c / Foo/findById.sqlという名前のリソースを検索します。

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

5.2. @ SqlUpdate / @ SqlBatch / @ SqlQuery

@ SqlUpdate、@ SqlBatch、および@SqlQueryアノテーションを使用して、指定されたパラメーターを使用して実行されるデータアクセスメソッドをマークします。 これらのアノテーションは、オプションの文字列値を取ることができます。これは、実行するリテラルSQLステートメント(名前付きパラメーターを含む)、または @UseClasspathSqlLocator と一緒に使用すると、それを含むリソース名になります。

@SqlBatch -注釈付きメソッドは、コレクションのような引数を持ち、単一のバッチステートメントで使用可能なすべてのアイテムに対して同じSQLステートメントを実行できます。 上記の各DAOクラスには、その使用法を示すbullkInsertメソッドがあります。 バッチステートメントを使用する主な利点は、大規模なデータセットを処理するときに達成できるパフォーマンスの向上です。

5.3. @GetGeneratedKeys

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

5.4. @ BindBean / @ Bind

@BindBeanおよび@Bindアノテーションを使用して、SQLステートメントの名前付きパラメーターをメソッドparametersにバインドします。 @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で作成されたインスタンスはスレッドセーフであり、メソッド呼び出し中のみデータベース接続を使用します。 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フィールドを設定します。

これらのデータベース操作はすべて、同じ基になる接続を使用して行われ、同じトランザクションの一部になります。 ここでの秘訣は、 TransactionAwareDataSourceProxyを使用してJDBIをSpringに結び付け、 onDemandDAOを作成する方法にあります。 JDBIが新しいConnectionを要求すると、現在のトランザクションに関連付けられている既存の接続を取得するため、登録されている可能性のある他のリソースにライフサイクルが統合されます。

8. 結論

この記事では、JDBIをSpring Bootアプリケーションにすばやく統合する方法を示しました。 これは、何らかの理由でSpring Data JPAを使用できないが、トランザクション管理や統合などの他のすべての機能を使用したいシナリオでの強力な組み合わせです。

いつものように、すべてのコードはGitHubから入手できます。