Part 2) 게시판 만들기 (3) - 리다이렉트 / update / delete / SQL 쿼리
6장 게시판 내 페이지 이동하기
리다이렉트(redirect)란?
클라이언트가 보낸 요청을 마친 후 계속해서 처리할 다음 요청 주소를 재지시하는 것.
분리된 기능을 하나의 연속적인 흐름으로 연결할 수 있다.
기존 서비스 문제점
1) 목록 페이지 : 새 글 작성 링크 X
2) 입력 페이지: 목록 페이지로 이동 X
새 글 작성 링크 만들어줌.
[ index.mustache ]
table 태그 밑에 a 태그 추가
</table>
<a href="/articles/new">New Article</a>
{{> layouts/footer}}
입력 페이지에 목록 페이지로 돌아가는 링크 추가
[ new.mustache ]
submit button 아래에 a 태그 추가
<button type="submit" class="btn btn-primary">Submit</button>
<a href="/articles">Back</a>
</form>
{{>layouts/footer}}
이렇게 back 링크가 생긴 것을 확인할 수 있다.
새로운 문제점 !!!
새 글을 작성하면 상세 페이지가 안나오고 에러 페이지가 뜨는 상황임!!
이럴 때는 리다이렉트를 이용하여, 방금 만든 글의 상세페이지가 뜨게 하면 된다.
[ ArticleController.java ]
return 값에 ' redirect:URL_주소 ' 를 입력해준다.
saved.getId() 는 id 값에 URL 주소가 달라지게 하기 위하여 위에 선언한 saved 객체를 saved.getId()로 호출해다.
이러면 articles/1 , aticles/2 ... 이런식으로 id 에 따른 URL 이 나온다.
+
getId() 메서드는 entity 패키지의 Atricle.java 에다가 게터 메서드를 만들어줘야한다.
간단하게 롬복으로 처리.
이렇게 getter 메서드를 선언해주던가.
class 위에 롬복으로 @Getter 어노테이션을 선언해준다.
submit 버튼을 누르면 article/2로 리다이렉트 된다.
상세페이지에서 목록 페이지로 이동하려면
[ show.mustache ]
</table>
<a href="/articles">go to Article List</a>
{{> layouts/footer}}
이렇게 a 태그를 추가해준다.
목록 페이지 -> 상세 페이지 이동하기. ( 제목을 클릭하면 상세 페이지로 이동 )
[ index.mustache ]
<tr>
<th>{{id}}</th>
<td><a href="/articles/{{id}}">{{title}}</a></td>
<td>{{content}}</td>
</tr>
title 부분을 a 태그로 감싸주면 끝!
1분 퀴즈
• ( ㉠ )(이)란 특정 페이지로 이동하는 HTML 태그로, 클릭 시 href 속성에 적힌 URL 주소로 요청을 보냅니다.
• ( ㉡ )(이)란 컨트롤러의 특정 메서드 수행을 종료한 후 계속해서 처리할 다음 요청 주소를 지시하는 것입니다. 이를 통해 분리된 기능을 연속적인 하나의 흐름으로 연결할 수 있습니다.
ㄱ : a 태그
ㄴ : redirect
7장 게시글 수정하기
1. 수정 페이지 만들고 기존 데이터 불러오기
[ show.mustache ]
{{#article}} {{/article}} 형식으로 범위를 지정하면 {{id}} 이렇게 입력하면 되지만,
범위가 지정되어있지 않을 때는 {{article.id}} 이렇게 해줘야한다.
[ ArticleController.java ]
edit() 메서드를 새로 추가해줬다.
@GetMapping("/articles/{id}/edit") // URL 요청 접수
public String edit(@PathVariable Long id, Model model) { // id 를 매개변수로 받아오기 & model 객체 받아오기
// 수정할 데이터 가져오기
Article articleEntity = articleRepository.findById(id).orElse(null); // DB에 수정할 데이터 가져오기. id 없으면 null
// 모델에 데이터 등록하기
model.addAttribute("article", articleEntity); // articleEntity를 article로 등록
// 뷰 페이지 설정
return "article/edit";
}
수정 페이지
[ edit.mustache ]
{{>layouts/header}}
{{#article}}
<form class="container" action="" method="post">
<div class="mb-3">
<label class="form-label">제목</label>
<input type="text" class="form-control" name="title" value="{{title}}"> <!-- name = "title" DTO의 title 필드와 연결-->
</div>
<div class="mb-3">
<label class="form-label">내용</label>
<textarea class="form-control" rows="3" name="content">{{content}}</textarea> <!-- DTO의 content 필드와 연결 -->
</div>
<button type="submit" class="btn btn-primary">Submit</button>
<a href="/articles/{{id}}" class="btn btn-dark">Back</a> <!-- 상세페이지로 다시 돌아가기 -->
</form>
{{/article}}
{{>layouts/footer}}
결과
[ 1분 퀴즈 ]
정답 : 1->3->4->2
2. 데이터를 수정해 DB에 반영한 후 결과를 볼 수 있게 <상세 페이지>로 리다이렉트 하기
전반적인 흐름은 다음과 같다.
클라이언트 - 서버 처리 기술
MVC | 서버 역할을 분담해 처리하는 기법 |
JPA | 서버와 DB 간 소통에 관여하는 기술 |
SQL | DB 데이터를 관리하는 언어 |
HTTP | 데이터를 주고받기 위한 통신 규약 |
MVC, JPA, SQL이 유기적으로 동작하는 것은 HTTP 의 요청과 응답으로 시작되고 끝이난다.
HTTP(HyperText Transfer Protocol)이란?
웹 서비스에서 사용하는 프로토콜이다. 클라이언트의 다양한 요청을 메서드를 통해 서버로 보내는 역할을 한다.
메서드로는 POST, GET, PATCH(PUT), DELETE 가 있다.
POST | 데이터 생성 요청 |
GET | 데이터 조회 요청 |
PATCH(PUT) | 데이터 수정 요청 |
DELETE | 데이터 삭제 요청 |
더미 데이터 (dummy data) 생성
[ data.sql ]
INSERT INTO article(id, title, content) VALUES (1, '가가가가', '1111');
INSERT INTO article(id, title, content) VALUES (1, '나나나나', '2222');
INSERT INTO article(id, title, content) VALUES (1, '다다다다', '3333');
더미 데이터가 들어간 것을 확인 할 수 있다.
이제 edit 페이지를 수정해 줄 것이다. 아까 form 태그에 action부분을 비워두었는데, 그 부분을 수정해줄 것이다.
method 를 POST 로 한 이유는 form 태그가 PATCH 메서드를 지원하지 않기 때문이다.
그리고 서버에서 id가 몇 번인지 알아야 하기 때문에 input 태그로 id도 추가해줬다.
ArticleController.java에 update 메서드 추가하기.
[ ArticleController.java ]
@PostMapping("/articles/update") // URL 요청 접수
public String update(ArticleForm form) { // 매개변수로 DTO 받아오기
log.info(form.toString());
// 1. DTO를 엔티티로 변환
Article articleEntity = form.toEntity(); //DTO(form)를 엔티티(articleEntity)로 변환하기
// 2. 엔티티를 DB에 저장
// 2-1 DB에서 기존 데이터 가져오기
Article target = articleRepository.findById(articleEntity.getId()).orElse(null);
// 2-2 기존 데이터 값 갱신
if (target != null) {
articleRepository.save(articleEntity); // target이 null 이 아니면 엔티티를 DB에 저장
}
// 3. 수정 결과 페이지로 리다이렉트하기
return "redirect:/articles/" + articleEntity.getId();
}
[ Article.java ]
+ DTO 에 id 필드 추가 및 생성자 수정
수정 시도
원래 데이터
수정 후
1분 퀴즈
[ 정답 ]
ㄱ: POST
ㄴ: 데이터 조회 ( Read )
ㄷ: UPDATE
8장 게시글 삭제하기 DELETE
글 삭제 순서
1. 클라이언트가 HTTP 메서드로 특정 게시글의 삭제 요청
2. 컨트롤러는 리포지터리를 통해 DB 데이터를 찾아 삭제 (기존 데이터가 있는 경우)
3. 삭제 완료 후 클라이언트를 결과 페이지로 리다이렉트
+ 결과 페이지에서 삭제 완료 메시지도 띄워 줄 예정.
이를 위한 클래스는 RedirectAttributes 이다.
RedirectAttributes 객체의 addFlashAttribute() 메서드는 리다이렉트된 페이지에서 사용할 일회성 데이터를 등록할 수 있음
1. Delete 버튼 추가
[ show.mustache ]
<a href="/articles/{{article.id}}/delete" class="btn btn-danger">DELETE</a>
2. Delete 요청을 받아서 삭제하기
HTTP 메서드를 통해서 삭제를 하려한다.
앞서 POST, GET, PATCH, DELETE 메서드가 있다고 했는데,
HTML 에서는 POST 와 GET 을 제외한 다른 메서드는 제공하지 않기 때문에 GET 방식으로 삭제 요청을 받아서
처리할 것이다.
ArticleController.java에 delete 메서드 추가하기.
[ ArticleController.java ]
@GetMapping("/articles/{id}/delete") // URL 요청 접수
public String delete(@PathVariable Long id) { // id를 매개변수로 가져오기
log.info("삭제 요청이 들어왔습니다.");
// 1. 삭제할 대상 가져오기
Article target = articleRepository.findById(id).orElse(null); // 데이터 찾기
// 2. 대상 엔티티 삭제하기
if (target != null) {
articleRepository.delete(target); // delete() 메서드로 대상 삭제하기
}
// 3. 결과 페이지로 리다이렉트하기
return "redirect:/articles";
}
위와 같이 delete 버튼을 누르면 삭제되는 것을 확인할 수 있다.
3. 삭제 완료 메시지 남기기
앞서 작성한 ArticleController.java 부분을 수정
@GetMapping("/articles/{id}/delete") // URL 요청 접수
public String delete(@PathVariable Long id, RedirectAttributes rttr) { // id를 매개변수로 가져오기
log.info("삭제 요청이 들어왔습니다.");
// 1. 삭제할 대상 가져오기
Article target = articleRepository.findById(id).orElse(null); // 데이터 찾기
// 2. 대상 엔티티 삭제하기
if (target != null) {
articleRepository.delete(target); // delete() 메서드로 대상 삭제하기
rttr.addFlashAttribute("msg", "삭제완료!!");
}
// 3. 결과 페이지로 리다이렉트하기
return "redirect:/articles";
}
[ header.mustache ] 에 메시지 창 추가
</nav>
{{#msg}}
<div class="alert alert-primary alert-dismissible">
{{msg}}
<button type="button" class="btn-close" data-bs-dismiss="alert" aria-label="Close"></button>
</div>
{{/msg}}
9장 CRUD와 SQL 쿼리 종합
서버에서 CRUD를 요청하면 JPA의 리파지토리가 DB에 해당 요청을 전달한다.
이때, DB는 SQL로 쿼리를 작성해 테이블 속 데이터를 관리한다.
쿼리(Query)란?
DB에 정보를 요청하는 구문이다. ( INSERT, SELECT, UPDATE, DELETE)
DB의 SQL 쿼리를 분석하기 전에 JPA 로깅 설정 먼저 하겠다.
로깅(Logging)이란 시스템이 작동할 대 당시의 상태와 작동 정보를 기록하는 것을 말한다.
설정은 application.properties 파일에서....
로깅 레벨
• TRACE(레벨1): DEBUG 레벨보다 더 상세한 정보
• DEBUG(레벨2): 응용 프로그램을 디버깅하는 데 필요한 세부 정보
• INFO(레벨3): 응용 프로그램의 순조로운 진행 정보
• WARN(레벨4): 잠재적으로 유해한 상황 정보
• ERROR(레벨5): 응용 프로그램이 수행할 수 있는 정도의 오류 정보
• FATAL(레벨6): 응용 프로그램이 중단될 만한 심각한 오류 정보
• OFF(레벨7): 로깅 기능 해제
현재 나는 레벨 2로 설정.
[ application.properties ]
# JPA 로깅 설정
# 디버그 레벨로 쿼리 출력
logging.level.org.hibernate.SQL=DEBUG
# 쿼리 줄바꿈하기
spring.jpa.properties.hibernate.format_sql=true
# 매개변수 값 보여 주기
logging.level.org.hibernate.type.descriptor.sql.BasicBinder=TRACE
# DB URL 설정
# 유니크 URL 생성하지 않기
spring.datasource.generate-unique-name=false
# 고정 URL 설정하기
spring.datasource.url=jdbc:h2:mem:testdb
URL 을 고정시켜줘서 이제 매번 번거롭게 바꿀 일이 없어졌다!!!!
id 자동 생성 (GeneratedValue)
기존에 작성한 data.sql 에 id 속성이랑 id 속성 값 지우기!!
커피 테이블 만들기
[ 1분 퀴즈 ]
2. 앞서 실습한 coffee 테이블에서 가격이 5,100원인 커피를 찾으려고 합니다. 이를 위한 쿼리를 작성해 보세요.
select * from coffee
where price = 5100;