LazyCollectionOption.EXTRA について知る

しかし、最も興味深いのは LazyCollectionOption.EXTRA 値です。これを@LazyCollectionアノテーションの値として指定すると、Hibernate はコレクションの要素のロードを可能な限り遅延させます。

コレクション内の要素の数を取得しようとすると、次のようになります。

User user = session.load(User.class, 1);
List<Comment> comments = user.getComments();
int count = commetns.size();

次に、このすべてのコードに対して、Hibernate は 1 つのクエリのみを実行します。

SELECT COUNT(id) FROM comment WHERE user_id = 1;

ただし、コレクションから 1 つのコメント、たとえば番号 3 を取得したい場合は、次のようになります。

User user = session.load(User.class, 1);
List<Comment> comments = user.getComments();
Comment comment = commetns.get(3);

そこで疑問が生じます。すべての要素をメモリにロードせずに、Hibernate はその要素が 3 番目であることをどのようにして知るのでしょうか?

この問題を解決するには、コメント テーブルに追加の列を作成し、コメントのコレクション内のコメントの順序番号を格納することが提案されています。また、このためには特別なアノテーション@OrderColumnが必要です。

その解決策は次のようになります。

@Entity
@Table(name=”user”)
class User {
   @Column(name=”id”)
   public Integer id;

   @OneToMany(cascade = CascadeType.ALL)
   @LazyCollection(LazyCollectionOption.EXTRA)
   @OrderColumn(name = "order_id")
   public List<Comment> comments;
}

LazyCollectionOption.EXTRA の主な利点

LazyCollectionOption.EXTRA の最大の利点は、 @ManyToManyアノテーションを使用して指定した場合にわかります。従業員とタスクがあり、1 人のユーザーに多くのタスクを割り当てることができる古いケースを考えてみましょう。

Java クラスは次のようになります。

従業員クラス:

@Entity
@Table(name=”employee”)
class Employee {
   @Column(name=”id”)
   public Integer id;

   @ManyToMany(cascade = CascadeType.ALL)
   @JoinTable(name="employee_task",
       	joinColumns=  @JoinColumn(name="employee_id", referencedColumnName="id"),
       	inverseJoinColumns= @JoinColumn(name="task_id", referencedColumnName="id") )
   @LazyCollection(LazyCollectionOption.EXTRA)
   private Set<EmployeeTask> tasks = new HashSet<EmployeeTask>();

}

そしてEmployeeTask クラス:

@Entity
@Table(name=”task”)
class EmployeeTask {
   @Column(name=”id”)
   public Integer id;

   @ManyToMany(cascade = CascadeType.ALL)
   @JoinTable(name="employee_task",
       	joinColumns=  @JoinColumn(name="task_id", referencedColumnName="id"),
       	inverseJoinColumns= @JoinColumn(name=" employee_id", referencedColumnName="id") )
   @LazyCollection(LazyCollectionOption.EXTRA)
   private Set<Employee> employees = new HashSet<Employee>();

}

そして、director にタスクを追加するには、次のコードのようなものを記述する必要があります。

Employee director = session.find(Employee.class, 4);
EmployeeTask task = session.find(EmployeeTask.class, 101);
task.employees.add(director);

session.update(task);
session.flush();

したがって、Task クラスのemployees フィールドに LazyCollectionOption.EXTRA アノテーションがある場合、(Task クラスの) 従業員コレクションと (Employee クラスの) タスク コレクションはデータベースからまったくロードされませ

このコードが実行されると、employee_task サービス テーブルにレコードが 1 つだけ挿入されます。覚えているとおり、このテーブルは次のようになります。

従業員_タスクテーブル:
従業員ID タスクID
1 1
2 2
5 3
5 4
5 5
4 7
6 8
4 101

追加された行は緑色で強調表示されます。この行を追加する場合、データベースからコレクションをロードする必要はありません。それがなくても Hibernate が機能します。これはまさに、LazyCollectionOption.EXTRA がデータベースの処理を大幅に高速化する場合に当てはまります。

N+1問題

しかし、もちろん、このモードには欠点もあります。たとえば、 LazyCollectionOption.EXTRA アノテーションはN+1 問題を生成します。

ユーザーのコメントをすべて確認する場合は、次のようにします。

User user = session.load(User.class, 1);
List<Comment> comments = user.getComments();
for (Comment comment : comments) {
    System.out.println(comment);
}

その後、Hibernate は、Comment オブジェクトごとに個別のリクエストで実行されます。さらに、すべてのコメントの数を取得するための追加のクエリも 1 つあります。これにより、コードの速度が大幅に低下する可能性があります。

ユーザーに 1000 件のコメントがある場合、Hibernate はこのコードを実行するためにデータベースに対して 1001 件のクエリを実行しますが、1 件で済む場合もあります。このクラスのすべてのオブジェクトが必要になることが事前にわかっていた場合。