복잡한 쿼리 짜는 법

윤주헌's avatar
Sep 23, 2024
복잡한 쿼리 짜는 법
복잡한 쿼리짜는 법
보드 쿼리로 이동
public void selectV1() { //게시글마다의 댓글 개수 뽑는 쿼리 //데이터 보면 String sql = """ select * from board_tb """; Query query = em.createNativeQuery(sql); }
 
db더미
insert into board_tb(title, content, created_at, user_id) values ('제목1', '내용1', now(), 1); insert into board_tb(title, content, created_at, user_id) values ('제목2', '내용2', now(), 1); insert into board_tb(title, content, created_at, user_id) values ('제목3', '내용3', now(), 2); insert into board_tb(title, content, created_at, user_id) values ('제목4', '내용4', now(), 2); insert into board_tb(title, content, created_at, user_id) values ('제목5', '내용5', now(), 2); insert into board_tb(title, content, created_at, user_id) values ('제목6', '내용6', now(), 1); insert into board_tb(title, content, created_at, user_id) values ('제목7', '내용7', now(), 1); insert into board_tb(title, content, created_at, user_id) values ('제목8', '내용8', now(), 2); insert into board_tb(title, content, created_at, user_id) values ('제목9', '내용9', now(), 2); insert into board_tb(title, content, created_at, user_id) values ('제목10', '내용10', now(), 2);
 
 
notion image
 
쿼리짜기
 
1 제목5 3
2 제목4 1
3 제목3 0 이렇게 뽑아야함
 
쿼리
여기서 댓글 카운트만 뽑으면 된다
select id, title from board_tb
 
댓글 카운트 뽑는 쿼리
select * from reply_tb where board_id =5;
notion image
count로 친다
select count(id) from reply_tb where board_id =5;
3 나온다
 
2개 쿼리 합치는 방법
select id, title , 5 from board_tb;
notion image
 
합치면 이런거를 스칼라 서브쿼리라고 한다 (스칼라 → 1개 ) 즉 한개만 가능하다!
select id, title , select count(id) from reply_tb where board_id =5 from board_tb;
notion image
 
 
 
select comment from reply_tb where board_id =5;
notion image
컬럼도 1개 야 하고 행도 1개여야 한다!
컬럼이 2개면 2차원
 
select id, title , select comment from reply_tb where board_id =5 from board_tb;
board에서 10개 퍼올렸어
제목 10부터 1까지 10에 커서가 있다
프로잭션 화살 내리면서 시작한다 즉 10로우 id10 title 10 그 다음 꺼내려고 하니까 select시작함 뽑아내니 3건이네? ,로 넣을 수 없어서
 
 
select id, title , select count(id) from reply_tb where board_id =5 from board_tb;
결과가
 
커서(초기)
→(내리면) 10 제목10
9 제목 9
8 제목 8
 
select id, title , select count(id) from reply_tb where board_id =bt.id from board_tb bt;
컬럼이름 별로임
notion image
select id, title , (select count(id) from reply_tb where board_id =bt.id) count from board_tb bt;
notion image
 
일단 뽑았으니

보드쿼리리포지토리 이동

public void selectV1() { //게시글마다의 댓글 개수 뽑는 쿼리 //데이터 보면 String sql = """ select id, title , (select count(id) from reply_tb where board_id =bt.id) count from board_tb bt; """; Query query = em.createNativeQuery(sql); }

테스트가서 보드쿼리리파지토리클래스 만들고 테스트

package org.example.springv3.board; import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.autoconfigure.orm.jpa.DataJpaTest; import org.springframework.context.annotation.Import; @DataJpaTest @Import(BoardQueryRepository.class) public class BoardQueryRepositoryTest { @Autowired private BoardQueryRepository boardQueryRepository; @Test public void selectV1_test(){ //given //when boardQueryRepository.selectV1(); //eye } }
이상 없음

보드쿼리레파지토리 이동

이제 값을 볼거임
 
디버그 모드로 왼쪽 클릭해서 보기
notion image
인터넷 mvnRepository
qlrm 치기
 
jquery를
notion image
notion image
 
notion image
화면으로 전달하는 dto responseDTO
 
dataTransfoObject
컨트롤러와 유저 사이에 있다
 
DB와 레파지토리 사이에 있다 DTO라고 부르기 애매하고 (DB에서 가지고 오는 것 DB데이터 같이)
 
DB데이터 그래대로 담은것을 model
왜 model인가? 클래스고 테이블인데
db 테이블을 자바세상 클래스로 바꾸는 것을 모델링이라고 한다
설계도 그대로 실제 세상에 올린다 즉 자기세상데이터로 옮기는 것
그래서 model이라고 한다
이거는 우리가 하는게 아니고 ORM이 해준다 (JPA에서는)
 
컨트롤러가 유저한테 줄 때 DTO
model에서 DTO를 받은건데 아직
DB데이터 아무 변형 없이 줄 수 있어서 그냥 DTO라고 부르기도 한다
한방에 주는 것을 한방쿼리라고 한다 (이런 것을 서비스쿼리도 없다)

보드폴더에 BoardListVO클래스 만들기

나중에는 다른데 옮길건데 일단은 다르게만들어보자
package org.example.springv3.board; import lombok.Data; @Data public class BoardListVO { private Integer id; private String title; private Integer count; }

보드쿼리레파지토리

vo같은 것 만든다
 
notion image
1건이면 uniqueRest
2건이상이면 list
package org.example.springv3.board; import jakarta.persistence.EntityManager; import jakarta.persistence.Query; import lombok.RequiredArgsConstructor; import org.qlrm.mapper.JpaResultMapper; import org.springframework.stereotype.Repository; import java.util.List; @RequiredArgsConstructor @Repository public class BoardQueryRepository { private final EntityManager em; public List<BoardListVO> selectV1() { //게시글마다의 댓글 개수 뽑는 쿼리 //데이터 보면 String sql = """ select id, title , (select count(id) from reply_tb where board_id =bt.id) count from board_tb bt; """; Query query = em.createNativeQuery(sql); JpaResultMapper mapper = new JpaResultMapper(); List<BoardListVO> boardList = mapper.list(query, BoardListVO.class); return boardList; } }

테스트

package org.example.springv3.board; import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.autoconfigure.orm.jpa.DataJpaTest; import org.springframework.context.annotation.Import; import java.util.List; @DataJpaTest @Import(BoardQueryRepository.class) public class BoardQueryRepositoryTest { @Autowired private BoardQueryRepository boardQueryRepository; @Test public void selectV1_test(){ //given //when List<BoardListVO> boardList = boardQueryRepository.selectV1(); //eye // System.out.println(boardList); } }
터짐
notion image
 
풀 생성자를 때림
우리는 풀 생성자가 없다 리플랙션으로 구현되지 않았다!
리플랙션으로 구현됐으면 setter로 필요한것 딱딱 넣을 줄 것임
 
 
풀 생성자 만들면 될까? 아니
타입이 다르잖아 count는 long이니까
바꾸자
 
주의점
  1. 풀 생성자 만들어야 한다
  1. 타입 맞춰야 한다
 
 

보드리스폰스로 이동

package org.example.springv3.board; import lombok.Data; import org.example.springv3.reply.Reply; import org.example.springv3.user.User; import org.springframework.data.domain.Page; import java.util.ArrayList; import java.util.List; public class BoardResponse { @Data public static class DetailDTO { private Integer id; private String title; private String content; private Boolean isOwner; // private Integer userId; private String username; //리플라이 엔티티 집이 넣으면 안된다 레이지 로딩 되니까! 똑같이 생긴 DTO만들면 된다 비영속객체 만들어서 응답하게 하는게 좋다! private List<ReplyDTO> replies = new ArrayList<>(); public DetailDTO(Board board, User sessionUser) { this.id = board.getId(); this.title = board.getTitle(); this.content = board.getContent(); this.isOwner = false; if (sessionUser != null) { if (board.getUser().getId() == sessionUser.getId()) { isOwner = true; // 권한체크 } } // this.userId = board.getUser().getId(); this.username = board.getUser().getUsername(); for(Reply reply : board.getReplies()) { replies.add(new ReplyDTO(reply,sessionUser)); } } } @Data public static class ReplyDTO{ private Integer id; private String comment; private String username; private Boolean isOwner; public ReplyDTO(Reply reply, User sessionUser) { this.id = reply.getId(); this.comment = reply.getComment(); this.username = reply.getUser().getUsername(); this.isOwner = false; if (sessionUser != null) { if (reply.getUser().getId() == sessionUser.getId()) { isOwner = true; // 권한체크 } } } } @Data public static class DTO { private Integer id; private String title; private String content; public DTO(Board board) { this.id = board.getId(); this.title = board.getTitle(); this.content = board.getContent(); } } @Data public static class PageDTO { private Integer number; // 현재페이지 private Integer totalPage; // 전체페이지 개수 private Integer size; // 한페이지에 아이템 개수 private Boolean first; private Boolean last; private Integer prev; // 현재페이지 -1 private Integer next; // 현재페이지 +1 private List<Content> contents = new ArrayList<>(); private List<Integer> numbers = new ArrayList<>(); public PageDTO(Page<Board> boardPage) { this.number = boardPage.getNumber(); this.totalPage = boardPage.getTotalPages(); this.size = boardPage.getSize(); this.first = boardPage.isFirst(); this.last = boardPage.isLast(); this.prev = boardPage.getNumber()-1; this.next = boardPage.getNumber()+1; int temp = (number / 3)*3; // 0-> 0, 3->3, 6->6 for(int i = temp; i<temp+2; i++){ this.numbers.add(i); } //for로 id title만 있으면 확인이 가능하다 //어디서 값을 가지고 와야 하지? for(Board board : boardPage.getContent()) { contents.add(new Content(board)); } } //생성자를 만들어서 @Data class Content { private Integer id; private String title; public Content(Board board) { this.id = board.getId(); this.title = board.getTitle(); } } } @Data // getter, setter, toString public static class ListDTO { private Integer id; private String title; private Long count; public ListDTO(Integer id, String title, Long count) { this.id = id; this.title = title; this.count = count; } } }

보트쿼리리포지토리

package org.example.springv3.board; import jakarta.persistence.EntityManager; import jakarta.persistence.Query; import lombok.RequiredArgsConstructor; import org.qlrm.mapper.JpaResultMapper; import org.springframework.stereotype.Repository; import java.util.List; @RequiredArgsConstructor @Repository public class BoardQueryRepository { private final EntityManager em; public List<BoardResponse.ListDTO> selectV1() { String sql = """ select id, title, (select count(id) from reply_tb where board_id = bt.id) count from board_tb bt; """; Query query = em.createNativeQuery(sql); JpaResultMapper mapper = new JpaResultMapper(); List<BoardResponse.ListDTO> boardList = mapper.list(query, BoardResponse.ListDTO.class); return boardList; } }

기존 VO는 지우기

기존 VO
package org.example.springv3.board; import lombok.Data; @Data public class BoardListVO { private Integer id; private String title; private Long count; public BoardListVO(Integer id, String title, Long count) { this.id = id; this.title = title; this.count = count; } }
 

바뀐 test

package org.example.springv3.board; import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.autoconfigure.orm.jpa.DataJpaTest; import org.springframework.context.annotation.Import; import java.util.List; @DataJpaTest @Import(BoardQueryRepository.class) public class BoardQueryRepositoryTest { @Autowired private BoardQueryRepository boardQueryRepository; @Test public void selectV1_test(){ //given //when List<BoardResponse.ListDTO> boardList = boardQueryRepository.selectV1(); //eye System.out.println(boardList); } }
 
Share article

code-sudal