
1. 현재 상황

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

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";
    }
4. 디자인 구현하기

<!-- 수정삭제버튼 -->
    <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>
<!-- 수정삭제버튼 -->
    <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>
- 프라이머리 키도 가져와야 함
 
       눈에 안 보이는 것들을 처리해야 함
      → 프라이머리 키를 비교해서 수정 삭제 버튼 활성화 여부 결정 가능

- 작성자가 없음
 
       join해서 데이터를 가져오거나 아니면 스칼라 서브 쿼리를 사용해야 함

- 게시글을 쓴 사람 중에 user 정보가 없는 것은 있을 수 없기에 inner join 사용
 
       inner join : 공통된 데이터만 가지고 옴
       게시글에 댓글, 조아요가 없을 수 있는 것은 outter join 사용
       outter join : 공통된 데이터가 아니여도 가지고 옴


5. 데이터를 골라내서 전달해야 함
     담을 repository 만들어야 함
select *
from board_tb bt inner join user_tb ut
on bt.user_id=ut.id
where bt.id=4; //눈에는 안보이지만 줄을 바꿀 때마다 \n이 다 있는 것
- 띄워쓰기 필요!
 
      눈에는 안보이지만 줄을 바꿀 때마다 \n이 다 있는 것
- Entity가 아닌 것은 파싱 안 해줌
 
       조인했으니까 Entity가 아님
- 엔티티가 아닌 것 받기
 
       (1) 직접 ResultSet으로 만들어서 내가 하나하나 커서를 내리면서 파싱해야 함
       (2) QLRM 라이브러리 사용하기





- 프라이머리 키는 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}}
Share article