Spring Boot (+ RESTful)

Spring Boot - # Project 03 - Spring 의 데이터바인딩 / html - form / @RequestParam

wy-family 2024. 12. 20. 16:50

 

 

리뷰 작성하는 것,

그리고 반영되는 것,

hibernate가 sql 해주는 것,

모두 확인을 마쳤다.

하지만 아직도 이해가 잘 안 되는 부분, 조금 애매하게 이해한 부분들을 추가로 더 공부해보겠다.

 

 

 

Review 라는 객체는 id, content, rating, createdAt, book 이라는 멤버를 가지고 있어.
book 은 ManyToOne 으로 Book 객체랑 연결이 된것이지만.
그리고 id 는 자동으로 증가하는 상황이야. GeneratedValue.
그런데, 여기서, 리뷰 및 평점 쓰기 부분에서, content랑 rating은 입력하는 것이 form 에 있는데,
createdAt 은 내가 구현을 하지 않은 것 같은데 어떻게 된거지?
그리고 book은 자동으로 연결이 되는건가?

 

리뷰 및 평점 쓰기 부분에서의 동작과 관련된 상세한 흐름을 하나씩 설명하겠습니다.


1. createdAt이 자동으로 설정되는 이유

createdAt은 Review 객체의 날짜/시간을 나타내는 멤버 변수입니다. 코드에서 직접 입력받지 않았음에도 값이 자동으로 설정된 이유는 다음 코드 때문입니다:

review.setCreatedAt(LocalDateTime.now());

이 코드의 의미

  • LocalDateTime.now(): 현재의 날짜와 시간을 가져옵니다.
  • setCreatedAt(): 가져온 날짜와 시간을 review 객체의 createdAt 필드에 설정합니다.

어디서 설정되었는가?

  • **BookController**의 reviewRegister 메서드 안에 있습니다:

이 코드가 폼 데이터를 처리할 때 실행되므로, 입력받지 않아도 자동으로 createdAt에 현재 시간이 저장됩니다.


2. book이 자동으로 연결되는 이유

book 필드는 Review 객체와 Book 객체 간의 관계를 표현합니다.

구현된 코드 확인

BookController의 reviewRegister 메서드에서 book을 명시적으로 설정하고 있습니다:

Book book = bookService.getById(book_id)
    .orElseThrow(() -> new IllegalArgumentException("Invalid book id: " + book_id));
review.setBook(book); // Review 객체에 Book 연결

흐름 설명

  1. 폼에서 book_id 값을 hidden input으로 전송:
    • **book_id**는 현재 보고 있는 책의 ID입니다.
<input type="hidden" name="book_id" th:value="${book.id}">
 
 
  1. **@RequestParam("book_id")**를 통해 컨트롤러 메서드에서 book_id를 받습니다:
public String reviewRegister(@RequestParam("book_id") Long book_id, Review review)
  1. **bookService.getById(book_id)**를 호출해서 Book 객체를 가져옵니다:
    • **book_id**를 이용해 데이터베이스에서 해당 책을 조회합니다.
  2. review.setBook(book):
    • 조회한 book 객체를 review의 book 필드에 설정합니다.
    • 이로써 리뷰와 해당 책이 연결됩니다.

3. 전체 흐름 정리

리뷰 및 평점 쓰기에서 일어나는 과정

  1. 폼 제출:
    • 사용자는 **content(리뷰 내용)**과 **rating(평점)**을 입력합니다.
    • **book_id**는 숨겨진 필드로 함께 전송됩니다.
  2. 컨트롤러의 reviewRegister 메서드:
    • @RequestParam을 통해 book_id를 받고, Review 객체를 생성합니다.
    • setCreatedAt(LocalDateTime.now()) → createdAt 필드를 현재 시간으로 설정.
    • setBook(book) → 리뷰와 책의 관계를 설정합니다.
  3. 저장:
    • reviewService.save(review)를 호출해서 리뷰를 데이터베이스에 저장합니다.
    • 이때 id는 자동으로 생성되고, createdAt과 book도 저장된 상태입니다.
  4. 리다이렉트:
    • 리뷰 저장이 완료되면 /detail/{book.id} 페이지로 이동합니다.

요약

  • createdAt: LocalDateTime.now()로 컨트롤러에서 설정.
  • book: setBook() 메서드를 통해 자동으로 연결.
  • id: @GeneratedValue로 데이터베이스에서 자동 생성.

@PostMapping("/register")
public String reviewRegister(@RequestParam("book_id") Long book_id, Review review) {}

 

<form th:action="@{/register}" method="post">
                <input type="hidden" name="book_id" th:value="${book.id}">
                <div class="form-group">
                    <label for="content">리뷰내용</label>
                    <textarea class="form-control" id="content" name="content" rows="3" required></textarea>
                </div>
                <div class="form-group">
                    <label for="rating">평점</label>
                    <select class="form-control" id="rating" name="rating" required>
                        <option value="1">1 - Poor</option>
                        <option value="2">2 - Fair</option>
                        <option value="3">3 - Good</option>
                        <option value="4">4 - Very Good</option>
                        <option value="5">5 - Excellent</option>
                    </select>
                </div>
                <button type="submit" class="btn btn-secondary btn-sm">리뷰등록</button>
            </form>

 

@RequestParam의 book_id 와 Long 의 book_id 의 차이는?
어디서 그 값들이 오는 것인지?
+ Review review 는 어디서 오는 것인지?

 

1. @RequestParam("book_id")는 어디에서 값을 가져오는가?

  • **detail.html**의 폼에는 다음과 같은 숨겨진 입력 필드가 있습니다:
<input type="hidden" name="book_id" th:value="${book.id}">
  • name="book_id": 폼이 전송될 때, 이 입력 필드의 값이 **book_id**라는 이름으로 서버로 전달됩니다.
  • th:value="${book.id}": 현재 책의 ID 값이 Thymeleaf를 통해 입력됩니다.

2. @RequestParam이 동작하는 원리

  • **@RequestParam("book_id")**는 HTTP 요청의 파라미터 중 **book_id**라는 이름을 가진 값을 가져옵니다.
    • name="book_id"와 일치하는 요청 파라미터 값이 자동으로 바인딩됩니다.

컨트롤러의 파라미터 선언

public String reviewRegister(@RequestParam("book_id") Long book_id, Review review)
  • @RequestParam("book_id") Long book_id:
    • 여기서 **book_id**는 HTML 폼에서 전송된 book_id의 값을 의미합니다.
    • 즉, book_id의 값Long book_id 변수에 저장됩니다.

3. Long book_id는 무엇인가?

  • **Long book_id**는 변수명입니다.
  • **RequestParam("book_id")**는 HTML 폼에서 넘어오는 name="book_id" 값을 가져와서 Long book_id 변수에 저장합니다.

정리: 동작 순서

  1. **detail.html**에서 <input type="hidden" name="book_id" th:value="${book.id}">가 포함된 폼이 제출됩니다.
  2. 폼 데이터를 전송할 때:
book_id=1
content="리뷰 내용"
rating=5
  1. reviewRegister 메서드에서:
    • **@RequestParam("book_id")**는 **book_id**라는 이름의 값을 가져옵니다.
    • 이 값이 Long book_id 변수에 할당됩니다.

결론

  • Long book_id: 서버에서 받은 book_id 값을 저장하는 변수명입니다.
  • @RequestParam("book_id"): HTTP 요청에서 book_id라는 이름의 파라미터를 가져오라는 의미입니다.

1. Review review는 무엇인가?

  • **Review**는 Review 클래스의 객체입니다.
  • 이 객체는 content, rating, createdAt, book과 같은 멤버 변수를 가지고 있습니다.

2. Review review는 어디서 오는가?

Review 객체는 Spring MVC가 폼 데이터를 바인딩해서 자동으로 생성하는 객체입니다.

  1. 폼 데이터 전송
    **detail.html**에서 사용자가 입력한 **리뷰 내용(content)**과 **평점(rating)**이 폼을 통해 전송됩니다:
<form th:action="@{/register}" method="post">
    <div class="form-group">
        <label for="content">리뷰내용</label>
        <textarea class="form-control" id="content" name="content" rows="3" required></textarea>
    </div>
    <div class="form-group">
        <label for="rating">평점</label>
        <select class="form-control" id="rating" name="rating" required>
            <option value="1">1 - Poor</option>
            <option value="2">2 - Fair</option>
            <option value="3">3 - Good</option>
            <option value="4">4 - Very Good</option>
            <option value="5">5 - Excellent</option>
        </select>
    </div>
    <button type="submit" class="btn btn-secondary btn-sm">리뷰등록</button>
</form>
  1. Spring MVC의 자동 객체 생성
    • 폼 데이터에서 name="content"와 name="rating"이 전송되면:
      • Spring이 Review 객체를 자동으로 생성합니다.
      • 데이터 바인딩: 전송된 값이 Review 객체의 필드에 자동으로 저장됩니다.
        • content → Review 객체의 content 필드에 저장
        • rating → Review 객체의 rating 필드에 저장
  2. Controller에서 받는 Review review
    • 이렇게 생성된 Review 객체가 컨트롤러 메서드의 Review review 파라미터에 전달됩니다:
public String reviewRegister(@RequestParam("book_id") Long book_id, Review review)

3. Review 객체에 저장되는 값

  • 폼 입력값:
    • content: 사용자가 입력한 리뷰 내용
    • rating: 사용자가 선택한 평점 값
  • Controller에서 추가로 설정하는 값:
review.setCreatedAt(LocalDateTime.now()); // 현재 시간 설정
review.setBook(book);                    // 어떤 책에 대한 리뷰인지 설정

4. 정리: Review review의 값 흐름

  1. 폼 전송:
    • 사용자가 content와 rating 값을 입력하고 제출합니다.
  2. Spring MVC 바인딩:
    • 폼 데이터를 기반으로 Review 객체가 자동으로 생성됩니다.
  3. Controller에서 추가 설정:
    • createdAt과 book 필드를 설정합니다.
  4. 저장:
    • reviewService.save(review)를 통해 최종적으로 데이터베이스에 저장됩니다.

최종 정리

  • Review review:
    • Spring이 폼 데이터를 바인딩해서 생성한 객체입니다.
    • content와 rating 값을 자동으로 담고 옵니다.
  • Controller에서 createdAt과 book을 추가로 설정합니다.

Spring이 Review 객체를 자동으로 생성하는 원리를 이해하려면 Spring MVC의 데이터 바인딩요청 파라미터 처리 과정을 알아야 합니다.


1. Spring의 데이터 바인딩이란?

Spring MVC는 폼 데이터요청 파라미터를 컨트롤러 메서드의 파라미터 객체에 자동으로 매핑(바인딩)합니다.

  • 매핑 원리:
    • HTTP 요청에서 전송된 파라미터의 이름객체의 필드 이름과 일치하면, 해당 필드에 값을 자동으로 할당합니다.

2. Review 객체를 자동 생성하는 원리

요청 파라미터와 객체 매핑

  1. detail.html에서 폼 데이터가 전송:
<textarea name="content">리뷰 내용</textarea>
<select name="rating">
    <option value="5">5</option>
</select>
  • name="content"와 name="rating" 값을 가진 데이터가 HTTP 요청의 파라미터로 전송됩니다:
content=리뷰 내용
rating=5
  1. 컨트롤러의 메서드 파라미터:
public String reviewRegister(@RequestParam("book_id") Long book_id, Review review)
  • 여기서 **Review review**는 객체 파라미터입니다.
  • Spring은 Review 객체의 **필드명(content, rating)**과 HTTP 파라미터 이름을 비교해서 일치하는 필드에 값을 자동으로 설정합니다.

Spring 내부에서 발생하는 일

Spring MVC는 다음과 같은 동작 순서로 Review 객체를 자동으로 생성하고 값(파라미터)을 매핑합니다:

  1. Review 객체의 기본 생성자 호출
    • Spring은 먼저 Review 클래스의 기본 생성자를 호출해서 빈 객체를 생성합니다.
     
public class Review {
    private String content;
    private int rating;

    public Review() { } // 기본 생성자
}

 

  1. HTTP 요청 파라미터 매핑
    • HTTP 요청의 파라미터 이름과 Review 클래스의 필드 이름이 일치하면 값을 바인딩합니다.
    • 예:
      • content=리뷰 내용 → Review 객체의 content 필드에 리뷰 내용 저장
      • rating=5 → Review 객체의 rating 필드에 5 저장
  2. Setter 메서드 사용 (있다면)
    • Spring은 setter 메서드를 통해 값을 할당하려고 시도합니다:
public void setContent(String content) {
    this.content = content;
}
public void setRating(int rating) {
    this.rating = rating;
}
  1. 객체가 완성된 상태로 메서드에 전달
    • 최종적으로 값이 매핑된 Review 객체가 컨트롤러 메서드에 전달됩니다.

3. Review 객체를 생성해야 한다는 걸 어떻게 아는가?

Spring은 컨트롤러 메서드의 파라미터 선언을 보고 객체 생성 여부를 결정합니다.

규칙

  1. 컨트롤러 메서드의 파라미터가 단순 타입(String, int 등)이라면, @RequestParam 또는 기타 어노테이션을 사용합니다.
  2. 파라미터가 객체 타입(예: Review)이라면:
    • HTTP 요청 파라미터를 바탕으로 해당 객체를 자동으로 생성하고 값을 바인딩합니다.

예시 비교

단순 타입

public String reviewRegister(@RequestParam("content") String content,
                             @RequestParam("rating") int rating) {
    Review review = new Review();
    review.setContent(content);
    review.setRating(rating);
    ...
}
  • 개발자가 수동으로 객체를 생성하고 값을 설정합니다.

객체 타입

public String reviewRegister(@RequestParam("book_id") Long book_id, Review review) {
    // Spring이 Review 객체를 자동으로 생성하고 필드를 바인딩
    ...
}
  • Spring이 자동으로 객체를 생성하고 필드에 값을 바인딩합니다.

4. 결론

Spring이 Review 객체를 생성하고 값을 설정하는 이유는:

  1. 컨트롤러 메서드의 파라미터에 Review 객체가 선언되어 있기 때문입니다.
  2. HTTP 요청 파라미터의 이름과 Review 객체의 필드 이름이 일치하기 때문입니다.
  3. Spring은 Review 클래스의 기본 생성자를 호출한 뒤, 필드에 값을 자동으로 매핑합니다.

이 원리를 Spring의 데이터 바인딩이라고 합니다.