Spring DataJdbcがテーブルを結合する方法

この投稿では、Spring DataJdbcが関連エンティティを取得するためのsqlクエリを構築する方法を見ていきます。



投稿は初心者プログラマー向けに設計されており、非常にトリッキーなものは含まれていません。





オンラインコース「JavaDeveloper」のデモデーに皆さんを招待しますプロフェッショナル」イベントの一環として、コースプログラムの詳細とご質問にお答えします。





Hibernateのようなソリューションの大部分が使用されます。これは、ネストされたオブジェクトを操作する場合に非常に便利です。



たとえば、クラスRecordPackageがあり、このクラスのフィールドの1つは、子(またはネストされた)オブジェクトのコレクションであるrecordsです。



Jdbcを使用する場合は、多くのルーチンコードを作成する必要があります。それを好む人はほとんどいません。そのため、Hiberhateを使用しています。



Hibernateを使用すると、一度に1つのメソッドを呼び出して、すべての子レコードを含むRecordPackageを取得できます。



1つのメソッドを使用してオブジェクト全体を取得したい一方で、Hibernateモンスターをいじりたくないです。



Spring Data Jdbcを使用すると、これら2つの世界(または少なくとも許容できるもの)を最大限に活用できます。



2つのケースを考えてみましょう。



  • 1対多の関係
  • 1対1の関係




実際に最も頻繁に遭遇するのはこれらの接続です。



例の完全なコードはGitHubにあります。ここでは、最小限のものだけを示します。

まず第一に、Spring DataJdbcは問題を解決するための魔法のツールではないことに注意してくださいそれには確かに欠点と制限があります。

ただし、多くの一般的なタスクでは、これは完全に適切なソリューションです。



1対多の関係



実際の例として、いくつかのデータのパケットのヘッダーと、このパケットに含まれるデータ行を検討できます。たとえば、fileはパッケージであり、file行はそのパッケージに入るデータ行です。



テーブルの構造は次のとおりです。



create table record_package
(
    record_package_id bigserial    not null
        constraint record_package_pk primary key,
    name              varchar(256) not null
);

create table record
(
    record_id         bigserial    not null
        constraint record_pk primary key,
    record_package_id bigint       not null,
    data              varchar(256) not null
);

alter table record
    add foreign key (record_package_id) references record_package;




2つのテーブル:(record_package特定のパケットのヘッダー)とrecord(パケットに含まれるレコード)。

この関係がJavaコードでどのように表示されるか:



@Table("record_package")
public class RecordPackage {
    @Id
    private final Long recordPackageId;
    private final String name;

    @MappedCollection(idColumn = "record_package_id")
    private final Set<Record> records;
….
}




ここでは、1対多の関係を定義することに関心があります。これは、注釈を使用してエンコードされ@MappedCollectionます。



この注釈には2つのパラメータがあります



。idColumn-接続が確立されるフィールド

; keyColumn-子テーブルのレコードが順序付けられるフィールド。



この順序については、別途言及する価値があります。この例では、子レコードがレコードテーブルに挿入される順序は重要ではありませんが、場合によっては重要になることがあります。このような順序付けの場合、レコードテーブルにはrecord_noのようなフィールドがあり、MappedCollectionアノテーションのkeyColumnに書き込む必要があるのはこのフィールドです。挿入を実行すると、Spring DataJdbcはこのフィールドの値を生成します。注釈に加えて、セットの<レコード>・リストに交換する必要があります<録音>非常に論理的で理解しやすいです。明示的に指定された子行のシーケンスは、選択を形成するときに考慮されますが、後でこれに戻ります。



これで、接続を特定し、試す準備ができました。



関連するエンティティを作成し、ベースから取得します。



  var record1 = new Record("r1");
  var record2 = new Record("r2");
  var record3 = new Record("r3");

   var recordPackage = new RecordPackage( "package", Set.of(record1, record2, record3));
   var recordPackageSaved = repository.save(recordPackage);

   var recordPackageLoaded = repository.findById(recordPackageSaved.getRecordPackageId());




レコードのコレクションがいっぱいのrepository.findByIdインスタンスを取得するには、 1つのメソッドを呼び出すだけでよいことに注意してくださいRecordPackage



もちろん、ネストされたレコードのコレクションを取得するために実行されたsqlクエリの種類に関心があります。



Hibernateと比較すると、Spring DataJdbcはその単純さの点で優れています。簡単にデバッグして要点を明らかにすることができます。org.springframework.data.jdbc.core.convert



パッケージ少し調べた後DefaultDataAccessStrategyクラスが見つかりましたこのクラスは、クラス情報に基づいてSQLクエリを生成する役割を果たします。このクラスでは、メソッドに関心があります
反復可能な<オブジェクト> findAllByPath




またはより正確には、行:



String findAllByProperty = sql(actualType) 
    .getFindAllByProperty(identifier, path.getQualifierColumn(), path.isOrdered());


ここでは、必要なSQLクエリが内部キャッシュから取得されます。



私たちの場合、次のようになります。



SELECT "record"."data" AS "data", "record"."record_id" AS "record_id", "record"."record_package_id" AS "record_package_id" 
FROM "record" 
WHERE "record"."record_package_id" = :record_package_id


すべてが明確で予測可能です。



子テーブルのレコードの順序を使用すると、どのようになりますか?もちろん、注文は必要になります。



org.springframework.data.relational.core.mappingパッケージのBasicRelationalPersistentPropertyクラスに移りましょう。このクラスには、クエリに順序を追加するかどうかを決定するメソッドがあります。



	
public boolean isOrdered() {
  return isListLike();
}


そして



private boolean isListLike() {
  return isCollectionLike() && !Set.class.isAssignableFrom(this.getType());
}




isCollectionLikeは、実際に「コレクション」(配列を含む)があることを確認します。

そして条件から!Set.class.isAssignableFrom(this.getType()); Setは偶然に使用されるのではなく、不要な並べ替えを除外するために使用されることが明らかになります。そしていつか、意図的にリストを使用して並べ替えを有効にする予定です。



1対多は多かれ少なかれ明確だと思います。次のケースに移りましょう。



1対1の関係



そのような構造があるとしましょう。



create table info_main
(
    info_main_id bigserial    not null
        constraint info_pk primary key,
    main_data    varchar(256) not null
);

create table info_additional
(
    info_additional_id bigserial    not null
        constraint additional_pk primary key,
    info_main_id       bigint       not null,
    additional_data    varchar(256) not null
);

alter table info_additional
    add foreign key (info_main_id) references info_main;


特定のオブジェクトに関する基本情報(info_main)と追加情報(info_additional)を含むテーブルがあります。



これをコードでどのように表すことができますか?



@Table("info_main")
public class InfoMain {
    @Id
    private final Long infoMainId;
    private final String mainData;

    @MappedCollection(idColumn = "info_main_id")
    private final InfoAdditional infoAdditional;
}




一見、最初の1対多の場合のように見えますが、違いがあります。今回は、子は実際にはオブジェクトであり、前の場合のようなコレクションではありません。



テスト用のコードは次のようになります。



  var infoAdditional = new InfoAdditional("InfoAdditional");

  var infoMain = new InfoMain("mainData", infoAdditional);

  var infoMainSaved = repository.save(infoMain);
  var infoMainLoaded = repository.findById(infoMainSaved.getInfoMainId());


今回生成されるsql式を見てみましょう。これを行うには、findByIdメソッドを次の場所に発掘します:



パッケージorg.springframework.data.jdbc.core.convertクラスDefaultDataAccessStrategy私たちはすでにこのクラスに精通しているので、今はメソッドに興味があります。



public <T> T findById(Object id, Class<T> domainType)




次のリクエストがキャッシュから取得されていることがわかります。



SELECT "info_main"."main_data" AS "main_data", "info_main"."info_main_id" AS "info_main_id", "infoAdditional"."info_main_id" AS "infoadditional_info_main_id", "infoAdditional"."additional_data" AS "infoadditional_additional_data", "infoAdditional"."info_additional_id" AS "infoadditional_info_additional_id" 
FROM "info_main" 
LEFT OUTER JOIN "info_additional" "infoAdditional" 
ON "infoAdditional"."info_main_id" = "info_main"."info_main_id" 
WHERE "info_main"."info_main_id" = :id




今のところ、左外側の結合は私たちに適していますが、そうでない場合はどうでしょうか。内部結合を取得するにはどうすればよいですか?

機能的なjoin-sの作成は、パッケージorg.springframework.data.jdbc.core.convert、クラスSqlGenerator、メソッドに含まれています。
private SelectBuilder.SelectWhere selectBuilder(Collection<SqlIdentifier> keyColumns)




このフラグメントに関心があります。



		
for (Join join : joinTables) {
  baseSelect = baseSelect.leftOuterJoin(join.joinTable).on(join.joinColumn).equals(join.parentId);
}




テーブルを結合する必要がある場合は、左外側結合のみのオプションがあります。

まだ内部結合ができないようです。



結論



Spring DataJdbcでテーブルを結合する方法の最も一般的な2つのケースについて説明しました。

原則として、私たちが現在持っている機能は、重大ではない制限はありますが、実際の問題を解決するのに非常に適しています。



例の全文はここにあります



そしてこれこの投稿のビデオ版です






All Articles