게시글 목록보기 (+default_batch_fetch_size)

coding S's avatar
Mar 14, 2024
게시글 목록보기 (+default_batch_fetch_size)

BoardRepository

@RequiredArgsConstructor @Repository public class BoardRepository { private final EntityManager em; public List<Board> findAll() { Query query = em.createQuery("select b from Board b order by b.id desc", Board.class); return query.getResultList(); }
 

Test 해보기!

💡
ORM이라 User객체가 같이 튀어나올 수 도 있으니 테스트 해줘야함! JPA는 무조건! 쿼리 실행 확인해봐야함! 예상치 못한게 튀어나올 수도 있으니!
notion image
💡
스니펫 설정 해줬음
@Test public void findAll_test() { //given //when List<Board> boardList = boardRepository.findAll(); //then }
 

LAZY와 EAGER 비교

[ EAGER ver. ]

notion image
notion image
💡
EAGER 전략으로 findAll을 했을 때! findById 일 때는 join을 해서 들고 왔는데, findAll 일 때는 Board만 조회한 뒤에 User가 게시글을 쓴 수만큼 select 절이 더 발생한다 즉, 3명의 user가 글을 적었기 때문에 user join이 3번 되는 것. → 쓰지 말아야겠죠? 더미 데이터를 보면 4개가 있는데 왜 4번이 아닐까? 1번은 캐싱 되기 때문!
💡
컬렉션 조회 시 EAGER은 악(惡) !!!
💡
글 쓴 유저가 많으니까 SELECT가 남발된다 → 직접 JOIN쿼리를 써라 (쌤은 제일 추천!)

[ 만약, 1번인 ssar만 더미 데이터에 있다면? ]

notion image
notion image
💡
board와 user 각각 1번씩 select가 날아간다. 그러니 총 쿼리문은 1번만 날아가는 것!
 

[ LAZY ver. ]

notion image
💡
똑같이 했는데, fetch전략을 LAZY로 바꾸니까 Board만 조회 됨!
 

default_batch_fetch_size ?

[ lazy loading 테스트 ]

@Test public void findAll_lazyloading_test() { List<Board> boardList = boardRepository.findAll(); boardList.forEach(board -> { System.out.println(board.getUser().getUsername()); //lazy loading }); }
💡
boardList.forEach(board -> { ... }); 이 부분에서, board는 boardList에 포함된 각각의 'Board' 객체를 차례로 가리키는 변수. 즉, forEach 루프가 반복될 때마다 board는 리스트의 다음 'Board' 객체를 참조하게 된다. 이를 통해 개별 'Board' 객체에 접근하여 추가적인 작업을 수행할 수 있다.
💡
Board를 타고 들어가서(?) User의 Username을 조회한다 → lazy loading 'User' 객체는 board.getUser()가 호출되는 시점에 데이터베이스에서 로드 됨!
처음 'Board' 엔티티 로드: 데이터베이스에서 'Board' 엔티티를 조회할 때, 연결된 'User' 엔티티는 즉시 로드되지 않는다. 이 시점에서는 'Board' 엔티티만 메모리에 로드됨. 'User' 엔티티에 접근 시 'Lazy Loading' 발생: 'Board' 엔티티로부터 'User' 엔티티의 정보(예: 'Username')가 필요할 때, 그제서야 'User' 엔티티를 데이터베이스에서 로드한다. 이 과정에서 'Lazy Loading'이 발생하며, 실제로 'User' 엔티티의 데이터가 필요한 시점까지 데이터베이스 조회를 지연시킨다.
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=? love 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=? cos 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=? ssar ssar
💡
EAGER이랑 똑같네 (?) → JOIN 써야겠죠
 

JOIN 외의 해결 방법 [ default_batch_fetch_size ] → 실무/프로젝트는 이걸 사용

notion image
notion image
💡
LAZY하게 땡길 때, SELECT를 3번 안 땡기고 자동으로 in 쿼리를 만들어줌! -> SELECT 2방에 끝남! default_batch_fetch_size = 10 이라고 했으니 10번의 [ ? ] 가 튀어나온 것!
데이터베이스 최적화 기법 중 하나인 IN 절을 이용한 쿼리 최적화 방법 Hibernate 같은 ORM을 사용할 때, default_batch_fetch_size 설정을 통해 한 번에 가져올 연관된 엔티티의 수를 제어하는 방법! default_batch_fetch_size = 10이라고 설정하면, 한 번의 쿼리로 최대 10개의 연관된 엔티티를 가져올 수 있게 된다. 이는 N+1 문제를 해결하는 데 도움이 되는 설정. (N+1 문제란, 한 번의 쿼리로 N개의 엔티티를 가져온 후, 각 엔티티에 대해 추가적으로 1번씩 쿼리를 더 날려야 하는 문제) 예를 들어, 게시글을 쓴 사람이 30명이고, 이 사람들의 정보를 가져오고 싶다면, IN 쿼리에는 한 번에 10명의 user_id가 들어가게 된다. 따라서, 30명의 정보를 가져오기 위해 총 3번의 쿼리가 실행되겠죠. 만약 게시글을 쓴 사람이 3명이라면, 한 번의 쿼리로 충분하고, 10명이라면 역시 한 번의 쿼리로 충분!
WHERE 절에서는 주로 기본키(PK)로 조회하기 때문에 인덱스를 효율적으로 사용할 수 있어 쿼리 성능이 좋다. -> 빠르다! JOIN보다 IN 쿼리가 더 빠르다! -> IN 쿼리가 인덱스를 효율적으로 활용할 수 있기 때문! 또한 user_id를 리스트 형태로 동적으로 생성하여 IN 쿼리에 사용하게 되는데, 이는 여러 user_id에 대한 정보를 한 번에 가져올 수 있게 하여 쿼리의 수를 줄이고 성능을 향상시키는 방법! 즉, user_id를 int LIST 같은 걸로 1, 2, 3 ~~ user_id를 모음. 이걸 동적으로 우리가 만들어야 하는 것
 

BoardController

@GetMapping("/" ) public String index(HttpServletRequest request) { List<Board> boardList = boardRepository.findAll(); request.setAttribute("boardList", boardList); return "index"; }
 

index.mustache

{{#boardList}} <div class="card mb-3"> <div class="card-body"> <h4 class="card-title mb-3">{{title}}</h4> <a href="/board/{{id}}" class="btn btn-primary">상세보기</a> </div> </div> {{/boardList}}
 

화면 확인

notion image
💡
목록이 생겼다
 

TIP!

"select b, (select count(r) from Reply r where r.board.id = b.id) from Board b order by b.id desc"
💡
서브쿼리는 이런식으로 작성한다~!
 

[ Pageable ]

notion image
💡
페이징도 이런 걸 쓰면 첫 페이지부터 마지막 페이지까지 촤아악 해줌
 

[ + + 보단 replace를 주로 사용 ]

String q = "seelct * from user where id = :id and username = :username"; q = q.replace(":id", id+"").replace(":username", username+"");
 
Share article

codingb