내가 JOIN 을 쓰지 않았던 이유 ( 과제 TIL )

KUN·2025년 5월 13일

목적

  1. 일정관리 API 를 제작한다.
  2. users 테이블과 schedules 테이블이 있다.
  3. users.userId 로 schedules.userId 외래키로 일정들을 가져와야한다.
  4. 그렇다면 JOIN 이 필요한데 나는 조인을 쓰지 않고 가져왔다.

나는 왜 그런 선택을 했을지에 대해서 정리해보도록 하겠다.
그리고, 내가 했던 방식과 JOIN 을 택한 방식 중 어떤 것이 더 효율적인가?
에 대해서 알아보도록 하겠다.


일정관리 다이어그램

1:N 관계를 가진 ERD 다이어그램


나는 어떤 코드를 짰는가?

해석하면 다음과 같다.

1. username 을 기준으로 userRepository 에서 Optional<User> 을 가져오겠다.
2. User 가 존재한다면 User 안에는 유저의 정보가 들어있을 것이다.
3. 그렇다면 User 에게서 userId 를 뽑아낸 이후 planItRepository 로 보내준다.
4. 그리고 Page 에 일정들을 담아서 반환...

왜 나는 이런방식을 채택 했는가?

이유

1. 내가 SQL을 완전히 제어하고 싶었다.
	- 복잡한 조건이 아니라면 JOIN 없이 단순한 쿼리 조합으로 충분하다고 판단했다.
2. users 테이블과 schedules 테이블 쿼리를 명확히 분리하고 싶었다.
	- 각 도메인(사용자, 일정)의 책임을 나눠 관리하는 것이 유지보수에 더 낫다고 느꼈다.
	- 사용자 존재 여부 확인과 일정 조회를 명확히 분리함으로써, 로직의 단계별 흐름이 드러나도록 설계했다.
3. JOIN은 복잡하고, 성능 차이가 크지 않다고 생각했다.
	- 데이터 양이 방대하지 않은 경우, 2번 쿼리가 성능에 치명적이지 않다.
	- 복잡한 JOIN 문을 작성하는 대신, 단순하고 명확한 흐름이 더 중요하다고 생각했다.

단점

1. 쿼리가 2번 실행되어 성능 손실이 발생할 수 있다.
	- users 테이블에서 사용자 ID를 조회한 뒤, 
      schedules 테이블에서 일정을 다시 조회해야 하므로 DB 입장에서는 불필요한 네트워크 왕복이 생긴다.
	- 사용자 수나 호출 빈도가 늘어날수록, 이 방식은 병목 가능성이 있다.
2. 조합 조건 검색에 유연하지 않다.
	- username과 date 같이 다중 조건으로 일정을 조회하려면 서비스 단에서 분기 로직이 복잡해진다.
	- JOIN 쿼리라면 SQL 레벨에서 한 번에 처리할 수 있지만, 
      이 방식은 조건이 늘어날수록 메서드가 쪼개지고 관리가 어려워진다.
3. 도메인 간 연관 관계를 끊어 사용하는 구조다.
	- 실제로 schedule은 user와 명확한 외래키 관계를 가지고 있지만, 
      코드 상에서는 단순한 ID 매핑으로만 취급된다.
	- 이는 나중에 사용자와 일정 간의 연관성을 기반으로 한 기능(예: 사용자 정보와 함께 일정 조회)
      을 구현할 때 불리하게 작용할 수 있다.
4. 에러 처리를 별도로 신경 써야 한다.
	- Optional<User>에서 isPresent() 또는 orElseThrow() 등 명시적인 처리가 필요하다.
	- JOIN을 사용하면 조회 결과가 없을 경우 단순히 0행 처리로 끝나지만, 
      지금 방식은 예외 발생 가능성이 높다.

더 나아가기 위해 어떤걸 해야할까?

  1. 쿼리 구조를 JOIN 으로 변경한다.
    • 하나의 쿼리로 모든 데이터를 조회 가능
    • 조건이 많아질수록 유연하고 효율적인 필터링 가능
    • 쿼리 수 감소로 인한 네트워크 비용 절감
    • INNER JOIN, LEFT JOIN 등으로 유저-일정 관계를 유연하게 다룰 수 있다.

이런식으로 작성하면 username 만 넘겨줘도 된다.


  1. 서비스 레이어 역할 분리한다. ( 번외 - JOIN 과 관계가 없지만.. )
    • 현재 구조에서는 PlanItServiceImpl 하나가 다음 두 가지 책임을 모두 수행한다.
    • username을 통해 User 정보를 조회
    • 해당 유저의 userId로 일정을 조회
    • 즉, 사용자 도메인 로직과 일정 도메인 로직이 한 클래스에 섞여 있다.

UserService 을 만들고 PlanItServiceImpl 에는 UserRepository 를 삭제했다.


느낀점

  • 처음에는 쿼리의 단순성과 흐름의 명확성을 우선시해서 JOIN을 사용하지 않았다.
    사용자 정보를 조회한 후, 해당 ID를 기반으로 일정을 가져오는 방식이 더 직관적이고 테스트하기 쉬울 것이라고 판단했다.

  • 하지만 이번에 JOIN 쿼리를 직접 적용해보면서 느낀 점은, 단순한 경우에는 지금 방식이 충분히 유효하지만,
    조건이 복잡해지거나 성능을 고려해야 하는 상황에서는 JOIN이 더 효과적인 선택이 될 수 있다는 것이다.

profile
배우노라, 실험하노라, 기록하노라

0개의 댓글