
우리가 선택한 전략
One 관계는 조인하고, Many 관계는 Lazy Loading 한다
[ 양방향 매핑 ]
@NoArgsConstructor
@Data
@Table(name = "board_tb")
@Entity
public class Board {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Integer id;
private String title;
private String content;
//@JoinColumn(name = "user_id")
@ManyToOne(fetch = FetchType.LAZY)
private User user; // db -> user_id
@CreationTimestamp // pc -> db (날짜주입)
private Timestamp createdAt;
@OrderBy("id desc")
@OneToMany(mappedBy = "board", fetch = FetchType.LAZY, cascade = CascadeType.REMOVE) // Entity 객체의 변수명 == FK의 주인
private List<Reply> replies = new ArrayList<>();
@Transient // 테이블 생성이 안됨
private boolean isBoardOwner;
@Builder
public Board(Integer id, String title, String content, User user, Timestamp createdAt) {
this.id = id;
this.title = title;
this.content = content;
this.user = user;
this.createdAt = createdAt;
}
}
@OneToMany 걸어줬다.
@OrderBy(”id desc”) 하면 댓글도 내림차순 가능!
[ 비어있는데 왜 new를 할까? ]

나중에 댓글 for문 돌릴때 new를 안해놓으면 터짐.
왜냐면 null을 for문 돌리는 거니까... new 해주는게 좋음!!
(json은 빈 배열은 replies : [] 이런식으로 만들어줄 것임)
[ BoardService ]
// board, isOwner
public Board 글상세보기(int boardId, User sessionUser) {
Board board = boardJPARepository.findByIdJoinUser(boardId)
.orElseThrow(() -> new Exception404("게시글을 찾을 수 없습니다"));
//게시글 주인 여부
boolean isBoardOwner = false;
if(sessionUser != null){
if(sessionUser.getId() == board.getUser().getId()){
isBoardOwner = true;
}
}
board.setBoardOwner(isBoardOwner);
//레이지 로딩
//댓글은 forEach문 돌리기 (N이니까)
board.getReplies().forEach(reply -> {
boolean isReplyOwner = false;
//댓글 주인 여부
if(sessionUser != null){
if(reply.getUser().getId() == sessionUser.getId()){
isReplyOwner = true;
}
}
reply.setReplyOwner(isReplyOwner);
});
return board;
}
[ 설명 ]
댓글의 목록을 순회하면서,
각 댓글이 현재 세션 사용자(sessionUser)에 의해 작성되었는지를 확인하고,
그 결과를 바탕으로 댓글이 '소유자'에 의해 작성되었는지 여부(isReplyOwner)를 설정!
-> 현재 세션의 사용자가 댓글의 작성자인지 확인해야 함! (삭제나 수정 버튼 표시 때문)
-> isReplyOwner 변수에 true 또는 false를 설정 하고, set으로 담는다.
이 모든 처리가 완료된 board 객체를 return 구문을 통해 메소드를 호출한 곳으로 반환.
반환된 board 객체에는 게시글의 상세 정보, 게시글 주인 여부,
각 댓글과 댓글 주인 여부 등의 정보가 포함되어 있으며,
이 정보로 게시글 상세 페이지를 렌더링하는 데 사용함.
[ BoardController ] → Lazy Loading 테스트?
@RequiredArgsConstructor
@Controller
public class BoardController {
private final BoardService boardService;
private final BoardRepository boardRepository;
private final HttpSession session;
@GetMapping("/board/{id}")
public String detail(@PathVariable Integer id, HttpServletRequest request) {
User sessionUser = (User) session.getAttribute("sessionUser");
Board board = boardService.글상세보기(id, sessionUser);
request.setAttribute("board", board);
//이 로고가 찍히면서 레이지 로딩이 될 것임
System.out.println("서버 사이드 랜더링 직전에는 Board와 User만 조회된 상태이다~~~~~~");
return "board/detail";
}
join user만 했죠? = join fetch 했죠?
= 게시글(Board)을 데이터베이스에서 조회할 때,
게시글을 작성한 사용자(User)의 정보도 함께 조회(join) 했죠?

[ detail.mustache ]

레이지 로딩이 일어나면서 user가 select 될 것임 이렇게 user.name처럼 머스태치에 뿌리면서 레이지 로딩을 발동시키면 됨! 서비스에서 레이지 로딩 발동시켜도 되지만... 지금은 머스태치에서 발동시키자!
[ 홈페이지의 상세보기에 들어가 보자 ]

댓글 조회 되는 것 확인! (쓰레기통도!)
Hibernate:
select
b1_0.id,
b1_0.content,
b1_0.created_at,
b1_0.title,
u1_0.id,
u1_0.created_at,
u1_0.email,
u1_0.password,
u1_0.username
from
board_tb b1_0
join
user_tb u1_0
on u1_0.id=b1_0.user_id
where
b1_0.id=?
Hibernate:
select
r1_0.board_id,
r1_0.id,
r1_0.comment,
r1_0.created_at,
r1_0.user_id
from
reply_tb r1_0
where
r1_0.board_id=?
서버 사이드 랜더링 직전에는 Board와 User만 조회된 상태이다~~~~~~
//댓글을 적은 user
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 (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
//디폴트 배치 사이즈가 아니었으면 select 2번 (유저 수만큼) 날아감
우리가 선택한 전략은 이런 식으로 쿼리가 발동한다.
[ 그런데 이게 뭐지? open-in-view? ]


→ open-in-view 설명 보러가기!
레이지 로징을 신경 안 쓰려면 조인을 하자!
우리 2차 프로젝트에선 오픈 인 뷰 끄고 할거임
DTO 만들어서 .get ~ 이런식으로… 레이지 로딩 할 것! 제이슨 데이터로!
그래서 원래는 필요 없지만… 일단 지금은 true로 설정하자!
Share article