본문 바로가기
JPA

[JPA] batchsize

by steadyMan 2024. 5. 1.

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

'JPA' 카테고리의 다른 글

[JPA] 일반필드와 컬럼 매핑  (0) 2022.08.20
[JPA] 영속성 컨텍스트  (0) 2022.08.09

댓글