JPAのパフォーマンス改善

使い方によってパフォーマンスが大きく変わる。こういう使い方が駄目というわけではなく、場合によっては別の方法を試した方が良いという例。

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文のみ発行されるようになる。

コメントを残す

メールアドレスが公開されることはありません。 が付いている欄は必須項目です