使い方によってパフォーマンスが大きく変わる。こういう使い方が駄目というわけではなく、場合によっては別の方法を試した方が良いという例。
1.問題点
「[YOMOU CRAWLER] 第2回 クラス図の作成」にある通りNovelには複数のHistoryがあって、更新のある度に追加保存している。
// 1.Novelのエンティティ
public class Novel extends BaseObject implements Serializable {
/** 小説の更新履歴セット */
@OneToMany(fetch = FetchType.LAZY, mappedBy = "novel", cascade = CascadeType.ALL, orphanRemoval = true)
private Set<NovelHistory> novelHistories = new HashSet<>();
public void addNovelHistory(NovelHistory novelHistory) {
novelHistories.add(novelHistory);
novelHistory.setNovel(this);
}
// 2.Novelの更新履歴のエンティティ
public class NovelHistory extends BaseObject implements Serializable {
/** 小説 */
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "novel_id")
private Novel novel;
// 3.使用箇所
// Novelオブジェクトは永続化済み
if (novelHistory != null) {
// 小説の更新履歴が作成された場合
novel.addNovelHistory(novelHistory);
}
このように永続化された状態のオブジェクトにAddするだけで自動的にInsert文が発行されるため、Javaのビジネスロジック開発に集中することが出来る。通常はこれで問題ないのだが、NovelHistoryに既に大量のデータが保存されているとパフォーマンスが問題になる。
FetchType.LAZYを指定しているため、novelHistories.add(novelHistory)を実行するときに関連するHistoryがデータベースからSelectされ、novelHistories変数に格納される。上記の例ではInsert前に以下のようなSQLが実行されている。
select 省略 from novel_history where novel_id = ?;
つまり、1件追加したいだけなのに関連する全てのHistoryをSelectしてしまっている。何千件もHistoryがあればそれだけでパフォーマンスが悪化する。
2.回避策
今回は以下の様に修正してこの問題を回避した。
// 3’.使用箇所
if (novelHistory != null) {
// 小説の更新履歴が作成された場合
novelHistory.setNovel(novel);
}
novelHistoryは永続化されていないため、適切な箇所でsaveする必要があるが、こうすれば単純にInsert文のみ発行されるようになる。