연관 관계가 설정된 엩티티를 조회할 경우 조회된 데이터 갯수(n) 만큼 연관관계의 조회 쿼리가 추가로 발생하여 데이터를 읽어오는 현상
@Entity
@Table(name = "user")
@ApiModel("회원엔티티")
public class User extends BaseTimeEntity {
/**
* 회원번호
*/
@Id
@Column(name = "user_no", nullable = false)
@GeneratedValue(strategy = GenerationType.IDENTITY)
@ApiModelProperty(value = "유저번호", required = true)
private Long userNo;
/**
* 이메일
*/
@Column(name = "email", length = 50, columnDefinition = "VARCHAR(50)")
@ApiModelProperty(value = "이메일")
private String email;
/**
* 비밀번호
*/
@Column(name = "password", length = 500, columnDefinition = "VARCHAR(500)")
@ApiModelProperty(value = "비밀번호")
private String password;
/**
* 생년월일
*/
@Column(name = "birth", length = 10, columnDefinition = "VARCHAR(20)")
private String birth;
/**
* 닉네임
*/
@Column(name = "nickname", length = 50, columnDefinition = "NVARCHAR(50)")
private String nickname;
/**
* 이용약관동의
*/
@Column(name = "agree_tos", columnDefinition = "varchar(5) default '0'")
private String agreeTos;
/**
* 개인정보수집 및 이용동의
*/
@Column(name = "agree_picu", columnDefinition = "varchar(5) default '0'")
private String agreePicu;
/**
* 이벤트, 프로모션 메일, SMS수신
*/
@Column(name = "agree_promotion", columnDefinition = "varchar(5) default '0'")
private String agreePromotion;
/**
* 성별
*/
@Column(name = "gender", columnDefinition = "varchar(5) default 'm'")
private String gender;
/**
* 역할구분(구매자,판매자,관리자)
*/
@Column(name = "role", columnDefinition = "varchar(20) default 'b'")
@Enumerated(EnumType.STRING)
private Role role;
/**
* 회원상태
*/
@Column(name = "del_yn", columnDefinition = "NVARCHAR(5) DEFAULT 'n'")
private String delYn;
/**
* 휴대폰 번호
*/
@Column(name = "phone_nm", nullable = false, columnDefinition = "VARCHAR(20)")
private String phoneNm;
@ApiModelProperty(value = "장바구니")
@OneToOne(fetch = LAZY, mappedBy = "user")
@ToString.Exclude // 연관관계의 주인이 아닌 객체를 mappedBy
private Cart cart;
@ApiModelProperty(value = "배송지")
@OneToMany(fetch = LAZY, mappedBy = "user")
@ToString.Exclude
private List<Delivery> deliveryList = new ArrayList<>();
@ApiModelProperty(value = "주문목록")
@OneToMany(fetch = LAZY, mappedBy = "user")
@ToString.Exclude
private List<Order> orderList = new ArrayList<>();
@OneToMany(fetch = LAZY, mappedBy = "user")
@ToString.Exclude
private List<Review> reviewList = new ArrayList<>();
@OneToOne(fetch = LAZY, mappedBy = "user")
@ToString.Exclude
private Jjim jjim;
}
Fetch 모드를 EAGER(즉시 로딩)으로 한 경우
JPQL
구문이 생성Fetch 모드를 LAZY(지연 로딩)으로 한 경우
JPQL
구문이 생성지연 로딩으로 설정했다고, N+1 문제가 해결될 것 같지만, 결국 영속성 컨텍스트에 존재하지 않는다면 N+1 문제가 발생한다. 그 시점만 달라질 뿐이다.
Fetch Join
JPQL을 사용해서 데이터를 가져올 때 연관된 데이터까지 같이 가져오도록 하는 방안이다.
이는 오라클에서 join 시 on절을 통해 정확하게 어떤 데이터를 연관해서 가져올지 설정하는 것과 비슷한 느낌이다.
public interface TeamRepository extends JpaRepository<Team, Long> {
@Query("select t from Team t join fetch t.users")
List<Team> findAllFetchJoin();
}
Hibernate: select team0_.id as id1_0_0_, user2_.id as id1_2_1_, team0_.name as name2_0_0_, user2_.first_name as first_na2_2_1_, user2_.last_name as last_nam3_2_1_, user2_.team_id as team_id4_2_1_, users1_.team_id as team_id1_1_0__, users1_.users_id as users_id2_1_0__ from team team0_ inner join team_users users1_ on team0_.id=users1_.team_id inner join user user2_ on users1_.users_id=user2_.id
============== N+1 시점 확인용 ===================
Batch Size
내가 생각하기에 해당 방안이 제일 효율적이라고 생각한다.
spring:
jpa:
properties:
hibernate:
default_batch_fetch_size: 1000
배치페치사이즈를 기본 1000정도로 설정한다.
Hibernate: select team0_.id as id1_0_, team0_.name as name2_0_ from team team0_
Hibernate: select users0_.team_id as team_id1_1_1_, users0_.users_id as users_id2_1_1_, user1_.id as id1_2_0_, user1_.first_name as first_na2_2_0_, user1_.last_name as last_nam3_2_0_, user1_.team_id as team_id4_2_0_ from team_users users0_ inner join user user1_ on users0_.users_id=user1_.id where users0_.team_id in (?, ?, ?, ?)
참조하는 객체가 사용될 때 하나씩 조회문을 수행하지 않고
한 번에 수행하기 위해서 batch_fetch_size 설정을 지정한다