1. 概要

アプリケーションからのSQLステートメントの管理は、パフォーマンスに大きな影響を与えるため、注意が必要な最も重要なことの1つです。 オブジェクト間のリレーションを操作する場合、フェッチには2つの主要なデザインパターンがあります。 最初のアプローチは怠惰なアプローチであり、もう1つは熱心なアプローチです。

この記事では、両方の概要を説明します。 さらに、Hibernateでの@LazyCollectionアノテーションについても説明します。

2. 遅延フェッチ

データの初期化を必要になるまで延期する場合は、遅延フェッチを使用します。 アイデアをよりよく理解するために例を見てみましょう。

市内に複数の支店を持つ会社があるとします。 すべての支店には独自の従業員がいます。 データベースの観点からは、ブランチとその従業員の間に1対多の関係があることを意味します。

レイジーフェッチアプローチでは、ブランチオブジェクトをフェッチすると、従業員はフェッチされません。 ブランチオブジェクトのデータのみをフェッチし、 getEmployees()メソッドを呼び出すまで、従業員のリストの読み込みを延期します。 その時点で、別のデータベースクエリが実行されて従業員が取得されます。

このアプローチの利点は、最初にロードされるデータの量を減らすことです。 その理由は、支店の従業員が必要ない可能性があり、すぐに使用する予定がないため、ロードしても意味がないためです。

3. 熱心なフェッチ

データを即座にロードする必要がある場合は、熱心なフェッチを使用します。会社、支店、および従業員の同じ例を取り上げて、このアイデアについても説明します。 データベースからブランチオブジェクトをロードすると、同じデータベースクエリを使用して、その従業員のリストもすぐにロードします。

熱心なフェッチを使用する場合の主な懸念事項は、不要な可能性のある大量のデータをロードすることです。 したがって、オブジェクトをロードすると、熱心にフェッチされたデータが常に使用されることが確実な場合にのみ使用する必要があります。

4. @LazyCollectionアノテーション

アプリケーションのパフォーマンスを管理する必要がある場合は、@LazyCollectionアノテーションを使用します。 Hibernate 3.0以降、@LazyCollectionはデフォルトで有効になっています。 @LazyCollectionを使用する主なアイデアは、データのフェッチでレイジーアプローチを使用するか、熱心なアプローチを使用するかを制御することです。

@LazyCollection を使用する場合、 LazyCollectionOption 設定には、 TRUE FALSE 、およびEXTRAの3つの構成オプションがあります。 ]。 それぞれを個別に説明しましょう。

4.1. LazyCollectionOption.TRUEを使用する

このオプションは、指定されたフィールドの遅延フェッチアプローチを有効にし、Hibernateバージョン3.0以降のデフォルトです。 したがって、このオプションを明示的に設定する必要はありません。 ただし、アイデアをより適切に説明するために、このオプションを設定した例を取り上げます。

この例では、 id name 、およびとの@OneToMany関係で構成されるBranchエンティティがあります。 ]Employeeエンティティ。 この例では、@LazyCollectionオプションを明示的にtrueに設定していることがわかります。

@Entity
public class Branch {

    @Id
    private Long id;

    private String name;

    @OneToMany(mappedBy = "branch")
    @LazyCollection(LazyCollectionOption.TRUE)
    private List<Employee> employees;
    
    // getters and setters
}

次に、 id name address 、およびで構成されるEmployeeエンティティを見てみましょう。 ]@ManyToOneBranchエンティティとの関係:

@Entity
public class Employee {

    @Id
    private Long id;

    private String name;

    private String address;
    
    @ManyToOne
    @JoinColumn(name = "BRANCH_ID") 
    private Branch branch; 

    // getters and setters 
}

上記の例では、ブランチオブジェクトを取得したときに、従業員のリストをすぐにロードしません。 代わりに、この操作は getEmployees()メソッドを呼び出すまで延期されます。

4.2. LazyCollectionOption.FALSEを使用する

このオプションをFALSEに設定すると、熱心なフェッチアプローチが有効になります。 この場合、Hibernateのデフォルト値をオーバーライドするため、このオプションを明示的に指定する必要があります。 別の例を見てみましょう。

この場合、 Branch エンティティがあり、 id name 、および@OneToManyの関係が含まれています。 Employeeエンティティ。 @LazyCollectionのオプションをFALSEに設定していることに注意してください。

@Entity
public class Branch {

    @Id
    private Long id;

    private String name;

    @OneToMany(mappedBy = "branch")
    @LazyCollection(LazyCollectionOption.FALSE)
    private List<Employee> employees;
    
    // getters and setters
}

上記の例では、ブランチオブジェクトを取得すると、すぐに従業員のリストをブランチにロードします

4.3. LazyCollectionOption.EXTRAを使用する

コレクションのプロパティのみに関心があり、コレクション内のオブジェクトをすぐに必要としない場合もあります。

たとえば、BranchEmployeeの例に戻ると、実際の従業員のエンティティを気にせずに、ブランチ内の従業員の数だけが必要になる可能性があります。 この場合、EXTRAオプションの使用を検討します。 このケースを処理するために例を更新しましょう。

前の場合と同様に、 Branch エンティティには、 id name 、および@OneToManyEmployeeの関係があります。 エンティティ。 ただし、@LazyCollectionのオプションをEXTRAに設定します。

@Entity
public class Branch {

    @Id
    private Long id;

    private String name;

    @OneToMany(mappedBy = "branch")
    @LazyCollection(LazyCollectionOption.EXTRA)
    @OrderColumn(name = "order_id")
    private List<Employee> employees;

    // getters and setters
    
    public Branch addEmployee(Employee employee) {
        employees.add(employee);
        employee.setBranch(this);
        return this;
    }
}

この場合、@OrderColumnアノテーションを使用していることがわかります。 その理由は、 EXTRA オプションは、インデックス付きリストコレクションに対してのみ考慮されるためです。 つまり、フィールドに @ OrderColumnで注釈を付けなかった場合、 EXTRA オプションを使用すると、レイジーと同じ動作が得られ、初めてアクセスしたときにコレクションがフェッチされます。

さらに、BranchEmployeeを両側から同期する必要があるため、 addEmployee()メソッドも定義します。 新しいEmployeeを追加してブランチを設定する場合は、Branchエンティティ内の従業員のリストも更新する必要があります。

ここで、3人の従業員が関連付けられている1つの Branch エンティティを永続化する場合、次のようにコードを記述する必要があります。

entityManager.persist(
  new Branch().setId(1L).setName("Branch-1")

    .addEmployee(
      new Employee()
        .setId(1L)
        .setName("Employee-1")
        .setAddress("Employee-1 address"))
  
    .addEmployee(
      new Employee()
        .setId(2L)
        .setName("Employee-2")
        .setAddress("Employee-2 address"))
  
    .addEmployee(
      new Employee()
        .setId(3L)
        .setName("Employee-3")
        .setAddress("Employee-3 address"))
);

実行されたクエリを見ると、HibernateがBranch-1の新しいBranchを最初に挿入することがわかります。 次に、Employee-1、Employee-2、Employee-3の順に挿入します。

これは自然な動作であることがわかります。 ただし、 EXTRA オプションの不正な動作は、上記のクエリをフラッシュした後、追加する Employee ごとに1つずつ、さらに3つのクエリを実行することです。

UPDATE EMPLOYEES
SET
    order_id = 0
WHERE
    id = 1
     
UPDATE EMPLOYEES
SET
    order_id = 1
WHERE
    id = 2
 
UPDATE EMPLOYEES
SET
    order_id = 2
WHERE
    id = 3

UPDATE ステートメントは、Listエントリインデックスを設定するために実行されます。 これは、 N + 1クエリの問題と呼ばれるものの例です。つまり、 N の追加のSQLステートメントを実行して、作成した同じデータを更新します。

この例からわかるように、 EXTRA オプションを使用すると、 N+1クエリの問題が発生する可能性があります。

一方、このオプションを使用する利点は、すべてのブランチの従業員リストのサイズを取得する必要がある場合です。

int employeesCount = branch.getEmployees().size();

このステートメントを呼び出すと、次のSQLステートメントのみが実行されます。

SELECT
    COUNT(ID)
FROM
    EMPLOYEES
WHERE
    BRANCH_ID = :ID

ご覧のとおり、サイズを取得するために従業員のリストをメモリに保存する必要はありませんでした。 それでも、 EXTRA オプションは追加のクエリを実行するため、避けることをお勧めします。

また、JPAとHibernateに限定されていないため、他のデータアクセステクノロジで N+1クエリの問題が発生する可能性があることにも注意してください。

5. 結論

この記事では、Hibernateを使用してデータベースからオブジェクトのプロパティをフェッチするためのさまざまなアプローチについて説明しました。

最初に、例を使用して遅延フェッチについて説明しました。 次に、熱心なフェッチを使用するように例を更新し、違いについて説明しました。

最後に、データをフェッチするための追加のアプローチを示し、その長所と短所を説明しました。

いつものように、この記事で紹介するコードは、GitHubから入手できます。