JPA
[JPA] batchsize
steadyMan
2024. 5. 1. 01:27
JPA을 사용할때 n+1문제를 극복하고자 지연로딩(FetchType.LAZY)를 사용하는데 이 역시 프록시를 초기화 실제 엔티티로 초기화 해야 하는 상황이라면 n+1문제가 발생하는건 마찬가지이다. n+1문제 해결과 조회 성능 향상의 목적으로 사용하는 것이 BatchSize이다
일반적으로 어노테이션
, 외부설정파일
을 통해 설정하며 연관관계의 초기화 대상 엔티티의 개수만큼 조회 쿼리가 발생하는 상황에 설정한 일정량의 데이터를 한 번에 로딩한다.
@Entity @Table("member")
@Getter @Setter
@NoArgsConstructor(access = AccessLevel.PROTECTED)
public class Member {
@Id
@Column(name = "member_id")
@GeneratedValue
private Long id;
private String username;
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "team_id")
private Team team;
.......
}
@Entity @Table("team")
@Getter @Setter
@NoArgsConstructor(access = AccessLevel.PROTECTED)
public class Team {
@Id
@GeneratedValue
@Column(name = "team_id")
private Long id;
private String name;
@OneToMany(mappedBy = "team", fetch = FetchType.LAZY)
private List<Member> members = new ArrayList<>();
.......
}
초기화 대상의 관계(@ManyToOne, @OneToMany...)와 무관하게 엔티티를 초기화하는 시점에 발생한다.
@Test
public void batchSizeTest() {
//== member 초기화 ==//
Member member = new Member("member");
Member member2 = new Member("member2");
Member member3 = new Member("member3");
Member member4 = new Member("member4");
memberRepository.save(member);
memberRepository.save(member2);
memberRepository.save(member3);
memberRepository.save(member4);
//== team 초기화 ==//
Team teamA = new Team("teamA");
teamRepository.save(teamA);
Team teamB = new Team("teamB");
teamRepository.save(teamB);
Team teamC = new Team("teamC");
teamRepository.save(teamC);
Team teamD = new Team("teamD");
teamRepository.save(teamD);
member.setTeam(teamA);
member2.setTeam(teamB);
member3.setTeam(teamC);
member4.setTeam(teamD);
//== persistence context 초기화 ==//
em.flush();
em.clear();
}
이 상황에서 team테이블을 조회해서 member에 접근하면 프록시 상태의 객체를 초기화를 위해 team의 개수만큼
member테이블에 조회 쿼리가 발생한다. 하지만 batchsize를 설정하면 설정한 사이즈만큼 in절 조건으로 다수의 데이터를 한 번에 로딩한다.
@Entity @Table("team")
public class Team {
...
@OneToMany(mappedBy = "team", fetch = FetchType.LAZY)
@BatchSize(size = 2)
private List<Member> members = new ArrayList<>();
...
}
@Test
public void batchSizeTest() {
...
List<Team> all = teamRepository.findAll();
all.forEach(t -> {
System.out.println("team.name = " + t.getName());
System.out.println("member.name = " + t.getMembers().get(0).getUsername());
});
}
실행 쿼리
select
t1_0.team_id,
t1_0.name
from team t1_0
---
select m1_0.team_id,m1_0.member_id,m1_0.age,m1_0.created_by,m1_0.last_modified_by,m1_0.username
from member m1_0
where m1_0.team_id in (1,2);
---
select m1_0.team_id,m1_0.member_id,m1_0.age,m1_0.created_by,m1_0.last_modified_by,m1_0.username
from member m1_0 where m1_0.team_id in (3,4);
team.findAll() 쿼리 한번에 총 4번의 member 테이블 조회 쿼리가 2번으로 줄었다.
물론 size를 더 높게 설정하면 한번만 실행될것이다.
어노테이션 설정 시 xToOne관계에서의 유의사항
xToOne관계인 필드에 어노테이션으로 bachsize 설정 시 정상적으로 작동하지 않는다. 하이버네이트 문서를 보니
어노테이션 설정은 entity class, xToMany의 collection fields에서만 지원한다고 한다.
이럴경우 해당 엔티티에 직접 설정, 외부설정파일에서의 전역적인 설정을 해줘야 정상적으로 작동한다.
@Entity @Table("team")
@BatchSize(size = 100)
public class Team {
...
}
# application.yml 파일 기준
spring:
jpa:
properties:
hibernate:
default_batch_fetch_size: 100