Spring [07] 책 게시판 1-(1)

totwo·2024년 9월 6일

Spring/JPA

목록 보기
7/17
post-thumbnail

Spring 구조


https://mybatis.org/mybatis-3/getting-started.html
myBatis의 SqlSessionFactory가 할 일을 Spring에서 HikariCP가 한다.

DispatcherServlet는 지정된 servlet-context.xml을 통해 MVC 준비한다.

Java : DAO
Mapper : MyBatis
Repository : Spring

Spring MVC 기본구조



main-java 폴더에 package 생성하기
보통 가운데에 있는 service layer 먼저 구현 후 controller, repository를 구현한다.


책 목록 게시판 만들기

  • Book table 생성

Book.java (entity)

  • entity 생성
package kr.smhrd.entity;

import lombok.AllArgsConstructor;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;

@Setter
@Getter
@NoArgsConstructor
@AllArgsConstructor
public class Book {
    private Long id; // 자동증가값 이름은 보통 id, 타입은 long
    // int a (아무 데이터도 넣지 않았을 때.. 쓸모가 없어서 0이든 데이터를 넣어주는 게 좋다)
    // Long a (아무 데이터도 넣지 않았을 때 상태 : null)
    private String title;
    private int price;
    private String author;
    private int page;
}

bookList.jsp

  • 책 목록 게시판 볼 views 생성
<%@ page language="java" contentType="text/html; charset=UTF-8"
    pageEncoding="UTF-8"%>
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
<%@ taglib prefix="fn" uri="http://java.sun.com/jsp/jstl/functions" %>
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
  <link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.4.1/css/bootstrap.min.css">
  <script src="https://ajax.googleapis.com/ajax/libs/jquery/3.7.1/jquery.min.js"></script>
  <script src="https://maxcdn.bootstrapcdn.com/bootstrap/3.4.1/js/bootstrap.min.js"></script> 
<title>bookList.jsp</title>
</head>
<body> 
	<div class="container">
	  <h2>SpringMVC01</h2>
	  <div class="panel panel-default">
	    <div class="panel-heading">Book</div>
	    <div class="panel-body">
	    	<table class="table table-bordered table-hover">
	    		<thead>
	    			<tr>
	    				<td>번호</td>
	    				<td>제목</td>
	    				<td>가격</td>
	    				<td>저자</td>
	    				<td>페이지수</td>
	    			</tr>
	    		</thead>
	    		<tbody>
		    		<!-- 책 목록 출력 -->
	    			<c:forEach varStatus="i" items="${bookList}" var="list"> <!-- 반복문의 상태를 기억하는 속성 -->
	    				<tr>
		    				<td><c:out value="${list.id}"/></td> <!--  index : 0부터 나옴, count : 1부터 나옴 -->
		    				<td><c:out value="${list.title}"/></td>
		    				<td><c:out value="${list.price}"/></td>
		    				<td><c:out value="${list.author}"/></td>
		    				<td><c:out value="${list.page}"/></td>
	    				</tr>
	    			</c:forEach>
	    		</tbody>
	    	</table> 
	    	</div>
	    <div class="panel-footer">AISCHOOL2024 - Fighting :]</div>
	  </div>
	</div>
</body>
</html>

BookController.java

@Controller
@RequiredArgsConstructor
// @RequestMapping("api") // mapping으로 '/api'로 줘서 할 수 있다
public class BookController {

    private final BookService bookService;
/*    // 책 전체 목록 요청 처리
    @RequestMapping("/bookList")
    public String bookList(Model model){
        List<Book> bookList = bookService.bookList();
        // viewname, 바인딩(setAttribute) 해줘야 함
        model.addAttribute("bookList", bookList);
        return "bookList"; // bookList.jsp -> ${bookList}
    } */
    // 책 전체 목록 요청 처리
    @GetMapping("/bookList")
    public String bookList(Model model){
        List<Book> bookList = bookService.bookList();
        // viewname, 바인딩(setAttribute) 해줘야 함
        model.addAttribute("bookList", bookList);
        return "bookList"; // bookList.jsp -> ${bookList}
    }
    // @PostMapping("/bookList") //같은 url이지만 mapping 방식을 다르게 하여 사용할 수 있다.
 }

BookService.java

@Service
@RequiredArgsConstructor // 생성자 주입 (자동으로 생성)
public class BookService {

    // @Autowired : 요즘 잘 안 쓰임
    private final BookRepository bookRepository;
    // <- final이 들어가면 값을 바꿀 수 없다.
    // 기능정의 (CRUD)
    // 책 전체 목록 가져오기
    // @Transactional
    public List<Book> bookList(){
        // 추가적인 로직을 구현
        return bookRepository.findAll();
    }
 }

client의 request 방식

  • get (전체/상세)
  • post (insert에 가까움)
  • put (post와 유사하나 update에 가깝다)
  • delete

url을 동일하게 하지만 방식을 바꿔 다른 기능으로 사용가능

책 등록

bookList.jsp

  • 책 등록 버튼 생성
	    	<button class="btn btn-sm btn-info" onclick="location.href='/s01/bookRegister'">책 등록</button>
	    	<!-- location.href에 '/bookRegister'를 적게 되면 root인 '/s01'이 지워지므로 적으면 안된다.-->
	    	<!-- 'bookRegister'를 적으면 앞의 '/s01'은 살아있긴 하지만 -->
	    	<!-- '/s01/bookRegister'로 적는 게 정석이다 그치만 유지보수를 위해 코드 안에 s01를 직접적으로 적지 않는 게 좋다 -->

bookController.java

  • 'bookRegister.jsp'로 이동시키는 기능 추가
    @GetMapping("/bookRegister")
    public String bookRegisterGet(){
        return "bookRegister"; // bookRegister.jsp
    }

bookRegister.jsp

<%@ page language="java" contentType="text/html; charset=UTF-8"
    pageEncoding="UTF-8"%>
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
<%@ taglib prefix="fn" uri="http://java.sun.com/jsp/jstl/functions" %>
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
  <link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.4.1/css/bootstrap.min.css">
  <script src="https://maxcdn.bootstrapcdn.com/bootstrap/3.4.1/js/bootstrap.min.js"></script>
<title>bookRegister.jsp</title>
</head>
<body> 
	<div class="container">
	  <h2>SpringMVC01</h2>
	  <div class="panel panel-default">
	    <div class="panel-heading">책 등록하기</div>
	    <div class="panel-body">
	    <!-- 책 등록 폼 -->
	    <form action="/s01/bookRegister" method="post">
          <div class="form-group">
            <label for="title">책 제목:</label>
            <input type="text" class="form-control" placeholder="Enter title" name="title" id="title">
            <!-- id는 label에 for로 연결하기 위해 작성함 (js) -->
            <!-- server로 전달하기 위해 name을 꼭 적어주어야 한다.-->
          </div>
          <div class="form-group">
            <label for="price">가격:</label>
            <input type="number" class="form-control" placeholder="Enter price" name="price" id="price">
          </div>
          <div class="form-group">
            <label for="author">저자:</label>
            <input type="text" class="form-control" placeholder="Enter author" name="author" id="author">
          </div>
          <div class="form-group">
            <label for="page">페이지:</label>
            <input type="number" class="form-control" placeholder="Enter page" name="page" id="page">
          </div>
          <button type="submit" class="btn btn-info btn btn-sm">등록</button>
          <button type="reset" class="btn btn-warning btn btn-sm">취소</button>
        </form>
	    </div>
	    <div class="panel-footer">AISCHOOL2024 - Fighting :]</div>
	  </div>
	</div>
</body>
</html>

request에 패킷정보를 저장해 servlet으로 보냄

bookController.java

    @PostMapping("/bookRegister")
    public String bookRegisterPost(Book book){
        // '파라미터 수집'한 거(t,p,a,p)를 Book에 넣어주는데 Spring에서 내부적으로 이루어진다.
        // @NoArgsConstructor 필수
        bookService.register(book);
        return "redirect:/bookList"; // 다시 리스트 페이지로 이동
    }

BookMapper.xml

	<insert id="save" parameterType="kr.smhrd.entity.Book">
		INSERT INTO BOOK(TITLE, PRICE, AUTHOR, PAGE)
		VALUES(#{title}, #{price}, #{author}, #{page})
	</insert>

BookRepository.java

  • save 추가
@Repository
public interface BookRepository { 
    public void save(Book book); // repository는 DB와 관련된 용어 사용하면 됨.
}

BookService.java

    // 책 등록 메서드
    public void register(Book book){
        bookRepository.save(book);
    }    

책 등록 버튼 클릭하여 책 등록해보기


책 정보 상세보기

bookList.jsp

  • title에 링크 연결하기
    <tbody>
        <!-- 책 목록 출력 -->
        <c:forEach varStatus="i" items="${bookList}" var="list"> <!-- 반복문의 상태를 기억하는 속성 -->
            <tr>
                <td><c:out value="${list.id}"/></td> <!--  index : 0부터 나옴, count : 1부터 나옴 -->
                <td><a href="/s01/bookView/${list.id}"><c:out value="${list.title}"/></a></td>
                <td><c:out value="${list.price}"/></td>
                <td><c:out value="${list.author}"/></td>
                <td><c:out value="${list.page}"/></td>
            </tr>
        </c:forEach>
    </tbody>

bookController.java

    @GetMapping("/bookView/{id}") // 이름은 지정해놓은 'id'랑 동일하게 안 가져와도 된다.
    public String bookView(@PathVariable Long id, Model model){
        model.addAttribute("book", book)
        return "bookView"; // bookView.jsp
    }

BookRepository.java

@Repository
public interface BookRepository { 
    public Book findById(Long id);
}

BookMapper.xml

	<select id="findById" parameterType="Long" resultType="kr.smhrd.entity.Book">
		<!-- parameterType 하나니까 생략 가능 -->
		SELECT * FROM BOOK WHERE ID = #{id};
	</select>

BookService.java

    // 책 상세보기 메서드
    public Book view(Long id){
        return bookRepository.findById(id);
    }

bookView.jsp

<%@ page language="java" contentType="text/html; charset=UTF-8"
    pageEncoding="UTF-8"%>
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
<%@ taglib prefix="fn" uri="http://java.sun.com/jsp/jstl/functions" %>
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
  <link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.4.1/css/bootstrap.min.css">
  <!-- <script src="https://ajax.googleapis.com/ajax/libs/jquery/3.7.1/jquery.min.js"></script> -->
  <script src="https://maxcdn.bootstrapcdn.com/bootstrap/3.4.1/js/bootstrap.min.js"></script> 
<title>bookView.jsp</title>
</head>
<body> 
	<div class="container">
	  <h2>SpringMVC01</h2>
	  <div class="panel panel-default">
	    <div class="panel-heading">책 상세보기 (ID : ${book.id})</div>
	    <div class="panel-body">
	        <table class="table table-bordered">
              <tr>
                <td>번호</td>
                <td>${book.id}</td>
              </tr>
              <tr>
                <td>제목</td>
                <td>${book.title}</td>
              </tr>
              <tr>
                <td>가격</td>
                <td>${book.price}</td>
              </tr>
              <tr>
                <td>저자</td>
                <td>${book.author}</td>
              </tr>
              <tr>
                <td>페이지</td>
                <td>${book.page}</td>
              </tr>
	        </table>
	        <!-- 수정, 삭제, 목록, 수량, 구매(장바구니) -->
	        <table class="table table-bordered">
              <tr align="center">
                <td>
                  <button class="btn btn-sm btn-info">수정</button>
                  <button class="btn btn-sm btn-primary">삭제</button>
                  <button class="btn btn-sm btn-default">목록</button>
                </td>
                <td>
                  수량 : <input type="number"/>
                  <button class="btn btn-sm btn-success">장바구니 담기</button>
                </td>
              </tr>
	        </table>

	        <!-- 리뷰 : 내용, 작성자(로그인), 평점(1~5) -->
            <h3>리뷰 작성</h3>
            <form action="/s01/reviewAdd" method="post">
              <input type="hidden" name="book_id" value="${book.id}"/>
                <table class="table">
                  <tr>
                    <td>내용</td>
                    <td><textarea rows="2" name="content" class="form-control"></textarea></td>
                  </tr>
                  <tr>
                    <td>평점</td>
                    <td>
                      <select class="form-control" name="rating">
                        <option value="1">1점 - 매우 나쁨</option>
                        <option value="2">2점 - 나쁨</option>
                        <option value="3">3점 - 보통</option>
                        <option value="4">4점 - 좋음</option>
                        <option value="5">5점 - 매우 좋음</option>
                      </select>
                    </td>
                  </tr>
                  <tr>
                    <td colspan="2">
                      <button type="submit" class="btn btn-sm btn-warning">리뷰등록</button>
                    </td>
                  <tr>
                </table>
             </form>
	    </div>
	    <div class="panel-footer">AISCHOOL2024 - Fighting :]</div>
	  </div>
	</div>
</body>
</html>

책 리뷰 작성

  • Review table 생성

Review.java (entity)

package kr.smhrd.entity;

import lombok.AllArgsConstructor;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;

import java.util.Date;

@Setter
@Getter
@NoArgsConstructor
@AllArgsConstructor
public class Review {
    private Long id; // 자동증가(AI)
    private Long book_id; // FK(외래키)
    private String content; // 내용
    private int rating; // 평점(1~5)
    private Date created_at; // 작성일자
}

ReviewService.java

package kr.smhrd.service;

import kr.smhrd.entity.Review;
import kr.smhrd.repository.ReviewRepository;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;

@Service
@RequiredArgsConstructor
public class ReviewService {

    private final ReviewRepository reviewRepository;

    public void register(Review review){
        reviewRepository.save(review);
    }
}

ReviewController.java

package kr.smhrd.controller;

import kr.smhrd.entity.Review;
import kr.smhrd.service.ReviewService;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.PostMapping;

@Controller
@RequiredArgsConstructor
public class ReviewController {

    private final ReviewService reviewService;
    @PostMapping("/reviewAdd")
    public String reviewAdd(Review review){
        // 리뷰 등록
        reviewService.register(review);
        return "redirect:/bookView/"+review.getBook_id();
    }
}

ReviewRepository.java

package kr.smhrd.repository;

import kr.smhrd.entity.Review;
import org.springframework.stereotype.Repository;

@Repository
public interface ReviewRepository {

    public void save(Review review);
}

ReviewMapper.mxl

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="kr.smhrd.repository.ReviewRepository">
    <insert id="save" parameterType="kr.smhrd.entity.Review">
        INSERT INTO REVIEW(BOOK_ID, CONTENT, RATING)
        VALUES(#{book_id}, #{content}, #{rating})
    </insert>
</mapper>

책 상세보기에서 리뷰 보기

ReviewMapper.mxl

	<select id="findAll" parameterType="Long" resultType="kr.smhrd.entity.Review">
        SELECT * FROM REVIEW WHERE BOOK_ID = #{book_id}
    </select>

ReviewService

    public List<Review> getByReviews(Long book_id){
        return reviewRepository.findAll(book_id);
    }

BookController

  • bookView 수정해주기
    @GetMapping("/bookView/{id}") // 이름은 지정해놓은 'id'랑 동일하게 안 가져와도 된다.
    public String bookView(@PathVariable Long id, Model model){
        Book book = bookService.view(id);
        model.addAttribute("book", book);
        // 리뷰 정보 가져오기
        List<Review> reviews = reviewService.getByReviews(id);
        model.addAttribute("reviews", reviews);
        return "bookView"; // bookView.jsp
    }

bookView.jsp

  • 날짜 원하는 대로 포매팅해주기

(1) taglib "fmt" 추가해주기

<%@ taglib prefix="fmt" uri="http://java.sun.com/jsp/jstl/fmt" %>

(2) 작성일 fmt태그 + formatDate를 이용해 원하는 패턴으로 수정해주기

<!-- <td>작성일 : ${review.created_at}</td> -->
<td>작성일 : <fmt:formatDate pattern="yyyy-MM-dd" value="${review.created_at}"/></td>

리뷰 목록

  • bootstrap 버전 변경해주고 필요한 거 추가해줌
    <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap@4.6.2/dist/css/bootstrap.min.css">
    <script src="https://cdn.jsdelivr.net/npm/jquery@3.7.1/dist/jquery.slim.min.js"></script>
    <script src="https://cdn.jsdelivr.net/npm/popper.js@1.16.1/dist/umd/popper.min.js"></script>
    <script src="https://cdn.jsdelivr.net/npm/bootstrap@4.6.2/dist/js/bootstrap.bundle.min.js"></script>
<!-- 작성된 리뷰가 있으면 출력 / 없으면 리뷰가 없습니다. -->
<h3>리뷰 리스트</h3>
<table class="table">
  <c:if test="${not empty reviews}">
  <c:forEach var="review" items="${reviews}">
  <tr>
    <!-- <td>작성일 : ${review.created_at}</td> -->
    <td>작성일 : <fmt:formatDate pattern="yyyy-MM-dd" value="${review.created_at}"/></td>
    <td>${review.content}</td>
    <td>평점 : <span class="badge badge-pill badge-danger">${review.rating}</span></td></td>
  </tr>
  </c:forEach>
  </c:if>
  <c:if test="${empty reviews}">
  <tr>
    <td colspan="3">등록된 리뷰가 없습니다.</td>
  </tr>
  </c:if>
</table>

profile
Hello, World!

0개의 댓글