@GetMapping("/detail/{id}")
public String getDetails(@PathVariable Long id, Model model) {
Optional<Book> bookOptional = bookService.getById(id);
if (bookOptional.isPresent()) {
Book book = bookOptional.get();
model.addAttribute("book", book);
// 평균 평점을 계산하여 모델에 추가
double averageRating = book.getReviews().stream().mapToInt(Review::getRating).average().orElse(0.0);
String formattedAverageRating = String.format("%.1f", averageRating);
model.addAttribute("averageRating", formattedAverageRating);
return "detail"; // 상세보기 View(detail.html) : ${book}
}else{
return "redirect:/";
}
}
여기서, 평균 평점을 구하는 방법으로 사용한 방식은 SQL 을 활용한 방식은 아니다.
하지만, SQL은 기본적으로 avg() 메서드를 통해서 평균을 구할 수 있었을 것.
지금까지는 SQL 쿼리를 직접적으로 사용하지 않았다. 하지만 이번에는 직접 SQL 쿼리를 작성하는 방법을 익히게 된다.
JPA Query Language (JPQL) - 사용자 정의 쿼리 JPA 에서 사용하는 QL
@Query 어노테이션을 이용하는 방법
코드를 Repository 인터페이스에 넣어주는 것.
그리고 Service 에서, 메서드를 사용해주는 것.
그리고 Controller 에서 id 값을 이용한 평균 구한 후, model 객체 바인딩까지.
여기서 중요한 건, Entity 를 기준으로 하게 되면,
FROM 뒤에 Review 라고, 클래스 이름과 동일하게 써야 한다.
그리고나서, WHERE 뒤에 r.book.id 라고 했는데,
r 은 Review 클래스의 객체이다. Review 클래스는 private Book book; 을 통해서 book 이라는 멤버를 가지고 있다.
book 은 Book 클래스의 객체이므로, 그 안에는 id 가 있다.
그런데, Entity 를 기준으로 하는게 아니라, table 을 기준으로 하게 되면,
table 은 대문자와 소문자가 구분이 없다. 그래서 이해를 돕기 위해 table 은 소문자로 표시해서, review 라고 한다.
그리고 나서, table 에서는 FK 열의 이름을 book_id 라고 했었다. 그래서, r.book_id 가 되는 것.
위치 기반 파라미터라고, @Param 을 쓰지 않는 방식도 있다.
@Query("SELECT AVG(r.rating) FROM Review r WHERE r.book.id = ?1")
public Double findAverageRatingByBookId(Long book_id);
@Query("SELECT AVG(r.rating) FROM review r WHERE r.book_id = ?1", nativeQuery = true)
public Double findAverageRatingByBookId(Long book_id);
여기서, ?1 이라고 하게 되면, 첫번째 파라미터를 받겠다는 의미가 된다.
만약 파라미터가 여러개였다면, ?2, ?3 이런 식으로 파라미터의 위치에 기반해서 어떤 파라미터를 받을 것인지를 표시할 수도 있다.
리뷰 삭제 기능을 추가해보자.
리뷰 목록을 보여주는 detail.html 에서, 리뷰를 보여주는 반복문에서, 삭제 버튼을 만들어주자.
1. <form> 태그
기능
- <form> 태그는 데이터를 서버로 전송하기 위해 사용됩니다.
- 여기서는 리뷰 삭제 요청을 서버로 보내는 폼입니다.
속성
- th:action="@{/deleteReview}":
- th:action: Thymeleaf 문법입니다.
- 서버로 전송할 URL 경로를 설정합니다.
- @{/deleteReview}:
- **/deleteReview**는 서버에서 이 요청을 처리할 핸들러 메서드가 있는 URL입니다.
- 예를 들어, BookController의 메서드에서 이 URL을 처리할 수 있습니다.
- th:action: Thymeleaf 문법입니다.
- method="post":
- HTTP 요청의 메서드 타입을 POST로 설정합니다.
- **POST**는 주로 데이터를 생성하거나 수정/삭제할 때 사용됩니다.
- 폼을 전송하면 데이터가 **HTTP 요청의 본문(body)**에 포함되어 서버로 전송됩니다.
- style="display: inline;":
- 이 스타일은 폼을 인라인(inline) 요소로 표시하도록 합니다.
- 인라인 요소는 다른 요소와 한 줄에 표시되며, 공간을 많이 차지하지 않습니다.
- 이 설정이 없으면 <form> 태그는 기본적으로 블록 요소이기 때문에 다른 요소와 줄 바꿈이 됩니다.
2. <input> 태그
- <input>: 사용자가 값을 입력하거나 서버로 값을 전송할 때 사용하는 태그입니다.
- type="hidden":
- hidden 타입은 화면에 표시되지 않는 숨겨진 입력 필드입니다.
- 화면에는 보이지 않지만, 폼이 제출될 때 값이 함께 서버로 전송됩니다.
- name="review_id":
- 이 입력 필드의 이름입니다.
- 서버에서 이 값을 식별할 때 review_id라는 키로 접근합니다.
- th:value="${reviews.id}":
- th:value: Thymeleaf 속성입니다.
- ${reviews.id}:
- reviews.id는 리뷰 객체의 id 값입니다.
- 이 값은 삭제할 리뷰의 고유 ID를 의미합니다.
- 서버로 폼이 전송될 때 **review_id**라는 이름으로 해당 reviews.id 값이 함께 전송됩니다.
3. <button> 태그
- <button>: 폼을 제출하거나 버튼 이벤트를 실행할 때 사용됩니다.
- type="submit":
- 버튼의 타입이 제출(submit) 버튼임을 명시합니다.
- 폼의 데이터를 서버로 전송하는 역할을 합니다.
- class="btn btn-danger btn-sm":
- 이 클래스들은 Bootstrap 스타일링에 사용됩니다:
- btn: Bootstrap의 버튼 스타일 기본 클래스입니다.
- btn-danger: 버튼을 빨간색으로 스타일링합니다. 주로 위험 동작(예: 삭제)을 나타낼 때 사용됩니다.
- btn-sm: 버튼의 사이즈를 작게(small) 만듭니다.
- 이 클래스들은 Bootstrap 스타일링에 사용됩니다:
- 삭제:
- 버튼에 표시되는 텍스트 내용입니다.
4. 실행 흐름
- 사용자가 버튼 클릭:
- "삭제" 버튼을 클릭하면 폼이 제출됩니다.
- 서버로 데이터 전송:
- 폼의 action 속성에서 설정한 /deleteReview URL로 POST 요청이 전송됩니다.
- **review_id**라는 이름으로 해당 리뷰의 id 값이 서버로 함께 전송됩니다.
- 서버 측 처리:
- Controller에서 /deleteReview 경로를 처리하는 메서드가 실행됩니다.
- 서버는 review_id 값을 받아서 해당 리뷰를 데이터베이스에서 삭제합니다.
5. 서버 측 (Controller 코드)
설명
- @PostMapping("/deleteReview"):
- POST 요청을 처리하는 메서드입니다.
- reviewService.deleteReviewById(review_id):
- 해당 리뷰를 삭제하는 로직을 서비스에 위임합니다.
- return "redirect:/detail/" + book_id;:
- 리뷰 삭제 후, 다시 해당 책 정보의 상세 페이지로 리다이렉트합니다.
6. 정리
- <form>: 서버로 데이터를 전송하는 폼입니다.
- action: 데이터를 보낼 URL 지정
- method="post": POST 요청을 통해 전송
- <input type="hidden">: 화면에 표시되지 않지만 값이 서버로 전송됩니다.
- <button>: 폼을 제출하는 버튼이며 Bootstrap 클래스를 사용해 스타일링되었습니다.
- 흐름: 버튼 클릭 → 폼 제출 → 서버로 review_id 전송 → 리뷰 삭제 로직 실행 → 페이지 리다이렉트.
삭제 버튼이 생겼다. 맨 아래에 있는 평점 1/5 을 삭제 해보겠다.
평균 평점이 3.3에서 4.0으로 제대로 변했다.
그리고 이것저것 더 많이 삭제 한 뒤에,
db-console 에서 review table 을 확인해보자.
삭제가 실제로 이루어진것까지 확인했다.
'Spring Boot (+ RESTful)' 카테고리의 다른 글
RESTful 웹 서비스 구축 - @RestController / MySQL / Swagger-ui (1) | 2024.12.23 |
---|---|
RESTful 웹 서비스 구축 - RESTful API와 마이크로 서비스: Spring Boot로 구현한 SOA 기반 아키텍쳐 (1) | 2024.12.22 |
Spring Boot - # Project 03 - Spring 의 데이터바인딩 / html - form / @RequestParam (1) | 2024.12.20 |
Spring Boot - # Project 03 - Bootstrap / @PostMapping (2) | 2024.12.19 |
Spring Boot - # Project 03 - 웹 버튼 클릭 / (1) | 2024.12.18 |