게시글 목록보기 (in query 직접 만들기)

coding S's avatar
Mar 14, 2024
게시글 목록보기 (in query 직접 만들기)

default_batch_fetch_size로 쉽게 쓸 수 있지만 동적 쿼리로 직접 만들어보자!

목록보기를 할 때, 게시글과 게시글을 쓴 사람은 다르잖아 게시글은 4갠데 ssar이 게시글을 2개 써서 작성자는 3명. (크기 4, 크기 3) 심지어 게시글은 desc로 되어있어서 최신순부터 나옴. 이걸 작성자랑 게시글을 동적 쿼리, in쿼리 이런 걸 사용해서 맞춰줘야 함
 

레파지토리에서 바로 짜지 말고, 하나씩 기능 빼서 해보자. 나눠서 차근차근

[ distinct 없다 ]

@Test public void findAll_custom_inquery_test() { List<Board> boardList = boardRepository.findAll(); int[] userIds = boardList.stream().mapToInt(board -> board.getUser().getId()).toArray(); for (int i : userIds) { System.out.println(i); } }
notion image
💡
user_id를 일단 배열로 담음
💡
3, 2, 1, 1 ?? → 1은 한 번만 들어가야 하니까 distinct를 건다.
 

[ distinct 사용 ] - toArray로 받기

@Test public void findAll_custom_inquery_test() { List<Board> boardList = boardRepository.findAll(); int[] userIds = boardList.stream().mapToInt(board -> board.getUser().getId()).distinct().toArray(); for (int i : userIds) { System.out.println(i); } }
notion image
💡
이렇게! 이런 식으로 나와야 함! 그 이후 Repository에서 동일한 것끼리 filter를 써서 매핑 시켜야함.
 

[ distinct 사용 ] - toList로 받기 (boxed 필요) ← 애가 좋아보임

@Test public void findAll_custom_inquery2_test() { List<Board> boardList = boardRepository.findAll(); List<Integer> userIds = boardList.stream().mapToInt(board -> board.getUser().getId()).distinct().boxed().toList(); System.out.println(userIds); }
notion image
 

[ 설명 ]

게시판(Board) 엔티티에 연결된 사용자(User)의 ID를 조회하는 것 stream API를 사용하여 boardList의 각 Board 객체에서 사용자(User)의 ID를 추출하여 정수 배열 userIds에 저장한다. mapToInt는 객체 스트림을 기본형 int 스트림으로 변환하는 중간 연산이며, board -> board.getUser().getId()는 각 게시판 엔티티에서 사용자 엔티티를 가져와 그 ID를 추출하는 람다 표현식. 마지막으로, toArray() 메서드는 스트림을 배열로 변환
 

쿼리문을 만들어야함. ? 개수를 정해주는 것! id가 몇 개 들어올지 모르니까!

@Test public void randomquery_test(){ int[] ids = {1,2}; // select u from User u where u.id in (?,?); String q = "select u from User u where u.id in ("; for (int i=0; i<ids.length; i++){ //0부터 시작해서 -1을 해줌. 쿼리문 마지막에는 )를 닫아야하니까, 마지막에 )를 해줌 if(i == ids.length -1){ q = q + "?)"; }else{ q = q + "?,"; } } System.out.println(q); }
notion image
💡
? 쓰면 안된다. [ :id ] 이거 써야한다. JPQL이니까! → 고쳐야 한다!! 일단 이런 식으로 나와야 한다~ 정도만 생각하자!
 

JPQL과 List로 쿼리문을 만들어주자!! (동적으로 쿼리 문자열 생성!)

@Test public void randomquery_test(){ // int[] ids = {1, 2, 3, 4}; List<Integer> ids = Arrays.asList(1, 2, 3, 4); // select u from User u where u.id in (?,?); String q = "select u from User u where u.id in ("; //User 테이블에서 id가 다음 리스트(ids) 안에 있는 데이터를 모두 골라줘 for (int i=0; i<ids.size(); i++){ //0부터 시작해서 -1을 해줌. 쿼리문 마지막에는 )를 닫아야하니까, 마지막에 )를 해줌 //사이즈는 4인데, 배열은 0부터 시작하니 0, 1, 2, 3. 때문에 마지막을 나타내려면 -1 해줌! if(i == ids.size() -1){ q = q + ":id" + i + ")"; }else{ q = q + ":id" + i + ","; } } System.out.println(q); }
User라는 데이터 테이블에서, 특정 id값을 가진 데이터를 찾기 위한 명령문(쿼리)을 만드는 것 우리가 찾고 싶은 id 값들은 1, 2, 3, 4 리스트(ids)에 있는 각 숫자를 하나씩 살펴보면서, 쿼리에 추가. 숫자를 추가할 때는 ":id0", ":id1"처럼 ":id" 뒤에 숫자를 붙여서 표시한다. 이렇게 하면 나중에 실제 숫자로 바꿔 넣을 수 있다! 최종적으로 "select u from User u where u.id in (:id0,:id1,:id2,:id3)"라는 쿼리를 만드는 게 목표! 즉, 나는 1, 2, 3, 4라는 id를 가진 User 데이터를 찾고 싶어"라고 데이터베이스에 말하는 방법을 만드는 것!
notion image
💡
이제 값을 넣을 때 query.setAttribute(”id0”, id0); query.setAttribute(”id1”, id1); query.setAttribute(”id2”, id2); 이렇게 넣어줄 것 → for문 돌려서 넣기. 이건 3개일 때만 사용할 수 있어서 재사용을 못하니까 늘어날 경우를 대비해 for문으로 !! 해야함!!
 

BoardRepository → 미완

public class BoardRepository { private final EntityManager em; public List<Board> findAllV2() { String q1 = "select b from Board b order by b.id desc"; //lazy로딩하면 망한다. 인쿼리(동적쿼리)로 만들어야 함. List<Board> boardList = em.createQuery(q1, Board.class).getResultList(); //애 크기 4 String q2 = ""; //boardList가 가지고 있는 유저의 개수만큼 ?를 걸어서 in 쿼리가 나와야함. List<User> userList = em.createQuery(q2, User.class).getResultList(); //애 크기 3 return boardList; //리턴되는 boardList에는 user가 채워져 있어야 함 //stream의 filter 사용 -> 다들 map을 사용함 }
 

정답

https://getinthere.notion.site/4-2-in-query-12863cbf332d4b6a9e0305cf264b3393
notion image
보드가 들고 있는 번호를 다 뽑아낸다. stream 안 쓰면 코드가 복잡... (for문으로 돌려야함ㅠㅠ) map으로 가공하면 자기 자신을 리턴하는데, mapToInt로 돌려준다. 그럼 이제 물길에 int로 바뀌어서 돌려준 것. 1, 1, 2, 3 라고 중복이 나오니까 distict 해서 1, 2, 3으로 바꿔줌 boxed라는 문법으로 받아야지 toList로 바꿔줌. (boxed.toList) 아니면 toArry라고 배열로 받아줘야함.
notion image
💡
stream 안 썼을 땐 이렇게 for문을 돌림….. (중복 제거해주려고 Set 씀)
 

이제 쿼리문 살펴보자

notion image
:userIds 하면 자기가 알아서 막 집어넣어줌. [ 1, 2, 3 ] 이 들어가겠지. id를 서로 비교해서 같으면 setUser해서 넣어주면
 

정수씨 버전

동적 쿼리 (문자열을 직접 짬)

notion image
if해서 i가 length - 1 이면 끝이라는 말이니까 ) set파라미터로 바인딩 안하고 q2 = q2 + userIds[i] + 로 바로 넣음!
💡
forEach는 현재 바퀴가 마지막 바퀴인지 모름
 

테스트로 확인

notion image
notion image
notion image
💡
user_id가 정확하게 매칭이 된 것도 확인
 
 

쌤 블로그 정리본

1. 조회 전략

우선 Lazy 전략으로 Board만 조회하고, Lazy loading을 통해 user들을 in query로 조회한다. (쿼리 2번만 끝)
Hibernate: select b1_0.id, b1_0.content, b1_0.created_at, b1_0.title, b1_0.user_id from board_tb b1_0 order by b1_0.id desc Hibernate: select u1_0.id, u1_0.created_at, u1_0.email, u1_0.password, u1_0.username from user_tb u1_0 where u1_0.id in (?, ?, ?)
 

2. 조회 쿼리 작성

1. 아래와 같이 조회를 두번한다. 2. 두개의 결과를 2중 for문을 돌면서 filter로 매칭시킨다.

2-1. V2는 JPQL에 List를 직접 매칭시키는 법 ← 어려워!

public List<Board> findAllV2(){ Query q1 = em.createQuery("select b from Board b order by b.id desc", Board.class); List<Board> boardList = q1.getResultList(); List<Integer> userIds = boardList.stream().mapToInt(value -> value.getUser().getId()).distinct().boxed().toList(); Query q2 = em.createQuery("select u from User u where u.id in :userIds", User.class); q2.setParameter("userIds", userIds); List<User> userList = q2.getResultList(); boardList.stream().forEach(b -> { User user = userList.stream().filter(u -> u.getId() == b.getUser().getId()).findFirst().get(); b.setUser(user); }); return boardList; }
 

2-2. V3는 for문을 돌면서 동적 쿼리를 만들어 내는 법 ← 기본기!

public List<Board> findAllV3(){ String q1 = "select b from Board b order by b.id desc"; List<Board> boardList = em.createQuery(q1 , Board.class).getResultList(); int[] userIds = boardList.stream().mapToInt(board -> board.getUser().getId()).distinct().toArray(); String q2 = "select u from User u where u.id in ("; for (int i = 0; i < userIds.length ; i++) { if (i == userIds.length - 1){ q2 = q2 + userIds[i] + ")"; }else { q2 = q2 + userIds[i] + ","; } } List<User> userList = em.createQuery(q2 , User.class).getResultList(); for (Board board : boardList){ for (int i = 0; i < userList.size(); i++) { User user = userList.get(i); if (user.getId() == board.getUser().getId()){ board.setUser(user); } } } return boardList; // user가 채워져 있어야함. }
 

3. 조회 쿼리 테스트

@Test public void findAllV2_test(){ List<Board> boardList = boardRepository.findAllV2(); System.out.println("findAllV2_test : 조회완료 쿼리 2번"); boardList.forEach(board -> { System.out.println(board); }); }
notion image
 
Share article

codingb