JPA, N+1을 해결하기 위한 Batch와 toString의 사용

infoqoch·2021년 3월 16일
0

스프링부트

목록 보기
6/10

JPA의 n+1 문제를 해소하는 다양한 방식이 있다. 가장 기본적으로는 페치 전략을 lazy로 바꾼다. 그리고 join을 할 때 fetch를 하거나 distinct를 하여 중복을 제거한다. 이런 방식으로 도전을 했는데 실패한 문제가 있었다. 해당 코드는 아래와 같다.

entity Member.java

@ToString
public class Member {
    @Id
    @GeneratedValue
    @Column(name = "member_id")
    private Long id;
    
    @OneToMany(mappedBy = "member", cascade = CascadeType.ALL)
    private List<book> bookList = new ArrayList<>(); .........(핵심문제)

	...후략...
}
MemberRepositoryTest.java

	@Autowired
	private final MemberRepository repository;
    
    @Test
    @Transactional(readOnly = true)
    public void test(){
    	List<Member> members = repository.findAll();......(1)
        members.stream().forEach(member->{
        	System.out.println(member.toString());........(2)
        });
    }

(1) 에서는 쿼리 한 번이 생성된다.
(2) 에서는 members.size()만큼 List< book> bookList를 출력하는 쿼리가 발생한다. 나는 (2)의 문제가 join 과 유사한 문제라고 생각했다. 그러므로 fetch 전략을 변경하거나 distinct를 변경하고자 했다. 그러나 해소되지 않았다. 여러 시도 끝에 해결했다. 그 해결책은 아래와 같다.

1. ToString을 제한적으로 사용.

MemberRepositoryTest.java

    public void test(){
    	List<Member> members = repository.findAll();
        members.stream().forEach(member->{
        	System.out.println(member.getName());........(2) 수정
        });
    }
  • 지금의 문제는 사실 쿼리 자체의 문제가 아니었다. 맴버 객체 각 각이 가지고 있는 bookList를 호출하는 문제였다. 페치 전략은 lazy이기 때문에 해당 bookList만 호출하지 않으면 쿼리는 발생하지 않았다.
  • (2)와 같이 필요로한 부분만 추출한다.
  • 사실 (2)와 같은 전략이 옳다고 생각한다. service에서 controller로 데이타를 넘길 때 엔티티에서 필요한 정보만 DTO로 만드는 것이 여러 모로 더 좋다고 보기 때문이다.

2. batch의 활용

  • batch란 한 번에 작업을 처리하는 것을 의미한다.
  • 현재의 문제를 batch를 통해 해결할 수 있었다.
Member.java
  
@BatchSize(size = 50)..........(3)
@OneToMany(mappedBy = "member", cascade = CascadeType.ALL)
private List<Borrow> borrowList = new ArrayList<>();
for (Member member : result) {
	System.out.println(member.toString());.......(2) 수정
}
  • Member를 탐색할 때마다 쿼리를 반복적으로 호출하지 않는다. 쿼리를 묶어서 나중에 단 한 번에 처리한다.(in을 사용한다)
  • 다대일 일대다 간 양방향 맵핑은 과연 언제나 유리할까? JPA를 사용하며 꼭 그렇지 않다는 생각을 많이 받았다. 기본적으로 10개 이상의 자료가 넘어가면 페이징 처리를 해야 하는데, 그럴 경우 양방향은 성능 낭비가 된다. 소규모의 데이타를 가지고, 또 그 데이타가 계속 활용되는 경우 양방향 매핑이 의미가 있다고 개인적으로 생각한다. 그렇게 다뤄야 할 데이타가 그렇게 많은 것 같진 않다. 그러므로 배치를 사용한 방법보단, 앞서의 방법을 통해 dto를 잘 활용하는 방식이 좋지 않을까 생각한다.
profile
JAVA web developer

0개의 댓글

관련 채용 정보