

https://mybatis.org/mybatis-3/getting-started.html
myBatis의 SqlSessionFactory가 할 일을 Spring에서 HikariCP가 한다.
DispatcherServlet는 지정된 servlet-context.xml을 통해 MVC 준비한다.


Java : DAO
Mapper : MyBatis
Repository : Spring

main-java 폴더에 package 생성하기
보통 가운데에 있는 service layer 먼저 구현 후 controller, repository를 구현한다.
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;
}
<%@ 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>
@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 방식을 다르게 하여 사용할 수 있다.
}
@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을 동일하게 하지만 방식을 바꿔 다른 기능으로 사용가능

<button class="btn btn-sm btn-info" onclick="location.href='/s01/bookRegister'">책 등록</button>
<!-- location.href에 '/bookRegister'를 적게 되면 root인 '/s01'이 지워지므로 적으면 안된다.-->
<!-- 'bookRegister'를 적으면 앞의 '/s01'은 살아있긴 하지만 -->
<!-- '/s01/bookRegister'로 적는 게 정석이다 그치만 유지보수를 위해 코드 안에 s01를 직접적으로 적지 않는 게 좋다 -->
@GetMapping("/bookRegister")
public String bookRegisterGet(){
return "bookRegister"; // 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으로 보냄
@PostMapping("/bookRegister")
public String bookRegisterPost(Book book){
// '파라미터 수집'한 거(t,p,a,p)를 Book에 넣어주는데 Spring에서 내부적으로 이루어진다.
// @NoArgsConstructor 필수
bookService.register(book);
return "redirect:/bookList"; // 다시 리스트 페이지로 이동
}

<insert id="save" parameterType="kr.smhrd.entity.Book">
INSERT INTO BOOK(TITLE, PRICE, AUTHOR, PAGE)
VALUES(#{title}, #{price}, #{author}, #{page})
</insert>
@Repository
public interface BookRepository {
public void save(Book book); // repository는 DB와 관련된 용어 사용하면 됨.
}
// 책 등록 메서드
public void register(Book book){
bookRepository.save(book);
}


<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>

@GetMapping("/bookView/{id}") // 이름은 지정해놓은 'id'랑 동일하게 안 가져와도 된다.
public String bookView(@PathVariable Long id, Model model){
model.addAttribute("book", book)
return "bookView"; // bookView.jsp
}
@Repository
public interface BookRepository {
public Book findById(Long id);
}
<select id="findById" parameterType="Long" resultType="kr.smhrd.entity.Book">
<!-- parameterType 하나니까 생략 가능 -->
SELECT * FROM BOOK WHERE ID = #{id};
</select>
// 책 상세보기 메서드
public Book view(Long id){
return bookRepository.findById(id);
}
<%@ 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>

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; // 작성일자
}
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);
}
}
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();
}
}
package kr.smhrd.repository;
import kr.smhrd.entity.Review;
import org.springframework.stereotype.Repository;
@Repository
public interface ReviewRepository {
public void save(Review review);
}
<?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>

<select id="findAll" parameterType="Long" resultType="kr.smhrd.entity.Review">
SELECT * FROM REVIEW WHERE BOOK_ID = #{book_id}
</select>
public List<Review> getByReviews(Long book_id){
return reviewRepository.findAll(book_id);
}
@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
}
(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>

<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>
