Spring/JPA

따로 따로 조회했을 때 연관관계가 있는 객체를 자동으로 로드해주나?

whyWhale 2023. 3. 21.

모험해볼 이야기


  • JPA는 영속성 컨텍스트를 이용하여 쓰기 지연을 제공하고 영속성 컨텍스트에 적재된 객체들은 DB에 쿼리를 날리지 않는 것으로 알고있다.
  • 서로 연관관계가 있는 객체들을 한번에 조회하는 것이 아니라 따로 조회한다 했을 때, 서로 연관 되있는 객체는 영속성 컨텍스트에 올라가 있으니 자동으로 연관관계 객체를 넣어주는지 궁금했다.

1:N관계로 이루어져 있는 Team과 User 객체가 있다. id가 0-4번 팀이 존재하고 각 User가 5명씩 속해있다

1. 따로 조회했지만 객체가 자동으로 연관관계를 맺어 주는지 실험해 봤다.

public List<Boolean> test() {
		List<TeamEntity> teams = teamRepository.findAll();
		List<UserEntity> byTeamIn = userRepository.findByTeamIn(teams);

		return teams.stream()
			.map(teamEntity -> entityManagerFactory.getPersistenceUnitUtil().isLoaded(teamEntity.getUsers()))
			.toList();
	}

===================== test code =========================

@Test
	@DisplayName("따로 조회했는데 Team 객체에 사용자가 자동으로 로드되는지 테스트,"
		+ " 결과: 사용자는 따로 조회한 팀이 로드되는 반면, 팀은 자동으로 사용자를 로드하지 않는다.")
	void test1() {
		//given
		List<TeamEntity> teams = IntStream.range(0, 5)
			.mapToObj(value -> TeamEntity.builder().name(String.valueOf(value)).build())
			.toList();
		teamRepository.saveAll(teams);

		List<UserEntity> users = IntStream.range(0, 50)
			.mapToObj(value -> UserEntity.builder()
				.name(String.valueOf(value))
				.team(teams.get(value % 5)).build())
			.toList();
		userRepository.saveAll(users);
		//when
		List<Boolean> isLoads = testService.test();
		//then
		isLoads.forEach(isLoad -> assertThat(isLoad).isFalse());
	}

 

2. 그러면 team에 있는 user의 메서드나 필드를 사용하면 영속성 컨텍스트안에 있으니 쿼리를 안날릴 것으로 예상했다.

  
public List<Boolean> test2() {
		List<TeamEntity> teams = teamRepository.findAll();
		List<UserEntity> byTeamIn = userRepository.findByTeamIn(teams);

		return teams.stream()
			.map(teamEntity -> {
				teamEntity.getUsers();
				return entityManagerFactory.getPersistenceUnitUtil().isLoaded(teamEntity.getUsers());
			})
			.toList();
	}

===================== test code =========================

  @Test
	@DisplayName("그러면 영속성 컨텍스트에 해당 사용자가 있으니 쿼리는 날리는지 테스트하기 ")
	void tes2() {
		//given
		List<TeamEntity> teams = IntStream.range(0, 5)
			.mapToObj(value -> TeamEntity.builder().name(String.valueOf(value)).build())
			.toList();
		teamRepository.saveAll(teams);
		List<UserEntity> users = IntStream.range(0, 50)
			.mapToObj(value -> UserEntity.builder()
				.name(String.valueOf(value))
				.team(teams.get(value % 5)).build())
			.toList();
		userRepository.saveAll(users);
		entityManager.clear();
		//when
		List<Boolean> isLoads = testService.test2();
		//then
		isLoads.forEach(isLoad -> assertThat(isLoad).isTrue());
	}

하지만 로드 되지 않는 user에 대한 쿼리가 발생했다(N+1)

결국을 따로 조회하여 합칠 때는 애플리케이션 내에서 조립을 해줘야 해야 하는 것이다.

 

Why

  • 해당 테스트를 시험해본 이유는 프로젝트에서 1:N관계에 있는 객체들을 양방향으로 매핑하지 않고 서비스 내에서 조립하여 사용하고 있었다. JPA는 영속성 컨텍스트를 사용하니 질의쿼리도 안날려야 하는거 아닌가란 생각이 들었다.
  • 하지만 위 생각은 예측일 뿐 직접 돌려봐야 정확히 알 수 있기 때문에 시도해봤다.
  • 단순히 애플리케이션 로그에 찍힌 것이 실제 DB로 질의 되었는지도 궁금해서 시도해봤다.

결론

JPA는 만능의 라이브러리가 아니다.

따로 조회하면 따로 애플리케이션내에서 조립해야 한다. 거기까지 JPA가 자동으로 넣어주지 않는다.

영속성 컨텍스트에 해당 객체가 있어도 결국은 해당 객체의 연관관계가 있는 객체는 로드되지 않았으므로 Flush하여 쿼리를 날린다.(로그와 실제 DB안에 로그로 확인한 결과 명백함.)

댓글