asentinel-ormによるランタイム定義カラム

asentinel-ormは、Spring JDBC、特にJdbcTemplateの上に構築された軽量ORMツールです。そのため、SQL生成、遅延読み込みなど、基本的なORMから期待されるほとんどの機能を備えています。

JdbcTemplateを活用することで、Spring管理のトランザクションに参加でき、すでにJdbcTemplateを使用してデータベースと対話するプロジェクトに簡単に統合できます。

2015年以降、asentinel-ormは複数のアプリケーションで成功裏に使用され、ビジネスニーズに応じて継続的に改善されてきました。2024年の夏には、正式にオープンソースプロジェクトとなり、これにより進化が加速し、貢献者の数が増えると考えています。

この記事では、いくつかのORMの主要機能を概説するためにサンプルアプリケーションを構築します:

  • シンプルな設定
  • カスタムアノテーションによる直感的なドメインエンティティモデリング
  • プレーンSQL文の簡単な記述と安全な実行
  • 自動SQL文の生成
  • 動的スキーマ(エンティティは追加のランタイム属性で強化され、コード変更なしで永続化および読み取られます)

アプリケーション

セットアップ

  • Java 21
  • Spring Boot 3.4.0
  • asentinel-orm 1.70.0
  • H2データベース

設定

asentinel-ormとやり取りし、その機能を活用するためには、OrmOperations のインスタンスが必要です。

JavaDocに記載されているように、この はORM操作を実行するための中心的なインタフェースであり、クライアントコードで特に実装する必要はありません。

サンプルアプリケーションには、このタイプのビーンを作成するための構成コードが含まれています。

Java

 

OrmOperations には2つのスーパーインタフェースがあります:

  • SqlBuilderFactorySqlBuilder のインスタンスを作成し、さらにSQLクエリを作成するために使用できます。 SqlBuilderは、クエリの一部を自動生成することができます。たとえば、列を選択するクエリの一部を生成できます。where句、order by句、その他の条件、実際の列は、SqlBuilderクラスのメソッドを使用して追加できます。このセクションの次の部分では、生成されたSqlBuilderクエリの例を示します。
  • Updater – エンティティをそれぞれのデータベーステーブルに保存するために使用されます。エンティティが新しく作成されたものか既に存在するものかに応じて、挿入または更新を実行できます。NewEntityDetector という戦略インタフェースが存在し、エンティティが新しいものかどうかを判定するために使用されます。デフォルトでは、SimpleNewEntityDetector が使用されます。

ORMによって生成されたすべてのクエリは、SqlQueryTemplate インスタンスを使用して実行され、さらにSpringのJdbcOperations/JdbcTemplate が必要です。最終的に、すべてのクエリは、Springトランザクションに参加しながら実行される古き良きJdbcTemplateに到達します。これは、任意のJdbcTemplate の直接実行と同様です

データベース特有のSQL構文とロジックは、JdbcFlavorインターフェースの実装を介して提供され、上記のほとんどのビーンに注入されます。本記事では、H2データベースが使用されるため、H2JdbcFlavor実装が構成されています。

サンプルアプリケーションの一部としてのORMの完全な構成はOrmConfigです。

実装

サンプルアプリケーションによって公開された実験的なドメインモデルは簡単明瞭で、2つのエンティティ – 自動車メーカーと 自動車モデルで構成されています。 名前が示す通りの関係は明白で、1つの自動車メーカーが複数の自動車モデルを所有する可能性があります。

自動車メーカーは、その名前に加えて、アプリケーションユーザーが実行時に動的に入力する属性(カラム)で強化されています。示されたユースケースは非常に単純です:

  • ユーザーには、動的属性の目的の名前とタイプを提供するように依頼されています
  • いくつかの自動車メーカーが、以前に追加された動的属性に具体的な値で作成され、その後
  • エンティティは、初期属性とランタイムで定義された属性の両方によって記述された状態でロードされます

初期エンティティは、以下のデータベーステーブルを使用してマッピングされます:

SQL

 

対応するドメインクラスは、上記のデータベーステーブルへのマッピングを構成するためにORM固有の注釈で装飾されています。

Java

 

Java

 

いくつかの考慮事項:

  • @Table – クラスをデータベーステーブルにマッピング(関連付け)します
  • @PkColumnid(一意の識別子)をテーブルの主キーにマッピングします
  • @Column – クラスのメンバーをテーブルの列にマッピングします
  • @Child – 別のエンティティとの関係を定義します
  • @Child が注釈付けされたメンバー – 遅延読み込みされるように構成されています
  • type テーブル列 – enum フィールド CarType にマッピングされます

CarManufacturer クラスがランタイムで定義された属性(ランタイムで定義されたテーブル列にマッピングされた属性)をサポートするために、以下のようなサブクラスが定義されています:

Java

 

このクラス は、ランタイムで定義された属性(フィールド)を Map に保存します。ランタイムフィールド値とORMとの相互作用は、DynamicColumnEntity インターフェースの実装によって実現されています。

Java

 

  • setValue() – テーブルから読み取られたランタイム定義の列の値を設定するために使用されます。
  • getValue() – テーブルに保存されたときにランタイムで定義された列の値を取得するために使用されます。

DynamicColumn は、@Column アノテーションがコンパイル時に既知のメンバーをマッピングするのと同様に、ランタイムで定義された属性をそれに対応する列にマッピングします。

アプリケーションを実行すると、CfRunner が実行されます。ユーザーには、CarManufacturer エンティティを拡張するための動的カスタム属性の名前とタイプを入力するように求められます(単純化のため、int および varchar  タイプのみがサポートされています)。

名前とタイプのペアごとに、新しい列をCarManufacturer データベーステーブルに追加するために、DML コマンドが実行されます。次のメソッド(CarService で宣言)がこの操作を実行します。

Java

 

各入力属性は、DefaultDynamicColumnDynamicColumn のリファレンス実装として記録されます。

すべての属性が定義されると、ユーザーがそれぞれの属性に値を提供することで、2つのカーメーカーがデータベースに追加されます。

Java

 

以下のメソッド(CarService で宣言)が実際に ORM を介してエンティティを作成します。

Java

 

OrmOperations update()メソッドの2つのパラメーター版が呼び出され、UpdateSettingsインスタンスを渡すことができ、実行時に定義された値が永続化されるべきであることをORMに伝えます。

最後に、以前追加された自動車メーカーの1つに対応する2つの車モデルが作成されます。

Java

 

以下のメソッド(CarServiceで宣言された)は、実際にORMを通じてエンティティを作成し、この時は動的属性なしでエンティティを永続化するためにOrmOperationsのupdate()メソッドを使用します。便利さのために、1回の呼び出しで複数のエンティティが作成されます。

Java

 

最後のステップとして、作成されたメーカーの1つが、その名前を使用してORM生成のクエリで再ロードされます。

Java

 

上記で定義されたメソッドに関するいくつかの説明は価値があります。

OrmOperations newSqlBuilder() メソッドはSqlBuilderインスタンスを生成し、その名前が示すように、SQLクエリを生成するために使用できます。SqlBuilder select() メソッドはクエリのテーブルから選択する部分を生成し、残りの部分(where、order by)は追加する必要があります。クエリの選択部分は、EntityDescriptorNodeCallback インスタンスを渡すことでカスタマイズできます(EntityDescriptorNodeCallbackの詳細は将来の記事の主題になるかもしれません)。

ORMに対して、実行時に定義された列を選択してマッピングする計画であることを知らせるために、DynamicColumnsEntityNodeCallback を渡す必要があります。それとともに、AutoEagerLoader を提供することで、ORMが製造元に関連するCarModels のリストをイagerにロードすることを理解します。しかしながら、これは実行時に定義された属性とは関係がありませんが、子メンバーがどのようにイagerにロードされるかを示しています。

結論

データがリレーショナルデータベースに格納されている場合に実行時に定義された列を扱う他の方法があるかもしれませんが、この記事で提示されたアプローチは、ORMによって直接生成された標準SQLクエリを使用して読み書きされる標準データベース列を使用するという利点があります。

「コミュニティ」でasentinel-ormや、そのようなツールを開発する理由について議論する機会があったことは珍しくありませんでした。通常、一見したところ、開発者はカスタムメイドのORMに対して消極的で控えめな姿勢を示し、なぜHibernateや他のJPA実装を使わないのかと尋ねました。

私たちの場合、主な動機は、ビジネスドメインの一部であるエンティティのために、時にはかなりの数の実行時に定義された属性(列)を迅速かつ柔軟かつ簡単に扱う必要があったことです。私たちにとって、それは正しい方法であることが証明されました。アプリケーションは本番環境でスムーズに実行されており、顧客はスピードと達成されたパフォーマンスに満足しており、開発者は直感的なAPIを使って快適で創造的です。

プロジェクトがオープンソースになったため、興味のある誰でも簡単に見て、客観的な意見を形成し、フォークし、PRを開き、貢献することができます。

リソース

  • オープンソースのORMプロジェクトはこちらです。
  • サンプルアプリケーションのソースコードはこちらです。

Source:
https://dzone.com/articles/runtime-defined-columns-with-asentinel-orm