우아한테크코스/post우테코

[내부코드 파헤치기] findAll은 JPQL인가?

nauni 2021. 12. 30. 09:42

왜 findAll이 JPQL인가라는 의문점을 가지게 되었는지에 대해 설명하며 파헤쳐본 내용들을 정리해보려고 한다.

N+1은 왜 발생하는가?

JPQL을 실행하면 JPA는 이것을 분석해서 SQL을 생성한다. 이때는 즉시 로딩과 지연 로딩에 대해서 전혀 신경 쓰지 않고 JPQL만 사용해서 SQL을 생성한다. (...) SQL의 결과 수만큼 추가로 SQL을 실행하는 것을 N+1문제라고 한다.

- 자바 ORM 표준 JPA 프로그래밍(책)

 

N+1문제는 JPQL을 사용할 때 나타날 수 있는 문제라고 한다. JPQL은 무엇인가? 처음에 나는 개발자가 직접 repository에 쿼리 메소드로 작성하는 메소드만 JPQL이라고 생각했다. 하지만 미션을 하다보니 findAll 메소드에서도 N+1이 발생하는 것이었다. 뭔가 이상하지 않은가? findAll은 기본적으로 제공되고 있는데 이것도 JPQL이라고 해야하는 건가?

JPQL은 무엇인가?

SQL이 데이터베이스 테이블을 대상으로 하는 데이터 중심의 쿼리라면 JPQL은 엔티티 객체를 대상으로 하는 객체지향 쿼리다. JPQL(Java Persistence Query Laugnage)은 엔티티 객체를 조회하는 객체지향 쿼리다. JPQL은 SQL을 추상화해서 특정 데이터베이스에 의존하지 않는다. 

- 자바 ORM 표준 JPA 프로그래밍(책)

 

책에서 해당 파트를 읽다보면 Criteria 쿼리를 소개한다. Criteria는 JPQL을 생성하는 빌더 클래스라고 한다. 실제로 내부 코드를 확인해보면 findById와 findAll이 내부적으로 사용하는 메소드가 다른 것을 확인해 볼 수 있다.

 

findAll 메소드 안을 들어가보자. 정의돈 것이 없다면 기존에 정의된 findAll을 사용하게 되어있고, findAll은 getQuery 메소드를 사용한다. 이것은 em(엔티티 메니저의 createQuery 메소드를 사용하는 것을 볼 수 있다.) 책에서 설명한 대로 JPQL은 Criteria 빌더를 사용하여 createQuery 를 사용하여 쿼리를 생성해 주는 것을 알 수 있다.

// CrudRepositoryInvoker.java

	@Override
	public Iterable<Object> invokeFindAll(Sort sort) {
		return customFindAllMethod ? super.invokeFindAll(sort) : repository.findAll();
	}
    
// SimpleJpaRepository.java

	@Override
	public List<T> findAll() {
		return getQuery(null, Sort.unsorted()).getResultList(); 
		// 여기의 getQuery 내부 메소드를 몇번 타고 가면 아래의 메소드를 사용하는 것을 알 수 있다.
	}

	protected <S extends T> TypedQuery<S> getQuery(@Nullable Specification<S> spec, Class<S> domainClass, Sort sort) {

		CriteriaBuilder builder = em.getCriteriaBuilder(); // em에서 Criteria 빌더를 사용한다.
		CriteriaQuery<S> query = builder.createQuery(domainClass); // createQuery 메소드를 사용한다.

		Root<S> root = applySpecificationToCriteria(spec, domainClass, query);
		query.select(root);

		if (sort.isSorted()) {
			query.orderBy(toOrders(sort, root, builder));
		}

		return applyRepositoryMethodMetadata(em.createQuery(query));
	}

그렇다면 findById는 JPQL이라고 보통 하지 않는데 그것은 어떻게 다른 것일까?

// SimpleJpaRepository.java

	@Override
	public Optional<T> findById(ID id) {

		Assert.notNull(id, ID_MUST_NOT_BE_NULL);

		Class<T> domainType = getDomainClass();

		if (metadata == null) {
			return Optional.ofNullable(em.find(domainType, id)); // em의 find 메소드를 사용한다.
		}

		LockModeType type = metadata.getLockModeType();

		Map<String, Object> hints = getQueryHints().withFetchGraphs(em).asMap();

		return Optional.ofNullable(type == null ? em.find(domainType, id, hints) : em.find(domainType, id, type, hints));
		// em의 find 메소드를 사용한다.
    }

위의 코드를 보면 findById는 em(엔티티매니저)의 find 메소드르 사용하는 것을 알 수 있다. 

 

JpaRepository, CrudRepository

spring-data-jpa 패키지를 구조를 살펴보면 data.jpa.repository 패키지에 있는 인터페이스가 data.repository에 있는 인터페이스를 구현하는 것을 알 수 있다. 내부 패키지를 살펴보면 data.repository는 spring-data-commons 패키지에서 관리되고 있다. data.jpa.repository와 spring-data-jpa는 다른 패키지에서 의존성을 가지고 관리되고 있다.

// package org.springframework.data.jpa.repository;
@NoRepositoryBean
public interface JpaRepository<T, ID> extends PagingAndSortingRepository<T, ID>, QueryByExampleExecutor<T> {

	/*
	 * (non-Javadoc)
	 * @see org.springframework.data.repository.CrudRepository#findAll()
	 */
	@Override
	List<T> findAll();
	// ...
}

// package org.springframework.data.repository;
@NoRepositoryBean
public interface PagingAndSortingRepository<T, ID> extends CrudRepository<T, ID> {
	// ...
}

spring-data-commons
spring-data-jpa

정리

JPA에 대해 공부하다 보니 JPQL의 기준과 JPA가 어떤 식으로 관리되는지 더 혼란스럽기만 했다. findAll이라는 단편적인 메소드였지만, 내부 코드도 보고 책에서 궁금했던 부분을 찾아가며 꽤 지식이 많이 정리되어 갔다. 단순히 쿼리 메소드로 작성하는 것만이 JPQL은 아니며 내부적으로 JPQL로 동작하고 있을 수 있다. JPA는 인터페이스이고 보통 그것을 구현한 구현체인 hibernate를 사용한다. 이에 더해서 SpringBoot를 사용한다면 spring-data-jpa를 사용하여 내부적으로 더 사용하기 편리하게 설정된 것들이 추가된다. 

 

사용하기 편리한 것들을 내부적인 그 구현관계나 의존관계가 보이지 않기 때문에 생각하지 못했던 내용이 내가 생각했던 사실과 다르게 다가올 수 있다고 생각한다. 이럴 때, 내부를 파헤치고 그 구현이나 관계가 어떻게 되어 있는지 확인할 수 있고, 그것을 확인하고 사용해야한다고 생각한다. 사실 이 내부코드를 파헤칠 때, 책을 볼 수 있는 상황이 아니라서 크루들과 이야기하면서 어렵게 추정했던 내용이었다. 하지만 지금 정리하는 시점에서 책을 보니 해당 내용이 다 나와있어서 진작봤으면 좋았을걸 하는 생각이 든다.

 

이 내용을 정리하며 JPA를 꽤나 날림으로 공부했구나 하는 생각이 들기도 하고... 내부를 이해하려는 그만큼의 고민의 시간이 있었기에 책의 내용이 진짜로 이해되는 것이라는 생각이 들기도 한다. 무튼 책에서 내용을 확인하니 속이 시원하다~!

참고

- 스프링 내부코드

- 자바 ORM 표준 JPA 프로그래밍