
이러면 서버가 편안해진다
큰 회사 직접 CDN을 만든다!
2.0은 html이 서버에 요청하는데 분석을 서버에서 하고 받아야할 데이터 cs js등이 있는데 길 열어두고 한번 주고 한번 더 주는게 아닌 한번에 준다 물길 열어두고 리스펀스 길 열어두고 안 끊고 계속 준다!
면접 질문
만약 리로드 적혀있으면 땡 기본이 안되어있네 왜 ajax쓰는데 리로드 하냐? 물어봄
ajax 비동기 통신이요 라고 말하면 안된다 아니 정확하게요
비동기 통신해서 데이터만 받고 부분 리로딩하는 것
앗 실수했네요 하고 설명해야한다!
- 알아야 할 것
폼 태그 action 때리면 응답을 반드시 이 페이지 f5가 된다!! 중요
서버가 데이터 받는다 하면
리스트
<form action="/text/form" method="post">
<input type="text"name="username">
<button>고고</button>
</form>
컨트롤러
@PostMapping("/test/form")
public String form(){
return "user/join-form";
}
보이드면 어떻게 될까?
@PostMapping("/test/form")
public void form(){
}
테스트에 테스트에? 찾는다?
결과

이번에는
@PostMapping("/test/form")
public @ResponseBody String form(){
return "ok";
}
이러면 화면 ok됨

만약
@PostMapping("/test/form")
public @ResponseBody String form(){
return """
//아무 html코드를 넣는다면 어떻게 될까?
""";
}
읽어지겠지?
잘 생각해보자 탬플릿 엔진 쓰는 이유는? 여기 return해도되잖아?
왜 안하냐?
편해서
탬플릿 앤진 역할
여기에 html 자바 코드 적어 그러면 컨트롤러에서 보내줄께
서버가 응답해주는데 파일로 보내는게 더 편해서
DB요청하고 값 받기
BoardResponse.DetailDTO board = boardService.게시글상세보기(User.builder().id(1).build(), 1);
이렇게 적을 수 잇는데
타이틀에 넣으려면 “”” “”” 이렇게 적으면 불편하다
“””.replace(”{{}}”)
내가 직접 바인딩 하는 것 보다 탬플릿 앤진에서 하는게 더 편해서!
폼테그 요청하면 응답하는데
js에 응답이 아닌 브라우저에 응답되고 반드시 페이지 리로드된다!!
중요
폼테그로 댓글 하면 부분리로딩이 안된다! 페이지 리로딩이 돼서 ajax쓰는 이유가 없다!
detail 머스테치 변경 form태그에서 ajax로

name, submit변경
<div class="card mt-3">
<!-- 댓글등록 -->
<div class="card-body">
<form >
<textarea class="form-control" rows="2" id="comment"></textarea>
<div class="d-flex justify-content-end">
<button type="button" class="btn btn-outline-primary mt-1">댓글등록</button>
</div>
</form>
</div>
id필요해? DB에 있잖아
user필요해? session값 들고오면 되니까
전송하게 되면 해킹할 수 있다
만약 1번 유저인데 이거를 클라이언트가 적어? 나는 10번이라고 말 할 수 있다
Board 꼭 필요
즉
comment, Board id 꼭 필요!
<input type="hidden" value="{{sessionUser.id}} id="userId">
f12해보면 댓글 등록하는 곳에
userid가 1이라고 적힌다
이거는 누구나 변경할 수 있다 그래서 쓰면 안된다!! 반드시 히든X 세션으로 들거와야한다
이거는 괜찮음
<input type="hidden" value="{{sessionUser.id}}" id="boardId">

<form >
<input type="hidden" value="{{sessionUser.id}}" id="boardId">
<div data-boardId="{{model.id}}"></div>
<textarea class="form-control" rows="2" id="comment"></textarea>
<div class="d-flex justify-content-end">
<button type="button" class="btn btn-outline-primary mt-1">댓글등록</button>
</div>
</form>
이러면
하려고 하는 것
하려고 하는 것



boardid boardid 중복돼서 안됐음
다시 데이터를 집어넣는거

<input type="hidden" value="{{sessionUser.id}}" id="boardId">
<div id="boardId2" data-hello="{{model.id}}"></div>

let boardId = $("#boardId2").data("hello");
alert(boardId);
let boardId2 = document.querySelector("#boardId2").dataset.hello;
console.log(boardId2);
결과


{{>layout/header}}
<div class="container p-5">
<!-- 수정삭제버튼 -->
{{#model.isOwner}}
<div class="d-flex justify-content-end">
<a href="/api/board/{{model.id}}/update-form" class="btn btn-warning me-1">수정</a>
<form action="/api/board/{{model.id}}/delete" method="post">
<button class="btn btn-danger">삭제</button>
</form>
</div>
{{/model.isOwner}}
<div class="d-flex justify-content-end">
<b>작성자</b> : {{model.username}}
</div>
<!-- 게시글내용 -->
<div>
<h2><b>{{model.title}}</b></h2>
<hr/>
<div class="m-4 p-2">
{{{model.content}}}
</div>
</div>
<!-- 댓글 -->
<div class="card mt-3">
<!-- 댓글등록 -->
<div class="card-body">
<form >
<input type="hidden" value="{{sessionUser.id}}" id="boardId">
<div id="boardId2" data-hello="{{model.id}}"></div>
<textarea class="form-control" rows="2" id="comment"></textarea>
<div class="d-flex justify-content-end">
<button type="button" class="btn btn-outline-primary mt-1">댓글등록</button>
</div>
</form>
</div>
<!-- 댓글목록 -->
<div class="card-footer">
<b>댓글리스트</b>
</div>
<div class="list-group">
{{#model.replies}}
<!-- 댓글아이템 -->
<div id="reply-{{id}}" 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">{{username}}</div>
<div>{{comment}}</div>
</div>
{{#isOwner}}
<form>
<button onclick="deleteReply('{{id}}')" type="button" class="btn">🗑</button>
</form>
{{/isOwner}}
</div>
{{/model.replies}}
</div>
</div>
</div>
<script>
let boardId = $("#boardId2").data("hello");
alert(boardId);
let boardId2 = document.querySelector("#boardId2").dataset.hello;
console.log(boardId2);
async function deleteReply(id) {
//1. header + body
let response = await fetch(`/api/reply/${id}`, {
method: "delete"
});
// console.log(response);
//2. body(parsing)
let responseBody = await response.json();//파싱
// console.log(responseBody);
if (response.ok) {
$(`#reply-${id}`).remove();
//location.reload(); f5 때리는거
/*
f5때리면 fetch 안보임 새로고침 전의 것이기에 이럴꺼면 form쓰지
즉 다시 다 다운받는 거임 다시 다 다운받는게 아닌 캐싱하는게 맞다! css js 이런것 정적대이터는 변하지 않는다.
내가 캐싱하고있는건가? 공부해야한다!
나중에 이런 데이터들은 어떻게 사용하냐면 스프링 서버
*/
} else {
alert(responseBody.msg);
}
}
</script>
{{>layout/footer}}
시작
디테일 변경
{{>layout/header}}
<div class="container p-5">
<!-- 수정삭제버튼 -->
{{#model.isOwner}}
<div class="d-flex justify-content-end">
<a href="/api/board/{{model.id}}/update-form" class="btn btn-warning me-1">수정</a>
<form action="/api/board/{{model.id}}/delete" method="post">
<button class="btn btn-danger">삭제</button>
</form>
</div>
{{/model.isOwner}}
<div class="d-flex justify-content-end">
<b>작성자</b> : {{model.username}}
</div>
<!-- 게시글내용 -->
<div>
<h2><b>{{model.title}}</b></h2>
<hr/>
<div class="m-4 p-2">
{{{model.content}}}
</div>
</div>
<!-- 댓글 -->
<div class="card mt-3">
<!-- 댓글등록 -->
<div class="card-body">
<form >
<input type="hidden" value="{{model.id}}" id="boardId">
<textarea class="form-control" rows="2" id="comment"></textarea>
<div class="d-flex justify-content-end">
<button type="button" class="btn btn-outline-primary mt-1">댓글등록</button>
</div>
</form>
</div>
<!-- 댓글목록 -->
<div class="card-footer">
<b>댓글리스트</b>
</div>
<div class="list-group">
{{#model.replies}}
<!-- 댓글아이템 -->
<div id="reply-{{id}}" 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">{{username}}</div>
<div>{{comment}}</div>
</div>
{{#isOwner}}
<form>
<button onclick="deleteReply('{{id}}')" type="button" class="btn">🗑</button>
</form>
{{/isOwner}}
</div>
{{/model.replies}}
</div>
</div>
</div>
<script>
async function deleteReply(id) {
//1. header + body
let response = await fetch(`/api/reply/${id}`, {
method: "delete"
});
// console.log(response);
//2. body(parsing)
let responseBody = await response.json();//파싱
// console.log(responseBody);
if (response.ok) {
$(`#reply-${id}`).remove();
//location.reload(); f5 때리는거
/*
f5때리면 fetch 안보임 새로고침 전의 것이기에 이럴꺼면 form쓰지
즉 다시 다 다운받는 거임 다시 다 다운받는게 아닌 캐싱하는게 맞다! css js 이런것 정적대이터는 변하지 않는다.
내가 캐싱하고있는건가? 공부해야한다!
나중에 이런 데이터들은 어떻게 사용하냐면 스프링 서버
*/
} else {
alert(responseBody.msg);
}
}
</script>
{{>layout/footer}}
form태그에 값 넣어 보내고 싶으면 hidden사용한다! 눈에 보일 필요는 없는데 가지고 가야 하잖아
시작 댓글 쓰기
api만들어야 해서 리플라이컨트롤러 이동
꼭 boardid와 comment는 꼭 필요하다
@PostMapping("/api/reply")
public ResponseEntity<?> save(){
}
RequsetDTO만들어야 함
json방식은 save(String name) 이런거 값 못받음
package org.example.springv3.reply;
import lombok.Data;
public class ReplyRequest {
@Data
public static class SaveDTO{
private Integer boardId;
private String comment;
//toEntity만들어라!! 순수한 비영속 객체 만들어야 한다 ! insert할 때는 투 엔티티
public Reply toEntity(){
//리플리에 빌더 없음
}
}
}
빌더 없어서 Reply클래스에 빌더 만들어주기
package org.example.springv3.reply;
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
import jakarta.persistence.*;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
import org.example.springv3.board.Board;
import org.example.springv3.user.User;
import org.hibernate.annotations.CreationTimestamp;
import java.sql.Timestamp;
@Table(name = "reply_tb")
@Data
@NoArgsConstructor
@Entity
public class Reply {
@GeneratedValue(strategy = GenerationType.IDENTITY)
@Id
private Integer id;
@ManyToOne(fetch = FetchType.LAZY)
private User user;
@ManyToOne(fetch = FetchType.LAZY)
private Board board;
private String comment; // 댓글 내용
@CreationTimestamp//em.persist 할 때 발동
private Timestamp createdAt;
@Builder
public Reply(Integer id, User user, Board board, String comment, Timestamp createdAt) {
this.id = id;
this.user = user;
this.board = board;
this.comment = comment;
this.createdAt = createdAt;
}
}
다시 돌아가서
package org.example.springv3.reply;
import lombok.Data;
import org.example.springv3.board.Board;
import org.example.springv3.user.User;
public class ReplyRequest {
@Data
public static class SaveDTO{
private Integer boardId;
private String comment;
//원래는
/*
insert into reply_tb(comment, board_id, user_id, created_at) values('댓글1', 5, 1, now());
네이티브 쿼리 쓰면 insert끝나는데
jpa쓰려면 객체 만들어서
어자피 조회해야하지 않나? 그래서 board_id찾고 없으면 404 있으면 넣고
jpa는 객체를 퍼시스트한다!
*/
//toEntity만들어라!! 순수한 비영속 객체 만들어야 한다 ! insert할 때는 투 엔티티
public Reply toEntity(User sessionUser, Board board){
//리플리에 빌더 없음
return Reply.builder()
.comment(comment)
.user(null)
.board(null)
.build();
//보드 객체를 넣어야함
}
}
}
다시 리플리 컨트롤러
//ajax쓰고 있어서 save, delete 이런거 필요 없음 구분할거니까
//2개 받아야함!! 즉 reqeustDTO만들어야함
@PostMapping("/api/reply")
public ResponseEntity<?> save(@RequestBody ReplyRequest.SaveDTO saveDTO){
User sessionUser = (User) session.getAttribute("sessionUser");
replyService.댓글쓰기(saveDTO, sessionUser);
return ResponseEntity.ok(Resp.ok(null));
}
화면이 컨트롤러 요청 데이터 서비스한테 요청
세션은 save할 때 필요함!
alt enter하면 자동 생성

댓글 서비스 이동
public void 댓글쓰기(ReplyRequest.SaveDTO saveDTO, User sessionUser) {
//1. 게시글 존재 유무 확인
//2. 댓글 저장
replyRepository.save(null);
}
프라이머리키를 못 걸면 삭제를 못한다!
public Reply 댓글쓰기(ReplyRequest.SaveDTO saveDTO, User sessionUser) {
//1. 게시글 존재 유무 확인
Board boardPs = boardRepository.findById(saveDTO.getBoardId())
.orElseThrow(() -> new ExceptionApi404("게시글을 찾을 수 없습니다."));
//2. 비영속 댓글 객체 만들기
Reply reply = saveDTO.toEntity(sessionUser, boardPs); //여기까지는 비영속
//3. 댓글 저장
Reply replyPS= replyRepository.save(reply); //담궈지자 마자 insert 프라이머리키 생긴다
return replyPS;
}
replyPs도 되고
public Reply 댓글쓰기(ReplyRequest.SaveDTO saveDTO, User sessionUser) {
//1. 게시글 존재 유무 확인
Board boardPs = boardRepository.findById(saveDTO.getBoardId())
.orElseThrow(() -> new ExceptionApi404("게시글을 찾을 수 없습니다."));
//2. 비영속 댓글 객체 만들기
Reply reply = saveDTO.toEntity(sessionUser, boardPs); //여기까지는 비영속
//3. 댓글 저장
replyRepository.save(reply); //담궈지자 마자 insert 프라이머리키 생긴다
return reply;
}
reply 가능
비영속 객체가 ps 에 들어가서 바로 들어가니 바로 insert 되고 3번부터 reply가 된다!!
package org.example.springv3.reply;
import lombok.RequiredArgsConstructor;
import org.example.springv3.board.Board;
import org.example.springv3.board.BoardRepository;
import org.example.springv3.core.error.ex.ExceptionApi403;
import org.example.springv3.core.error.ex.ExceptionApi404;
import org.example.springv3.user.User;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
@Transactional(readOnly = true)
@RequiredArgsConstructor
@Service //컴포넌트 스켄 IOC에 넣기
public class ReplyService {
private final ReplyRepository replyRepository;
private final BoardRepository boardRepository;
@Transactional
public void 댓글삭제(int id, User sessionUser){
//애는 integer인지 어떻게 알았을까? 프라이머리키에 integer로 적어놔서 안다!
//jpa레파지토리 만들기 어렵지 않다 다음에 만들어보자! 궁금하다고 하면 알려주심 제내릭만 잘 알면 된다!
//삭제 전에 조회 먼저 하자
//ajax요청이니까 apiException터트려야 한다!! 받아줘야 하니까 앞에 reply에 PS 넣어줌
Reply replyPS = replyRepository.findById(id).orElseThrow(
() -> new ExceptionApi404("해당 댓글을 찾을 수 없습니다")
);
//지금 댓글만 조회할건데 User Board 안 내용은 비어있음 하지만 getUser.getId 포린키가 프라이머리키 들고있으니까 조회안함!!
// BoardId, User Id 는 이미 있음 래이지 로딩은 안 내용은 없지만 프라이머리키는 가지고 있다
//즉 select한번 더 할 필요 없다
// 하지만 getusername()하려면 select한번 더 해야함
//이렇게 하려면 mFindById만들어서 join하는 쿼리 만들고 1번만 select할 수 있게 만들면 된다!!
//findById하면 reply객체 만들어줌 id에 들어가고 comment들어가고 user, board에는 set id만 들어가 있고 안차있다!!
if(replyPS.getUser().getId() != sessionUser.getId()){
throw new ExceptionApi403("댓글 삭제 권한이 없습니다");
//이런 것 전부가 트랜잭션이다 그래서 @Transaction붙여야 한다
}
replyRepository.deleteById(id);
}
@Transactional
public Reply 댓글쓰기(ReplyRequest.SaveDTO saveDTO, User sessionUser) {
//1. 게시글 존재 유무 확인
Board boardPs = boardRepository.findById(saveDTO.getBoardId())
.orElseThrow(() -> new ExceptionApi404("게시글을 찾을 수 없습니다."));
//2. 비영속 댓글 객체 만들기
Reply reply = saveDTO.toEntity(sessionUser, boardPs); //여기까지는 비영속
//3. 댓글 저장(reply가 영속화됨)
replyRepository.save(reply); //담궈지자 마자 insert 프라이머리키 생긴다
return reply;
}
}
댓글 response 클래스만들기
csr로 쓰래기통 보이고 안보이고 할 지 생각해야함
내가 적은 글은 바로 내 거니까 무조건 true
package org.example.springv3.reply;
import lombok.Data;
public class ReplyResponse {
@Data
public static class DTO{
private Integer id;
private String comment;
private String username;
//깊은 복사 시작
public DTO(Reply reply) {
this.id = reply.getId();
this.comment = reply.getComment();
this.username = reply.getUser().getUsername();
}
}
}
리플라이 객체 안에 user객체가 없어 board객체도 없이 비워져 있으면
lazy로딩 걸림 서비스 딴에서 getter해서 가지고온다??? 이거 모르겠음…
댓글 서비스
package org.example.springv3.reply;
import lombok.RequiredArgsConstructor;
import org.example.springv3.board.Board;
import org.example.springv3.board.BoardRepository;
import org.example.springv3.core.error.ex.ExceptionApi403;
import org.example.springv3.core.error.ex.ExceptionApi404;
import org.example.springv3.user.User;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
@Transactional(readOnly = true)
@RequiredArgsConstructor
@Service //컴포넌트 스켄 IOC에 넣기
public class ReplyService {
private final ReplyRepository replyRepository;
private final BoardRepository boardRepository;
@Transactional
public void 댓글삭제(int id, User sessionUser){
Reply replyPS = replyRepository.findById(id).orElseThrow(
() -> new ExceptionApi404("해당 댓글을 찾을 수 없습니다")
);
if(replyPS.getUser().getId() != sessionUser.getId()){
throw new ExceptionApi403("댓글 삭제 권한이 없습니다");
}
replyRepository.deleteById(id);
}
@Transactional
public ReplyResponse.DTO 댓글쓰기(ReplyRequest.SaveDTO saveDTO, User sessionUser) {
//1. 게시글 존재 유무 확인
Board boardPs = boardRepository.findById(saveDTO.getBoardId())
.orElseThrow(() -> new ExceptionApi404("게시글을 찾을 수 없습니다."));
//2. 비영속 댓글 객체 만들기
Reply reply = saveDTO.toEntity(sessionUser, boardPs); //여기까지는 비영속
//3. 댓글 저장
replyRepository.save(reply); //담궈지자 마자 insert 프라이머리키 생긴다
return new ReplyResponse.DTO(reply);
}
}
리플리 컨트롤러 이동
@PostMapping("/api/reply")
public ResponseEntity<?> save(@RequestBody ReplyRequest.SaveDTO saveDTO){
User sessionUser = (User) session.getAttribute("sessionUser");
ReplyResponse.DTO replyDTO = replyService.댓글쓰기(saveDTO, sessionUser);
return ResponseEntity.ok(Resp.ok(replyDTO));
}
리뷰
ajax요청 api/reply 통해서
json데이터 받기 위해서는 DTO 받아서 @RequestBody를 붙여야 한다!
생각 해야 하는 것은 어떤 데이터를 클라이언트에게 받아야 하는가? (comment, boardId)
인증체크해야한다 (우리가 할 필요 없는게 API) 붙이면 되니까
유저 정보 받아와야 한다
insert할때 유저 정보가 필요해서 session에 받아서 댓글쓰기로 던진다!
서비스에서는 댓글 insert하고 프라이머리키 있는 애 가지고 오는 것
@Transactional
public ReplyResponse.DTO 댓글쓰기(ReplyRequest.SaveDTO saveDTO, User sessionUser) {
//1. 게시글 존재 유무 확인
Board boardPs = boardRepository.findById(saveDTO.getBoardId())
.orElseThrow(() -> new ExceptionApi404("게시글을 찾을 수 없습니다."));
//2. 비영속 댓글 객체 만들기
Reply reply = saveDTO.toEntity(sessionUser, boardPs); //여기까지는 비영속
//3. 댓글 저장
replyRepository.save(reply); //담궈지자 마자 insert 프라이머리키 생긴다
return new ReplyResponse.DTO(reply);
}
만약 jpa로 안 쓰면
toEntity 필요 없음
서비스에서
게시글 존재유무, 비영속 이런거 다 필요 없고
레파지토리에서
@Repository
public class ReplQueryRespository{
EntityManager em;
public void insert(ResplyRequest.SaveDTO saveDTO,, insert userId){
예전 쿼리 만들기
}
}
댓글쓰기할 때
private final ReplyQueryRepository repository;
replyQueryRepository.insert(saveDTO
빡시다 다시 select해야해서!
insert실행된다 해도 프라이머리키를 알 수 없다 그래서
public int maxId(){
Query query em.createNativeQuery("select max(id) from reply_tb", Long.class);
Long replyId = (Long)query.getSingleResult();
return replyId.intValue();
}
서비스에서 insert했고
밑에
int replyId = replyqueryRepository.maxId();
//리턴해줘야 한다! 예 트랜잭션 걸려있다 딴 놈은 못함 maxId하면 내가 isnert한 데이터 맞음
그럼 다시 db가서 조회해야 한다
또 finbyId 필요하다
Reply reply = replyQueryRepository.findById(replyId);
return new ReplyResponse.DTO(reply);
알아서 동기화 시키니까 내가 maxId 이런것 다 필요 없이 알아서 찾아주니까 좋다!
- 게시글 존재 유무확인
- 인서트하기
- MaxId 찾기
- MaxId로 댓글 조회하기
- DTO 응답
패치로 보내보자!
디테일 머스테치
{{>layout/header}}
<div class="container p-5">
<!-- 수정삭제버튼 -->
{{#model.isOwner}}
<div class="d-flex justify-content-end">
<a href="/api/board/{{model.id}}/update-form" class="btn btn-warning me-1">수정</a>
<form action="/api/board/{{model.id}}/delete" method="post">
<button class="btn btn-danger">삭제</button>
</form>
</div>
{{/model.isOwner}}
<div class="d-flex justify-content-end">
<b>작성자</b> : {{model.username}}
</div>
<!-- 게시글내용 -->
<div>
<h2><b>{{model.title}}</b></h2>
<hr/>
<div class="m-4 p-2">
{{{model.content}}}
</div>
</div>
<!-- 댓글 -->
<div class="card mt-3">
<!-- 댓글등록 -->
<div class="card-body">
<form>
<input type="hidden" value="{{model.id}}" id="boardId">
<textarea class="form-control" rows="2" id="comment"></textarea>
<div class="d-flex justify-content-end">
<button onclick="saveReply()" type="button" class="btn btn-outline-primary mt-1">댓글등록</button>
</div>
</form>
</div>
<!-- 댓글목록 -->
<div class="card-footer">
<b>댓글리스트</b>
</div>
<div class="list-group">
{{#model.replies}}
<!-- 댓글아이템 -->
<div id="reply-{{id}}" 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">{{username}}</div>
<div>{{comment}}</div>
</div>
{{#isOwner}}
<form>
<button onclick="deleteReply('{{id}}')" type="button" class="btn">🗑</button>
</form>
{{/isOwner}}
</div>
{{/model.replies}}
</div>
</div>
</div>
<script>
async function saveReply() {
//1. Reply 객체 만들기(ID로 찾아서)
//이거 그대로 전송 못해서 json으로 변경해서 보내야함
let reply = {
comment: $("#comment").val(),
boardId: $("#boardId").val()
};
//2. fetch 요청하기 메서드, 어떤 마임타입가지고 있는지(컨탠트 타입) get은 where절에 걸려고 쓰니까 쿼리 스트링이다
let response = await fetch(`/api/reply`, {
method: "post",
body: JSON.stringify(reply),
headers: {
"Content-Type": "application/json; charset=utf-8"
}
});
//파싱
let responseBody = await response.json(); // DTO!!
console.log(responseBody);
//3.CSR 하기
}
async function deleteReply(id) {
//1. header + body
let response = await fetch(`/api/reply/${id}`, {
method: "delete"
});
// console.log(response);
//2. body(parsing)
let responseBody = await response.json();//파싱
// console.log(responseBody);
if (response.ok) {
$(`#reply-${id}`).remove();
//location.reload(); f5 때리는거
/*
f5때리면 fetch 안보임 새로고침 전의 것이기에 이럴꺼면 form쓰지
즉 다시 다 다운받는 거임 다시 다 다운받는게 아닌 캐싱하는게 맞다! css js 이런것 정적대이터는 변하지 않는다.
내가 캐싱하고있는건가? 공부해야한다!
나중에 이런 데이터들은 어떻게 사용하냐면 스프링 서버
*/
} else {
alert(responseBody.msg);
}
}
</script>
{{>layout/footer}}

오류난게
package org.example.springv3.reply;
import lombok.Data;
import org.example.springv3.board.Board;
import org.example.springv3.user.User;
public class ReplyRequest {
@Data
public static class SaveDTO{
private Integer boardId;
private String comment;
//원래는
/*
insert into reply_tb(comment, board_id, user_id, created_at) values('댓글1', 5, 1, now());
네이티브 쿼리 쓰면 insert끝나는데
jpa쓰려면 객체 만들어서
어자피 조회해야하지 않나? 그래서 board_id찾고 없으면 404 있으면 넣고
jpa는 객체를 퍼시스트한다!
*/
//toEntity만들어라!! 순수한 비영속 객체 만들어야 한다 ! insert할 때는 투 엔티티
public Reply toEntity(User sessionUser, Board board){
//리플리에 빌더 없음
return Reply.builder()
.comment(comment)
.user(sessionUser)
.board(board)
.build();
//보드 객체를 넣어야함
}
}
}
디버깅 먼저 뿌려보기! 시스아웃
@Slyfj
- 디버깅해도 모르면
sout해서 어디 부분에서 잘못됐는지 확인 추적하기!!
- save 아래 빨간점 찍고 벌래 실행 벌래로 실행
스텝오버 f8
다시 재 실행actuator 옆 버튼 누르기
스탭인투 F7 메서드 내부로 들어가라!
스텝아웃 시프트 F8 빠져나오기
마지막 부분
로케이션 리로드
//어팬드? 프리팬드??? 찾아보자
어팬드 해야한다!!
{{>layout/header}}
<div class="container p-5">
<!-- 수정삭제버튼 -->
{{#model.isOwner}}
<div class="d-flex justify-content-end">
<a href="/api/board/{{model.id}}/update-form" class="btn btn-warning me-1">수정</a>
<form action="/api/board/{{model.id}}/delete" method="post">
<button class="btn btn-danger">삭제</button>
</form>
</div>
{{/model.isOwner}}
<div class="d-flex justify-content-end">
<b>작성자</b> : {{model.username}}
</div>
<!-- 게시글내용 -->
<div>
<h2><b>{{model.title}}</b></h2>
<hr/>
<div class="m-4 p-2">
{{{model.content}}}
</div>
</div>
<!-- 댓글 -->
<div class="card mt-3">
<!-- 댓글등록 -->
<div class="card-body">
<form>
<input type="hidden" value="{{model.id}}" id="boardId">
<textarea class="form-control" rows="2" id="comment"></textarea>
<div class="d-flex justify-content-end">
<button onclick="saveReply()" type="button" class="btn btn-outline-primary mt-1">댓글등록</button>
</div>
</form>
</div>
<!-- 댓글목록 -->
<div class="card-footer">
<b>댓글리스트</b>
</div>
<div class="list-group">
{{#model.replies}}
<!-- 댓글아이템 -->
<div id="reply-{{id}}" 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">{{username}}</div>
<div>{{comment}}</div>
</div>
{{#isOwner}}
<form>
<button onclick="deleteReply('{{id}}')" type="button" class="btn">🗑</button>
</form>
{{/isOwner}}
</div>
{{/model.replies}}
</div>
</div>
</div>
<script>
//리로드 안 하려고
function replyItem(reply) {
return ` <div id="reply-${reply.id}" 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">${reply.username}</div>
<div>${reply.comment}</div>
</div>
<form>
<button onclick="deleteReply('${reply.id}')" type="button" class="btn">🗑</button>
</form>
</div>`;
}
//어팬드? 프리팬드??? 찾아보자
async function saveReply() {
//1. Reply 객체 만들기(ID로 찾아서)
//이거 그대로 전송 못해서 json으로 변경해서 보내야함
let reply = {
comment: $("#comment").val(),
boardId: $("#boardId").val()
};
//2. fetch 요청하기 메서드, 어떤 마임타입가지고 있는지(컨탠트 타입) get은 where절에 걸려고 쓰니까 쿼리 스트링이다
let response = await fetch(`/api/reply`, {
method: "post",
body: JSON.stringify(reply),
headers: {
"Content-Type": "application/json; charset=utf-8"
}
});
//파싱
let responseBody = await response.json(); // DTO!!
console.log(responseBody);
//3.CSR 하기
//이거는 전체 리로드
//location.reload();
}
async function deleteReply(id) {
//1. header + body
let response = await fetch(`/api/reply/${id}`, {
method: "delete"
});
// console.log(response);
//2. body(parsing)
let responseBody = await response.json();//파싱
// console.log(responseBody);
if (response.ok) {
$(`#reply-${id}`).remove();
//location.reload(); f5 때리는거
/*
f5때리면 fetch 안보임 새로고침 전의 것이기에 이럴꺼면 form쓰지
즉 다시 다 다운받는 거임 다시 다 다운받는게 아닌 캐싱하는게 맞다! css js 이런것 정적대이터는 변하지 않는다.
내가 캐싱하고있는건가? 공부해야한다!
나중에 이런 데이터들은 어떻게 사용하냐면 스프링 서버
*/
} else {
alert(responseBody.msg);
}
}
</script>
{{>layout/footer}}
박스를 찾아야 하는데 없음 그래서 만들어준다
최종
{{>layout/header}}
<div class="container p-5">
<!-- 수정삭제버튼 -->
{{#model.isOwner}}
<div class="d-flex justify-content-end">
<a href="/api/board/{{model.id}}/update-form" class="btn btn-warning me-1">수정</a>
<form action="/api/board/{{model.id}}/delete" method="post">
<button class="btn btn-danger">삭제</button>
</form>
</div>
{{/model.isOwner}}
<div class="d-flex justify-content-end">
<b>작성자</b> : {{model.username}}
</div>
<!-- 게시글내용 -->
<div>
<h2><b>{{model.title}}</b></h2>
<hr/>
<div class="m-4 p-2">
{{{model.content}}}
</div>
</div>
<!-- 댓글 -->
<div class="card mt-3">
<!-- 댓글등록 -->
<div class="card-body">
<form>
<input type="hidden" value="{{model.id}}" id="boardId">
<textarea class="form-control" rows="2" id="comment"></textarea>
<div class="d-flex justify-content-end">
<button onclick="saveReply()" type="button" class="btn btn-outline-primary mt-1">댓글등록</button>
</div>
</form>
</div>
<!-- 댓글목록 -->
<div class="card-footer">
<b>댓글리스트</b>
</div>
<div class="list-group" id="reply-box">
{{#model.replies}}
<!-- 댓글아이템 -->
<div id="reply-{{id}}" 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">{{username}}</div>
<div>{{comment}}</div>
</div>
{{#isOwner}}
<form>
<button onclick="deleteReply('{{id}}')" type="button" class="btn">🗑</button>
</form>
{{/isOwner}}
</div>
{{/model.replies}}
</div>
</div>
</div>
<script>
//리로드 안 하려고
function replyItem(reply) {
return ` <div id="reply-${reply.id}" 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">${reply.username}</div>
<div>${reply.comment}</div>
</div>
<form>
<button onclick="deleteReply('${reply.id}')" type="button" class="btn">🗑</button>
</form>
</div>`;
}
//어팬드? 프리팬드??? 찾아보자
async function saveReply() {
//1. Reply 객체 만들기(ID로 찾아서)
//이거 그대로 전송 못해서 json으로 변경해서 보내야함
let reply = {
comment: $("#comment").val(),
boardId: $("#boardId").val()
};
//2. fetch 요청하기 메서드, 어떤 마임타입가지고 있는지(컨탠트 타입) get은 where절에 걸려고 쓰니까 쿼리 스트링이다
let response = await fetch(`/api/reply`, {
method: "post",
body: JSON.stringify(reply),
headers: {
"Content-Type": "application/json; charset=utf-8"
}
});
//파싱
let responseBody = await response.json(); // DTO!!
console.log(responseBody);
//3.CSR 하기
//이거는 전체 리로드
//location.reload();
//왜 바디인가? 리스펀스 바디 . 바디에 들어가 있기 때문에!!
$("#reply-box").prepend(replyItem(responseBody.body));
//공백 넣으려고
$("textarea").val("");
}
async function deleteReply(id) {
//1. header + body
let response = await fetch(`/api/reply/${id}`, {
method: "delete"
});
// console.log(response);
//2. body(parsing)
let responseBody = await response.json();//파싱
// console.log(responseBody);
if (response.ok) {
$(`#reply-${id}`).remove();
//location.reload(); f5 때리는거
/*
f5때리면 fetch 안보임 새로고침 전의 것이기에 이럴꺼면 form쓰지
즉 다시 다 다운받는 거임 다시 다 다운받는게 아닌 캐싱하는게 맞다! css js 이런것 정적대이터는 변하지 않는다.
내가 캐싱하고있는건가? 공부해야한다!
나중에 이런 데이터들은 어떻게 사용하냐면 스프링 서버
*/
} else {
alert(responseBody.msg);
}
}
</script>
{{>layout/footer}}
Share article