[스프링부트] 익명 블로그 V6 - Join으로 연관 관계인 객체 1번에 불러오기

익명 블로그 V6 - 2
이나겸's avatar
Dec 02, 2024
[스프링부트] 익명 블로그 V6 - Join으로 연관 관계인 객체 1번에 불러오기
💡

Join Fetch

JPQL(Java Persistence Query Language)에서 고유하게 사용하는 쿼리 구문
연관된 엔티티를 한 번의 쿼리로 함께 가져올 때(로드할 때) 사용
💡

JPQL(Java Persistence Query Language)

객체 지향 쿼리 언어
데이터베이스 테이블 대신 엔티티 객체를 대상으로 쿼리 작성 할 수 있게 해줌

Join Fetch 특징

  • 일반적인 join과 달리 지연 로딩(Lazy Loading)을 방지하여 연관된 엔티티를 즉시 로딩(Eager Loading)하게 함
  • 기본적으로 Inner Join
  • LEFT JOIN FETCH로 Left Outer Join은 가능
    • JPQL은 RIGHT JOIN과 FULL JOIN을 지원하지 않음
  • N + 1 쿼리 문제 방지하는데 유용하고 성능 최적화에 도움
    • 연관된 엔티티를 한 번의 쿼리로 모두 조회하여 데이터베이스 쿼리 횟수 줄임

주의점

  • 메모리 사용량이 많아질 수 있어서 모든 경우에 적합하지는 않음
    • 쿼리의 수를 줄이는 데 도움이 되지만, 연관된 엔티티가 많을 경우 한 번에 많은 데이터가 로드 될 수 있기 때문
  • 복잡한 쿼리
    • 여러 엔티티를 Join Fetch로 함께 조회할 때 조인되는 테이블의 데이터가 많으면 중복된 데이터 반환될 수 있음
    • 이런 경우에 결과를 적절히 처리하는 방법을 고려해야 함
 

Board 클래스와 User 클래스의 연관 관계

[Board 클래스 - 엔티티]

  • Board와 User 객체는 n:1 관계
    • 참조하고있는 User 객체에 @ManyToOne(fetch = FetchType.LAZY) 설정
@NoArgsConstructor(access = AccessLevel.PROTECTED) // 외부에서 빈 생성자 생성하지 못하게 제한(protected) @Getter @Table(name = "board_tb") // 테이블 명 @Entity // Board 객체를 테이블로 생성하며, 데이터베이스 테이블과 매핑하여 데이터베이스 작업(insert, update, delete 등)을 수행 public class Board { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) // AutoIncrement private Integer id; private String title; private String content; // 연관 관계 설정해놓은 User 객체(user_tb 테이블) 자체를 들고오는 것이 아니라 // user_tb 테이블의 PK (user_id(필드로는 userId))를 들고옴 // 연관 관계 설정 (User 쪽이 1이니까 ManyToOne) @ManyToOne(fetch = FetchType.LAZY) private User user; // update(글 수정)를 setter로 만들어서 활용하기로 함 // setter는 명확한 의도가 있는것만 해놓는게 좋기 때문에 public void update(String title, String content) { this.title = title; this.content = content; } }
 

[BoardRepository 클래스]

  • Board와 User 객체는 n:1 관계
    • id를 통해 Board 객체 1개 찾는 메서드 생성
    • JPQL 쿼리를 사용한 Join
      • Join 결과가 하나의 테이블이 됨
    • fetch가 빠지면 user 테이블이 null이라서 콘솔 출력 안됨
@RequiredArgsConstructor @Repository public class BoardRepository { private final EntityManager em; // id를 통해 Board 객체 1개 찾는 메서드 public Optional<Board> findByIdJoinUser(int id) { // 조인 결과가 하나의 테이블 / JPQL쿼리 사용 // fetch가 빠지면 user테이블이 null이라 콘솔 안나옴 String sql = """ select b from Board b join fetch b.user where b.id = :id """; Query q = em.createQuery(sql, Board.class); q.setParameter("id", id); try { // 해당 try문은 이상한 구조로 실제 적용X. 참고만 하기 Board board = (Board) q.getSingleResult(); return Optional.ofNullable(board); } catch (RuntimeException e) { return Optional.ofNullable(null); } } }
 

연관 관계(ORM)에 있는 객체(Board, User)를 1번에 불러올 경우의 장점

성능 향상

  • 지연 로딩 방지
    • fetch = FetchType.LAZY(지연 로딩)로 설정된 관계에서는 User 객체를 사용할 때 추가 쿼리 실행됨
    • join fetch 사용하면 Board 객체 로드할 때 User 객체도 함께 로드돼서 추가 쿼리 방지 할 수 있음
  • 쿼리 최적화
    • 한 번의 쿼리로 Board와 User 데이터를 모두 가져오기 때문에 데이터베이스와의 통신 횟수 줄일 수 있음
    • 데이터베이스 부하를 줄이고 네트워크 트래픽 감소시켜서 성능 최적화

코드의 간결성

  • 명확한 데이터 접근
    • Board와 관련된 User 정보를 별도의 쿼리 없이 즉시 사용할 수 있어서 코드가 간결해짐
  • 복잡성 감소
    • 여러 번의 쿼리를 수행할 필요가 없어서 코드의 복잡성이 줄어듦

일관된 데이터

  • 데이터 일관성 유지
    • join fetch 사용해서 연관된 엔티티를 한 번에 로드하면 동일한 트랜잭션 내에서 일관된 데이터 사용 가능
    • 데이터베이스 상태의 일관성을 유지하는 데 도움됨
Share article

Nakyeom's Study