v3 - 게시글 상세보기

coding S's avatar
Mar 14, 2024
v3 - 게시글 상세보기
[ 학습 목표 ] 1. Eager -> 연관된 객체에 값을 채워준다 2. Lazy -> 연관된 객체에 값을 채워주지 않는다 3. Lazy Loading -> Lazy 전략일 때만 일어남 4. Object Relation Mapping 5. 직접 조인 (조인도 해주고 매핑 전략으로 매핑도 해주고 -> ORM)
 

 
Board를 조회하면 user_id를 User객체의 id에 담아서 Board 객체를 만들어준다. -> join이 되는 건 아님!
@RequiredArgsConstructor @Repository public class BoardRepository { private final EntityManager em; public Board findById(int id) { //id, title, content, user_id(이질감), created_at Board board = em.find(Board.class, id); return board; } }
 

fetch 전략의 종류

notion image
EAGER = 연관된 객체를 들고와 LAZY = 내꺼만 들고와
 

게시글 상세보기 테스트 (EAGER 일 때 → JOIN이 일어남)

notion image
💡
@ManyToOne은 fetch 전략을 적지 않으면 디폴트가 EAGER이다.
@Import(BoardRepository.class) @DataJpaTest public class BoardRepositoryTest { @Autowired private BoardRepository boardRepository; @Test public void findById_test() { int id = 1; boardRepository.findById(1); } }
notion image
💡
EAGER로 하니까 join이 일어났네! User도 모두 select 하네! left join은 연산이 많이 일어나서 별루…. 성능이…
notion image
💡
이렇게 일어나는게 아니라고!! JOIN이 일어나고, User를 죄다 받아옴 왜냐? fetch가 EAGER이라서!!
 

게시글 상세보기 쿼리 작성 (LAZY → JOIN 일어나지 않음!)

notion image
fetch전략을 LAZY로 바꾸고 다시 test를 돌려봤더니...
notion image
💡
딱 필요한 것만 들고 왔네!! = JOIN이 일어나지 않음!
 

Lazy 테스트 코드

@Test public void findById_test() { int id = 1; System.out.println("start - 1"); Board board = boardRepository.findById(id); System.out.println("start - 2"); System.out.println(board.getUser().getId()); System.out.println("start - 3"); } }
notion image
 

Lazy 로딩 설명 및 테스트 코드

@Import(BoardRepository.class) @DataJpaTest public class BoardRepositoryTest { @Autowired private BoardRepository boardRepository; @Test public void findById_test() { int id = 1; System.out.println("start - 1"); Board board = boardRepository.findById(id); System.out.println("start - 2"); System.out.println(board.getUser().getId()); System.out.println("start - 3"); System.out.println(board.getUser().getUsername()); // user_id는 조회가 됐으나, getUsername은 안 되었다. // 그러나... null값이 뜨는게 아니라, 값이 잘 뜬다!! // 그러나... select 가 2번 일어난다. } }
notion image
💡
이게 바로 lazy 로딩. 별로 좋은게 아니다….. select 문 2번 날리는 걸 누가 좋아하나 ㅠㅠ 차라리 직접 join 쿼리를 적어라
💡
lazy 로딩이란? 연관 관계 fetch 전략이 lazy 상태일 때, 연관된 객체의 PK가 아닌 필드값에 접근을 하면 지연 로딩(한번 더 조회 쿼리가 지연되게 발동)이 일어난다. 없으니까, 자기가 스스로 조회해서 집어넣어서 채워넣는다. 어짜피 쓸거면 .. 뭐하러 지연 로딩을 하나? join해서 직접 써라!
 

우리는 상세보기를 할 때에 어떤 전략을 선택해야 하나?

* Lazy 전략을 사용하고 * 필요한 경우 직접 조인하자
 

[ 프로젝트에 적용하기 ]

[ BoardRepository 쿼리문에 fetch 안 적은 버전 ]

@RequiredArgsConstructor @Repository public class BoardRepository { private final EntityManager em; public Board findByIdJoinUser(int id) { Query query = em.createQuery("select b from Board b join b.user u where b.id = :id", Board.class); //이게 바로 조인 query.setParameter("id", id); return (Board) query.getSingleResult(); }
Board에 User도 있으니까 b.user로도 조인해라 (on 생략 가능) 외래키가 아니라 다른거랑 조인하고 싶으면 on을 적으면 되는데, 이런 경우는 거의 없다.

[ fetch 안 적은 버전 테스트 ]

notion image
b1_0.user_id 밑에 user도 촤아아악 나와야함. = 프로젝션이 안됨. -> join할 때는 fetch 해서 select 절을 프로젝션 해줘야한다. -> 바꿔보자!!
 

BoardRepository 쿼리문에 fetch 적은 버전 (상세 보기는 이걸 쓰자!)

→ 이게 바로 join

@RequiredArgsConstructor @Repository public class BoardRepository { private final EntityManager em; public Board findByIdJoinUser(int id) { Query query = em.createQuery("select b from Board b join fetch b.user u where b.id = :id", Board.class); //이게 바로 조인 query.setParameter("id", id); return (Board) query.getSingleResult(); }
💡
fetch라고 적어야 b와 연관되어 있는 것까지 쫙 나온다. join과 fetch는 한 쌍!

[ fetch 적은 버전 테스트 ]

notion image
💡
user도 쫘아아악 나왔다. 이렇게 join할 때는 fetch 해서 select 절을 프로젝션 해줘야한다.
 

테스트 코드

package shop.mtcoding.blog.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(BoardRepository.class) @DataJpaTest public class BoardRepositoryTest { @Autowired private BoardRepository boardRepository; @Test public void findByIdJoinUser_test(){ int id = 1; boardRepository.findByIdJoinUser(id); } @Test public void findById_test() { int id = 1; System.out.println("start - 1"); Board board = boardRepository.findById(id); System.out.println("start - 2"); System.out.println(board.getUser().getId()); System.out.println("start - 3"); System.out.println(board.getUser().getUsername()); } }
 

[ 컨트롤러 ]

@GetMapping("/board/{id}") public String detail(@PathVariable Integer id, HttpServletRequest request) { Board board = boardRepository.findByIdJoinUser(id); request.setAttribute("board", board); return "board/detail"; }
 

[ detail.mustache ]

notion image
💡
board 안에 user 객체가 있으니까 DTO없이 타고 타고 넘어가서 username 을 꺼내 쓰면 된다.
 

[ 화면 확인 ]

notion image
💡
작성자도 잘 나옴~
 
Share article

codingb