
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