[ 학습 목표 ] 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 전략의 종류
EAGER = 연관된 객체를 들고와 LAZY = 내꺼만 들고와
게시글 상세보기 테스트 (EAGER 일 때 → JOIN이 일어남)
@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); } }
EAGER로 하니까 join이 일어났네! User도 모두 select 하네!
left join은 연산이 많이 일어나서 별루…. 성능이…
이렇게 일어나는게 아니라고!! JOIN이 일어나고, User를 죄다 받아옴
왜냐? fetch가 EAGER이라서!!
게시글 상세보기 쿼리 작성 (LAZY → JOIN 일어나지 않음!)
fetch전략을 LAZY로 바꾸고 다시 test를 돌려봤더니...
딱 필요한 것만 들고 왔네!! = 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"); } }
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번 일어난다. } }
이게 바로 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 안 적은 버전 테스트 ]
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 적은 버전 테스트 ]
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 ]
board 안에 user 객체가 있으니까 DTO없이 타고 타고 넘어가서 username 을 꺼내 쓰면 된다.
[ 화면 확인 ]
작성자도 잘 나옴~
Share article