게시판 만들기 - 마무리

Feb 02, 2024
게시판 만들기 - 마무리

1. 현재 상황

notion image
 
  1. get요청 시
  • 쿼리 스트링은 전부 where절에 정보를 달아 보내는 것
GraphQL : 페이스북에서 개발된 쿼리
  • where절에서도 어떤 컬럼을 조회할 것인지 내가 정할 수 있음
  • 프라이머리 키나 유니크한 키는 쿼리 스트링에 달지말자
그게 아닌 것들은 쿼리 스트링으로 질의하면 됨
  • 주소 뒤에 바로 테이블 명(별칭?)뒤에 붙임
  • body 데이터 : x-www-form-urlencoded
notion image
 

3. 뭐든 넣어도 실행시키려면 변수화

@GetMapping("/board1") // 1이 프라이머리키 public String detail() { return "board/detail"; } @GetMapping("/board/{id}") // 뭐든 넣어도 실행시키려면 변수화시켜서 {} public String detail() { return "board/detail"; } @GetMapping("/board/{id}") // 1이 프라이머리키 -> 뭐든 넣어도 실행시키려면 변수화시켜서 {} public String detail(@PathVariable int id) { // 파싱하게 치환해서 알려줌 return "board/detail"; }
notion image
 

4. 디자인 구현하기

notion image
<!-- 수정삭제버튼 --> <div class="d-flex justify-content-end"> <button class="btn btn-warning me-1">수정</button> <button class="btn btn-danger">삭제</button> </div> <div> 작성자 : ssar </div>
notion image
<!-- 수정삭제버튼 --> <div class="d-flex justify-content-end"> <button class="btn btn-warning me-1">수정</button> <button class="btn btn-danger">삭제</button> </div> <div class="d-flex justify-content-end mt-2"> <b>작성자 : ssar</b> </div>
notion image
 
  • 프라이머리 키도 가져와야 함
눈에 안 보이는 것들을 처리해야 함 → 프라이머리 키를 비교해서 수정 삭제 버튼 활성화 여부 결정 가능
notion image
 
  • 작성자가 없음
join해서 데이터를 가져오거나 아니면 스칼라 서브 쿼리를 사용해야 함
notion image
 
  • 게시글을 쓴 사람 중에 user 정보가 없는 것은 있을 수 없기에 inner join 사용
inner join : 공통된 데이터만 가지고 옴
게시글에 댓글, 조아요가 없을 수 있는 것은 outter join 사용
outter join : 공통된 데이터가 아니여도 가지고 옴
notion image
notion image
 

5. 데이터를 골라내서 전달해야 함

담을 repository 만들어야 함
select * from board_tb bt inner join user_tb ut on bt.user_id=ut.id where bt.id=4; //눈에는 안보이지만 줄을 바꿀 때마다 \n이 다 있는 것
notion image
  • 띄워쓰기 필요!
눈에는 안보이지만 줄을 바꿀 때마다 \n이 다 있는 것
  • Entity가 아닌 것은 파싱 안 해줌
조인했으니까 Entity가 아님
  • 엔티티가 아닌 것 받기
(1) 직접 ResultSet으로 만들어서 내가 하나하나 커서를 내리면서 파싱해야 함
(2) QLRM 라이브러리 사용하기
notion image
notion image
notion image
notion image
notion image
 
  • 프라이머리 키는 1개니까 getSingleResult로 받음
Object[] rs = (Object[]) query.getSingleResult(); in id= (int) rs[0] // 오브젝트니까 int로 다운캐스팅 해야함 String content = (String) rs[1];
컬럼 하나 하나의 배열(타입)을 모름 → Object 사용
오브젝트 배열의 모임 : row(행)
Entitysms 직접 테이블을 만들어줬으니까 다 알아서 해주는 것
Object 배열 타입을 리턴
→ 0번지에 id 1번지 content 3번지 createdat 4번지 title이 담김
 
  • 통신에서 만든 데이터는 다 DTO
스프링이 요청해서 주는 DTO는 응답 DTO
 

6. @JpaResultMapper

: JPA 네이티브 쿼리를 사용할 때,
결과를 Entity가 아닌 DTO로 매핑하기 위한 라이브러리 또는 유틸리티
  • DTO 만들 때 풀 생성자 (all-args constructor)를 제공
> 객체를 생성 > 셋터(setter) 메서드를 사용하여 필드 값을 설정
  • HttpServletRequest? 디스패처 서블릿이?해서 톰캣이 꺼내줌????
포문을 돌리게 되면 반복문 안에 ?
 
package shop.mtcoding.blog.board; import jakarta.persistence.EntityManager; import jakarta.persistence.Query; import lombok.RequiredArgsConstructor; import org.qlrm.mapper.JpaResultMapper; import org.springframework.stereotype.Repository; import shop.mtcoding.blog._core.Constant; import java.util.List; @RequiredArgsConstructor @Repository public class BoardRepository { private final EntityManager em; // jpa가 제공해줌 // 조회니까 트랜잭션 필요없음 public List<Board> findAll(int page) { int value = page * Constant.PAGING_COUNT; Query query = em.createNativeQuery("select * from board_tb order by id desc limit ?,?", Board.class); query.setParameter(1, value); query.setParameter(2, Constant.PAGING_COUNT); List<Board> boardList = query.getResultList(); return boardList; } // 이 결과를 리퀘스트에 담고 뷰 화면 가서 뿌리기 public int findBoardTotalCount() { Query query = em.createNativeQuery("select count(*) from board_tb"); Long boardTtalCount = (Long) query.getSingleResult(); return boardTtalCount.intValue(); } public BoardResponse.DetailDTO findById(int id) { // 조인해서 응답 // JpaResultMapper가 헷갈리지 않게 필요한 컬럼명을 적어주기 Query query = em.createNativeQuery("select bt.id, bt.title, bt.content, bt.created_at, bt.user_id, ut.username from board_tb bt inner join user_tb ut on bt.user_id = ut.id where bt.id = ?"); query.setParameter(1, id); JpaResultMapper rm = new JpaResultMapper(); // 컬럼명을 보고 매핑을 해줌 BoardResponse.DetailDTO responseDTO = rm.uniqueResult(query, BoardResponse.DetailDTO.class);// pk니까 한개 - 오브젝트(테이블)로 받음 return responseDTO; } }
package shop.mtcoding.blog.board; import lombok.AllArgsConstructor; import lombok.Data; import java.sql.Timestamp; public class BoardResponse { @AllArgsConstructor @Data // bt.id, bt.content, bt.title, bt.created_at, bt.user_id, ut.username public static class DetailDTO { private Integer id; private String title; private String content; private Timestamp createdAt; private Integer userId; private String username; } }
package shop.mtcoding.blog.board; import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpSession; import lombok.RequiredArgsConstructor; import org.springframework.stereotype.Controller; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.RequestParam; import shop.mtcoding.blog._core.PagingUtil; import java.util.List; @RequiredArgsConstructor @Controller public class BoardController { private final HttpSession session; private final BoardRepository boardRepository; // http://localhost:8080?page=0 @GetMapping({"/", "/board"}) public String index(HttpServletRequest request, @RequestParam(defaultValue = "0") int page) { //System.out.println("페이지: "+page); List<Board> boardList = boardRepository.findAll(page); request.setAttribute("boardList", boardList); // 가방에 담음 int currentPage = page; int nextPage = currentPage + 1; int prevPage = currentPage - 1; request.setAttribute("nextPage", nextPage); request.setAttribute("prevPage", prevPage); // 이것만 담으면 disable 못함 // 현재 페이지가 퍼스트인지 라스트인지 만든다. boolean first = PagingUtil.isFirst(currentPage); boolean last = PagingUtil.isLast(currentPage, 4); request.setAttribute("first", first); request.setAttribute("last", last); return "index"; } @GetMapping("/board/saveForm") public String saveForm() { return "board/saveForm"; } // 상세보기시 호출 @GetMapping("/board/{id}") // 1이 프라이머리키 -> 뭐든 넣어도 실행시키려면 변수화시켜서 {} public String detail(@PathVariable int id, HttpServletRequest request) { // 파싱하게 치환해서 알려줌 BoardResponse.DetailDTO reponseDTO = boardRepository.findById(id); request.setAttribute("board", reponseDTO); // 키를 통해 값을 찾음 return "board/detail"; // 포워드 발동 } }
{{> /layout/header}} <div class="container p-5"> <!-- 수정삭제버튼 --> <div class="d-flex justify-content-end"> <button class="btn btn-warning me-1">수정</button> <button class="btn btn-danger">삭제</button> </div> <div class="d-flex justify-content-end mt-2"> <b>작성자</b> : {{board.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"> <!-- 댓글아이템 --> <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">cos</div> <div>댓글 내용입니다</div> </div> <form action="/reply/1/delete" method="post"> <button class="btn">🗑</button> </form> </div> <!-- 댓글아이템 --> <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">ssar</div> <div>댓글 내용입니다</div> </div> <form action="/reply/1/delete" method="post"> <button class="btn">🗑</button> </form> </div> </div> </div> </div> {{> /layout/footer}}
notion image
 
Share article
RSSPowered by inblog