[Spring Boot] 39. 스프링부트 블로그 v2 (JPA) (9) 게시글 상세보기 (+Join Query)

김주희's avatar
Apr 06, 2025
[Spring Boot] 39. 스프링부트 블로그 v2 (JPA) (9) 게시글 상세보기 (+Join Query)

1. 게시글 상세보기 화면

notion image
 

2. 게시글 상세보기

1. BoardController

@GetMapping("/board/{id}") public String detail(@PathVariable("id") int id, HttpServletRequest request) { Board board = boardService.글상세보기(id); request.setAttribute("model", board); return "board/detail"; }
 

2. BoardService

public Board 글상세보기(int id) { // Board board = boardRepository.findById(id); // LAZY 로딩이므로 Board만 조회해서 board 정보 밖에 없다. // board.getUser().getEmail(); // 원래 null인데 lazy로딩이 발동해서 해당 유저 id로 select가 발동해서 값을 넣어준다. -> 비효율적이므로 안쓴다! return boardRepository.findByIdJoinUser(id); }
 

3. BoardRepository

public Board findByIdJoinUser(Integer id) { // Board를 조회할건데 User를 join // 객체지향쿼리 - board 객체 안에 있는 user와 join(relation해서 join) 해야하므로 User가 아닌 b.user과 join해야한다. // select b : board에 있는 필드만 projection // join fetch : b.user 안에 있는 것까지 전부 projection Query query = em.createQuery("select b from Board b join fetch b.user u where b.id = :id", Board.class); // inner join의 경우 pk, fk 연결 시 on절 생략 가능 query.setParameter("id", id); return (Board) query.getSingleResult(); }
 

4. board/detail.mustache

notion image
 
 

3. Join Query Test

0. BoardRepositoryTest

/* * 1. 쿼리가 어떻게 동작하는지 * 2. fetch가 없으면 * 3. b.user가 아니면 */ @Test public void findByIdJoinUser_test() { // given Integer boardId = 1; // when boardRepository.findByIdJoinUser(boardId); // eye }

1 . BoardRepositoryTest (1) - 쿼리가 어떻게 동작할까?

notion image
notion image
 

2 . BoardRepositoryTest (2) - fetch가 없으면 어떻게 동작할까?

notion image
notion image
 
 

3. BoardRepositoryTest (2) - b.user가 아니라 User로 join하면 어떻게 동작할까?

이때는 fetch가 있든 없든 동일한 에러가 발생한다!
 
notion image
notion image
notion image
 
 

4. 자기 게시글만 수정, 삭제 버튼이 보이도록 게시글 상세보기

notion image
→ 게시글 주인인지에 대한 새로운 필드가 하나 필요하다!
→ Board를 건들면 안됨 ⇒ DTO가 필요!
notion image
 
public class BoardResponse { @Data public static class DetailDTO { private Integer id; private String title; private String content; private Boolean isPublic; private Boolean isOwner; // 값이 안 들어갈 경우: Boolean - null / boolean - 0 private String username; // User 객체를 다 들고 갈 필요X private Timestamp createdAt; // model에 있는 것을 옮기는 것 // 깊은 복사 : 객체를 그대로 가져와서 getId 등으로 넣는게 낫다! public DetailDTO(Board board, Integer sessionUserId) { this.id = board.getId(); this.title = board.getTitle(); this.content = board.getContent(); this.isPublic = board.getIsPublic(); this.isOwner = sessionUserId == board.getUser().getId(); this.username = board.getUser().getUsername(); this.createdAt = board.getCreatedAt(); } } }
 
@GetMapping("/board/{id}") public String detail(@PathVariable("id") int id, HttpServletRequest request) { User sessionUser = (User) session.getAttribute("sessionUser"); BoardResponse.DetailDTO detailDTO = boardService.글상세보기(id, sessionUser.getId()); request.setAttribute("model", detailDTO); return "board/detail"; }
 
 
public BoardResponse.DetailDTO 글상세보기(int id, Integer userId) { // Board board = boardRepository.findById(id); // LAZY 로딩이므로 Board만 조회해서 board 정보 밖에 없다. // board.getUser().getEmail(); // 원래 null인데 lazy로딩이 발동해서 해당 유저 id로 select가 발동해서 값을 넣어준다. -> 비효율적이므로 안쓴다! Board board = boardRepository.findByIdJoinUser(id); BoardResponse.DetailDTO detailDTO = new BoardResponse.DetailDTO(board, userId); return detailDTO; }
 

5. 로그인하지 않은 상태로 게시글 상세보기를 할 경우

sessionUser가 null이 되므로 글상세보기(id, sessionUser.getId())중 sessionUser.getId()에서 터진다.
따라서 sessionUser가 null일 경우를 미리 처리하는 것이 좋다.
notion image
 
@GetMapping("/board/{id}") public String detail(@PathVariable("id") int id, HttpServletRequest request) { User sessionUser = (User) session.getAttribute("sessionUser"); Integer sessionUserId = (sessionUser == null) ? null : sessionUser.getId(); BoardResponse.DetailDTO detailDTO = boardService.글상세보기(id, sessionUserId); request.setAttribute("model", detailDTO); return "board/detail"; }
Share article

jay0628