61. 댓글 목록 보기

박선규's avatar
Mar 25, 2024
61. 댓글 목록 보기
 
 

양방향이랑 정방향 차이

📌
fk 기준으로 본다.
  1. 정방향 맵핑 한 방향으로만 연관 관계가 정의되는 것을 말한다. 즉, 한 객체가 다른 객체를 참조할 수 있지만, 반대 방향으로는 참조가 이루어지지 않는다. (반대 방향으로의 접근이 필요할 때는 제약)
  1. 양방향 맵핑 두 객체가 서로를 참조하는 관계를 말한다. 이는 한 객체를 통해 연관된 다른 객체에 접근할 수 있으며, 반대 방향에서도 동일하게 접근할 수 있다. (관계의 주인을 관리하고, 불필요한 업데이트를 피하기 위해 추가적 주의가 필요) 정방향:BOARD랑 User (user의 board_id가 없음 board의 user_id가있음) 양방향:BOARD랑 Reply (서로서로 fk를 가지고있음)
 
전략
📎
1. One관계는 조인하고, Many관계는 Lazy Loading 한다 (eager이 디폴트 전략) 2. One 관계는 조인하고, Many관계를 페이징해야 된다면, 직접 쿼리를 만들어서 두번 조회한다. (lazy가 디폴트 전략) 3. One관계와 Many관계를 한방에 조인한다. 현재 1번으로 진행한다.
 
 

양방향 매핑

댓글 테이블 만들기

package shop.mtcoding.blog.reply; import jakarta.persistence.*; import lombok.Builder; import lombok.Data; import lombok.NoArgsConstructor; import org.hibernate.annotations.CreationTimestamp; import shop.mtcoding.blog.board.Board; import shop.mtcoding.blog.user.User; import java.sql.Timestamp; @NoArgsConstructor @Data @Table(name = "reply_tb") @Entity public class Reply { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) private Integer id; private String comment; @ManyToOne(fetch = FetchType.LAZY) private User user; @ManyToOne(fetch = FetchType.LAZY) private Board board; @CreationTimestamp private Timestamp createdAt; @Transient private boolean isReplyOwner; @Builder public Reply(Integer id, String comment, User user, Board board, Timestamp createdAt) { this.id = id; this.comment = comment; this.user = user; this.board = board; this.createdAt = createdAt; } }
 

댓글 Service

// 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); board.getReplies().forEach(reply -> { boolean isReplyOwner = false; if(sessionUser != null){ if(reply.getUser().getId() == sessionUser.getId()){ isReplyOwner = true; } } reply.setReplyOwner(isReplyOwner); }); return board; }

댓글 뷰

{{> layout/header}} <div class="container p-5"> {{#board.isOwner}} <!-- 수정삭제버튼 --> <div class="d-flex justify-content-end"> <a href="/board/{{board.id}}/update-form" class="btn btn-warning me-1">수정</a> <form action="/board/{{board.id}}/delete" method="post"> <button class="btn btn-danger">삭제</button> </form> </div> {{/board.isOwner}} <div class="d-flex justify-content-end"> <b>작성자</b> : {{board.user.username}} </div> <!-- 게시글내용 --> <div> <h2><b>{{board.title}}</b></h2> <hr/> <div class="m-4 p-2"> {{board.content}} </div> </div> <!-- 댓글 --> <div class="card mt-3"> <!-- 댓글등록 --> <div class="card-body"> <form action="/reply/save" method="post"> <textarea class="form-control" rows="2" name="comment"></textarea> <div class="d-flex justify-content-end"> <button type="submit" class="btn btn-outline-primary mt-1">댓글등록</button> </div> </form> </div> <!-- 댓글목록 --> <div class="card-footer"> <b>댓글리스트</b> </div> <div class="list-group"> {{#board.replies}} <!-- 댓글아이템 --> <div class="list-group-item d-flex justify-content-between align-items-center"> <div class="d-flex"> <div class="px-1 me-1 bg-primary text-white rounded">{{user.username}}</div> <div>{{comment}}</div> </div> <form action="/reply/{{id}}/delete" method="post"> <button class="btn">🗑</button> </form> </div> {{/board.replies}} </div> </div> </div> {{> layout/footer}}

1. Board

1 : N 관계에서는 엔포드(N이 foreignkey 드라이빙 테이블) 이기 때문에 필드가 생성은 되면 안됨
OneToMay 어노테이션을 사용하여 양방향 매핑
notion image
ManyToOne은 EAGER이 default 전략이고, OneToMany는 Lazy가 default 전략이다.
package shop.mtcoding.blog.board; import com.fasterxml.jackson.annotation.JsonIgnore; import com.fasterxml.jackson.annotation.JsonIgnoreProperties; import jakarta.persistence.*; import lombok.Builder; import lombok.Data; import lombok.NoArgsConstructor; import org.hibernate.annotations.CreationTimestamp; import shop.mtcoding.blog.reply.Reply; import shop.mtcoding.blog.user.User; import java.sql.Timestamp; import java.util.ArrayList; import java.util.List; @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; @OneToMany(mappedBy = "board", fetch = FetchType.LAZY)//양방향 매핑 private List<Reply> replies = new ArrayList<>(); //댓글이 없으면 null 값이 들어가 터질 수 있어서 new 해서 댓글 박스를 만들어놓기 @Transient // 테이블 생성이 안됨 private boolean isOwner; @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; } }
 
 
 
 
 

3가지 전략의 구체적인 방식

📎
원관계 :1:1의 관계일 때 표현 방식
매니 관계 :1:N 관계일 때 표현 방식
 
1번 전략 (원관계는 조인하고 매니 관계는 lazy Loding하기 → DTO 담기)
📎
관점:상세페이지 지금 U 1, b 1, r n 이 필요한 상태다. 전략(1)(eager 디폴트 전략)
원관계 B 관계에서 봤을 때 user가 1 (객체 하나를 잡고
매니관계 A객체와 C객체가 1:N개의 관계(BOARD, REPLY) 게시글 상세페이지의 관점에서 봤을 때 USER랑 BOARD랑 원관계가 된다.
package shop.mtcoding.blog.board; import org.springframework.data.jpa.repository.JpaRepository; import org.springframework.data.jpa.repository.Query; import org.springframework.data.repository.query.Param; import java.util.Optional; public interface BoardJPARepository extends JpaRepository<Board, Integer> { // 통신 2 회 인데 12번 (LAZY Loading 을 추천, 이걸 사용하려면 그냥 OneToMany 하나만 만들어 주면 된다) // 지금은 이거 사용할 거! @Query("select b from Board b join fetch b.user u where b.id = :id") Optional<Board> findByIdJoinUser(@Param("id") int id); // // 통신 1 회 인데 28번 // @Query("select b from Board b join fetch b.user u left join fetch b.replies r where b.id = :id") // Optional<Board> findByIdJoinUserAndReplies(@Param("id")int id); }
 
 
 
 
 
 
 
전략(2)(lazy 디폴트 전략)
Many 관계를 양방향 매핑하기
역방향의 필드를 적고 조회 할때 담는 용도로만 쓴다. @OneToMany 달아주기 보드라는 필드명이 (entity 객체의 변수명 ==fk의 주인)
notion image
(컬렉션은 빌더를 허용 안하기 때문에 혹여나 빌더에 넣지 않기!)
 
직접 조회 2번 하면 페이징도 할 수 있다.(limit도 할 수 있기 때문에) 전략(3) 조인을 한방에 해버린다
@Query("select b from Board b join fetch b.user u join fetch b.replies r where b.id = :id") Optional<Board> findByIdJoinUserAndReplies(@Param("id")int id);

 

오픈인 뷰

📌
Spring에서의 Open-In-View(OSIV)는 세션 당 요청(Session per request)이라는 트랜잭션 패턴의 구현체이다. - Open-In-View : false DB를 통해 조회된 영속성 컨텍스트(Persistence Context)는 트랜잭션이 종료되는 순간 커넥션이 종료된다. - Open-In-View : true (디폴트값) DB를 통해 조회된 영속성 컨텍스트(Persistence Context)는 트랜잭션이 종료되어도 커넥션이 종료되지 않고, 클라이언트에게 응답이 된 이후 종료된다.
 

Open-In-View : true

notion image
notion image
 

- Open-In-View : false

notion image
 
notion image
 
 
notion image
 
📎
Connection 이 request 요청시에 만들어지고, response가 될때 사라진다. open in view를 끄면 connection 이 request 요청시에 만들어지고, 서비스 종료시에 사라진다. 그래서 view rendering 시에 lazy loading이 되지 않는다.
 
notion image
Share article

p4rksk