DB 쿼리 - Join과 페이징

구환준/모건·2023년 11월 2일

UMC-5th

목록 보기
4/10

쿼리를 어떻게 짤까

저번주에 공부한 데이터베이스의 구성을 잘해야 하는 이유 → 쿼리를 쉽게 쓰기 위해서

워크북에도 나와있듯, 테이블을 어떻게 구성하냐에 따라 쿼리의 길이와 복잡도가 달라집니다

JOIN

INNER JOIN(내부 조인)

두 테이블에서 조건에 맞는 레코드(튜플)를 결합합니다.
조건에 맞지 않는 레코드는 결과에서 제외됩니다.

❗두 테이블에 모두 지정한 열의 데이터가 있어야 합니다

보통 JOIN하면 INNER JOIN을 의미합니다

SELECT <열 목록>
FROM <첫 번째 테이블>
    INNER JOIN <두 번째 테이블>
    ON <조인 조건>
[WHERE 검색 조건]

#INNER JOIN을 JOIN이라고만 써도 INNER JOIN으로 인식합니다.

Member:

member_id(PK)name
1a
2b
3c

POST:

post_id(PK)titleauthor_id(FK)
1hi1
2hello1
3bye4

author_id = member_id를 이용해 INNER JOIN 하면

member_idnamepost_idtitleauthor_id
1a1hi1
1a2hello1

OUTER JOIN

SELECT <열 목록>
FROM <첫 번째 테이블(LEFT 테이블)>
    <LEFT | RIGHT | FULL> OUTER JOIN <두 번째 테이블(RIGHT 테이블)>
     ON <조인 조건>
[WHERE 검색 조건]

코드로 왼쪽,오른쪽 테이블의 구분 어려울 때
→ A <LEFT | RIGHT | FULL> OUTER JOIN B 라고 생각하면 됨

  • LEFT
    조인을 하되, 왼쪽 테이블의 모든 레코드가 출력되어야 함 (조건에 안맞는 레코드는 null과 연결)
member_id(PK)name
1a
2b
3c
post_id(PK)titleauthor_id(FK)
1hi1
2hello1
3bye4

author_id = member_id를 이용해 LEFT OUTER JOIN 하면

member_idnamepost_idtitleauthor_id
1a1hi1
1a2hello1
2bnullnullnull
3cnullnullnull
  • RIGHT
    조인을 하되, 오른쪽 테이블의 모든 레코드가 출력되어야 함 (조건에 안맞는 레코드는 null과 연결)
    author_id = member_id를 이용해 RIGHT OUTER JOIN 하면
member_idnamepost_idtitleauthor_id
1a1hi1
1a2hello1
nullnull3bye4
  • FULL
    조인을 하되, 양 테이블의 모든 레코드가 출력되어야 함 (조건에 안맞는 레코드는 null과 연결)
    author_id = member_id를 이용해 FULL OUTER JOIN 하면
member_idnamepost_idtitleauthor_id
1a1hi1
1a2hello1
2bnullnullnull
3cnullnullnull
nullnull3bye4

→ 행의 순서는 ORDER BY를 이용해 제어 가능(위의 경우는 member_id 순서, 그 이후 author_id 순서)

CROSS JOIN(카티전 곱)

SELECT *
FROM <첫 번째 테이블>
    CROSS JOIN <두 번째 테이블>

각 테이블의 모든 행을 이어붙인다 라고 이해하면됩니다(조건X) 위에서의 두 테이블을 이용하면

3 X 3이 되니 행이 9개짜리인 테이블이 만들어집니다

SELF JOIN

그냥 스스로를 INNER JOIN 하면 됩니다

서브쿼리

서브쿼리를 활용하면 데이터를 다양한 방식으로 분석하고 필터링하는 등의 복잡한 연산을 수행할 수 있습니다

페이징

Offset 페이징

페이지 번호와 객체 개수를 이용

JPA의 Pageable 클래스(인터페이스)가 offset 페이징 방식

public PageDto getAllPosts(int page, int size) {
        Pageable pageable = PageRequest.of(page, size, Sort.by(Sort.Direction.DESC, "createdAt"));
        Page<Post> postPage = postRepository.findAll(pageable);
        boolean hasNext = postPage.hasNext();
        List<PostResponseDto> posts = postPage.getContent().stream()
                .map(PostResponseDto::of)
                .collect(Collectors.toList());
        return PageDto.builder()
                .posts(posts)
                .hasNext(hasNext)
                .build();
    }

Page객체 전체를 출력해도 되지만 PageDto를 이용해 필요한 정보만 출력!

createdAt을 기준으로 DESC정렬(가장 최근에 작성한 글이 가장 위에 오도록)

Cursor(또는 Keyset) 페이징

모바일 환경에서의 무한 스크롤 방식(인스타그램)이 커서 페이징 방식

JPA에서 직접적으로 지원하진 않고 워크북처럼 쿼리 작성으로 구현

스프링 프로젝트에도 쿼리가?

@Query

이름은 쿼리지만 엄밀히 말하면 살짝 다르다

JPA → JPQL(SQL을 추상화한 객체지향언어)

실제 SQL을 사용하려면 nativeQuery = true로 해주면 된다

@Query("SELECT p FROM Post p INNER JOIN p.author a WHERE a.member_id = :memberId")
List<Post> findAllPostsByAuthorMemberI(@Param("memberId") Long memberId);

위에서 써본 커서 페이징도 쿼리로 구현 가능하다

@Query(value = "SELECT * FROM posts WHERE post_id > :lastPostId ORDER BY createdAt DESC LIMIT 10", nativeQuery = true)
List<Post> findPostsForNextPage(@Param("lastPostId") Long lastPostId);

미션

어땠나요..?

숙제

Untitled

요런 느낌

질문

  • @NoArgsConstructor 와 @AllArgsConstructor @NoArgsConstructor JPA(Hibernate)는 엔터티 객체를 생성할 때 기본 생성자(파라미터가 없는 생성자)를 사용하여 인스턴스를 생성하게 됩니다. 따라서 엔터티 클래스는 항상 기본 생성자를 가지고 있어야 합니다. 만약 기본 생성자가 없다면, JPA는 엔터티 인스턴스를 생성하지 못하고, 동작하지 않게 됩니다. @AllArgsConstructor 코드 상에서 직접 엔터티 객체를 생성할 때 사용하는 것입니다. 이 생성자가 없다면, 개발자가 엔터티를 생성하여 사용할 때 모든 필드를 따로 setter 메서드로 설정해야 합니다.
    Member member = new Member();
    member.setMemberId(id);
    member.setName(name);
    member.setEmail(email);
    이렇게 생성하던 객체
    Member member = new Member(id, name, email);
    만약 둘 다 없다면?
    @Entity
    public class Member {
    
        @Id
        @GeneratedValue(strategy = GenerationType.IDENTITY)
        private Long memberId;
    
        private String name;
        private String email;
    
        // 기본 생성자
        public Member() {
        }
    
        // 모든 필드를 파라미터로 가지는 생성자
        public Member(Long memberId, String name, String email) {
            this.memberId = memberId;
            this.name = name;
            this.email = email;
        }
  • 엔티티에 객체 자료형은 @Setter 못쓰나요? 쓸 수 있습니다! 그런데 @Setter 어노테이션은 엔티티 단계에선 웬만하면 안쓰는게 좋아서 꼭 필요한 것만 메서드로 만드는 것을 추천드립니다
  • ResponseDto 속 of 메서드 다시 설명해주십쇼! of 메서드는 엔터티 인스턴스를 받아서, 해당 엔터티의 데이터를 사용해 ResponseDto의 인스턴스를 생성하고 반환하는 역할을 합니다. 즉, 엔터티 객체를 ResponseDto 객체로 변환하는 역할을 하는 메서드입니다. static 키워드를 사용한 이유는 of 메서드를 인스턴스 생성 없이 클래스 레벨에서 바로 호출할 수 있게 만들기 위함입니다
  • ResponseDto 굳이 쓰는 이유? 쓰지 않으면 객체를 직접 응답데이터에 넣어야하는데, @OneToMany같은 연결된 엔티티인 경우 @JsonIgnore를 사용하지 않으면 Member 엔터티와 PostLike 리스트가 JSON으로 직렬화 될 때 불필요한 데이터까지 API 응답에 포함될 수 있습니다. 또한, 양방향 관계에서 순환 참조 문제도 발생할 수 있습니다.

0개의 댓글