【Spring Data JPA】N+1問題
はじめに
お世話にになります、hosochinです
Spring Data JPAでよくあるN+1問題の解決方法についてメモします
JPAのN+1問題
N+1問題とは、意図しないSQL文が大量に(取得するレコードの数だけ)発行されてしまう問題です
この問題はO/R MapperによるSQL文の自動生成が原因で、たとえば以下のような現象になります
- 対象テーブルからN個のレコードを取得するSELECT文を1回発行する
- 1で取得したN個のレコードの関連データを取得するSELECT文を1回ずつ発行する(N回発行される)
N+1問題を発生させてみる
DB
以下のようなテーブルにレコードが入っているとします
- userテーブル
user_id (PK) | user_name |
---|---|
1 | Taro |
2 | Jiro |
3 | Saburo |
- teamテーブル
- teamテーブル.user_id は userテーブル.user_id と多対1の関係
team_id (PK) | team_name | user_id (PK) |
---|---|---|
1 | Aチーム | 1 |
1 | Aチーム | 2 |
2 | Bチーム | 3 |
実装
Lombokいれてます
- userテーブルのEntity
@Entity
@Table(name="user")
@Data
public class UserEntity {
@Id
@Column(name = "user_id")
private String userId;
private String userName;
}
- teamテーブルのEntity
@Entity
@Table(name="team")
@IdClass(value=TeamKey.class)
@Data
public class JoinTeamEntity implements Serializable {
@Id
@Column(name = "user_id")
private String teamId;
private String teamName;
@Id
private String userId;
// userテーブルと結合するキー
@ManyToOne
@JoinColumn(name = "user_id", insertable = false, updatable = false)
private UserEntity user;
}
- teamテーブルの複合主キークラス
@Data
public class TeamKey implements Serializable {
@Id
private String teamId;
@Id
@Column(name = "user_id")
private String userId;
}
- repositoryクラス
public interface TeamRepository extends JpaRepository<JoinTeamEntity, TeamKey> {}
findAll()してみる
application.propertiesのspring.jpa.show-sqlをtrueにすると発行されたクエリがログ出力されるようになります
こいつをtrueにしてから動かします
- 実行されたクエリ(見やすいように少し整形してます)
select user_id, team_id, team_name from team;
select user_id, user_name from user where user_id='1';
select user_id, user_name from user where user_id='2';
select user_id, user_name from user where user_id='3';
最初にチームテーブルをselectして、そこで取得したuser_idの数だけuserテーブルにselectする…N+1問題が発生しています
解決方法
JPQLでJOIN FETCHを使って解決するという方法があります
クエリのJOIN のあとに FETCH をつけるだけです
- repositoryクラス
public interface TeamRepository extends JpaRepository<JoinTeamEntity, TeamKey> {
@Query("SELECT t FROM JoinTeamEntity t LEFT JOIN FETCH t.user")
List<JoinTeamEntity> findAll();
}
- 実行されるクエリ
select
team.user_id,
team.team_id,
team.team_name,
user.user_id,
user.user_name
from
team team
left outer join user user on team.user_id=user.user_id;
👍
ディスカッション
コメント一覧
まだ、コメントがありません