JSP->Controller->Service->Dao->xml->DB
서비스는 인터페이스. 스프링은 껍데기인 인터페이스를 Implement한 serviceImpl을 사용해서 현실화함
왜 인터페이스를 거쳐서 jsp->service->serviceImpl로 가야하는지?
-> 컨트롤러에서 ServiceImpl을 쓸 때, Service interface를 @autowired함
-> 스프링은 interface를 거쳐서 요청을 세분화하기 때문(확장의 여지를 남겨놓는다)
package kr.or.ddit;
public class BookVO {
private int bookId;
private String title;
private String category;
private int price;
private String insertDate;
public int getBookId() {
return bookId;
}
public void setBookId(int bookId) {
this.bookId = bookId;
}
public String getTitle() {
return title;
}
public void setTitle(String title) {
this.title = title;
}
public String getCategory() {
return category;
}
public void setCategory(String category) {
this.category = category;
}
public int getPrice() {
return price;
}
public void setPrice(int price) {
this.price = price;
}
public String getInsertDate() {
return insertDate;
}
public void setInsertDate(String insertDate) {
this.insertDate = insertDate;
}
@Override
public String toString() {
return "BookVO [bookId=" + bookId + ", title=" + title + ", category=" + category + ", price=" + price
+ ", insertDate=" + insertDate + "]";
}
}
package kr.or.ddit;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.ModelAttribute;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.servlet.ModelAndView;
//컨트롤러 어노테이션(Annotation)
//어노테이션이 있는 클래스
//스프링프레임워크(디자인패턴 + 라이브러리 집합)가 웹 브라우저(크롬)의 요청(request)을 받아들이는 컨트롤러라고 인지해서
//자바 빈(java Bean)으로 등록해서 관리
@Controller
public class BookController {
private static final Logger logger = LoggerFactory.getLogger(BookController.class);
@Autowired
BookService bookService;
//Model : 컨트롤러가 반환할 데이터(VO객체, List, List<VO>, Map, List<Map>, JSON)를 담당
// 반환? 1)forward(데이터 담기 O) 2) redirect(데이터 담기 X)
//View : 화면 담당
//localhost:5090/create
//요청을 매핑한다
@RequestMapping(value="/create", method=RequestMethod.GET)
public ModelAndView create() {
ModelAndView mav = new ModelAndView();
//원래는 localhost:8090/WEB-INF/view/book/create.jsp
//servlet-context.xml에서...
//prefix : /WEB-INF/views/
mav.setViewName("book/create");
//suffix : .jsp
return mav;
}
//HTTP파라미터 : 웹 브라우저(크롬)에서 서버(톰캣)로 전달하는 데이터
// 제목, 분류, 가격.. 등이 파라미터로 넘어옴
//?title=톰소여의모험&category=소설&price=10000
//스프링은 HTTP파라미터를 자바 메소드의 파라미터로 변환해서 컨트롤러 메소드를 호출해줌
//map으로 받을 때에는 RequestParam 어노테이션
//{"title":"톰소여의 모험","category":"소설"...}
//vo로 받을때에는 ModelAtrribute 어노테이션
@RequestMapping(value="/create", method=RequestMethod.POST)
public ModelAndView createPost(ModelAndView mav,
@RequestParam Map<String, Object>map) { //@RequestParam 요청 파라미터를 받아주는 어노테이션
logger.info("map : " + map.toString());
BookVO bookVO = new BookVO();
//result : insert된 book_id의 값(p.k)
int result = bookService.insert(mapToVO(map));
logger.info("result : " + result);
if(result==0) { //insert가 안됨 -> 책 입력 화면으로 돌아가라
//데이터를 담지 않고 단순히 되돌아감.. --> forward아닌 redirect!
mav.setViewName("redirect:/create");
}else { //insert 성공 -> 상세 페이지를 요청
//?bookId=1 <-- 쿼리스트링 or 파라미터 목록
mav.setViewName("redirect:/detail?bookId="+result);
}
return mav;
}
//20220127숙제
//map -> BookVO로 변환(public BookVO mapToVo(map)..메서드로 처리)
public BookVO mapToVO(Map<String, Object>map) {
BookVO bookVO = new BookVO();
//bookVO.setTitle(map.get("title")); map에서 title뽑아내면 Object형이기 때문에 에러(나머지도 마찬가지!)
bookVO.setTitle((String)map.get("title"));//형변환
bookVO.setCategory((String)map.get("category"));
bookVO.setPrice(Integer.parseInt((String)map.get("price")));
return bookVO;
}
//book 상세보기
//파라미터 목록(쿼리스트링) : bookId=1
//{bookId,1}
@RequestMapping(value="detail", method=RequestMethod.GET)
public ModelAndView detail(@RequestParam Map<String,Object> map) {
logger.info("map : " + map);
BookVO bookVO = new BookVO();
bookVO.setBookId(Integer.parseInt((String)map.get("bookId")));
//{bookID:1,나머지는 null}
logger.info("bookVO(before) : " + bookVO.toString());
//상세보기 데이터 가져오기
bookVO = this.bookService.detail(bookVO);
//{bookId:1,나머지도 있음}
logger.info("bookVO(after) : " + bookVO.toString());
ModelAndView mav = new ModelAndView();
//book/detail : 뷰의 경로
//forwarding
mav.addObject("data", bookVO); //데이터를 담음
mav.setViewName("book/detail");
return mav;
}
// /update?bookId=3
// 쿼리스트링 : bookId=3
// map : {bookId:3}
// 책 수정화면 = 책 입력화면(jsp) + 책 상세 서비스 로직
@RequestMapping(value = "/update", method=RequestMethod.GET)
public ModelAndView update(@RequestParam Map<String, String> map) {
logger.info("map : " + map.get("bookId"));
//책 상세 서비스 로직 사용
BookVO bookVO = new BookVO();
bookVO.setBookId(Integer.parseInt(map.get("bookId")));
bookVO = this.bookService.detail(bookVO);
ModelAndView mav = new ModelAndView();
//request.setAttribute("data", bookVO);
mav.addObject("data", bookVO);
//forwarding : setViewName()로 페이지 이동값 세팅
mav.setViewName("book/update");
return mav;
}
//책 수정 post
@RequestMapping(value="/update", method=RequestMethod.POST)
public ModelAndView updatePost(@ModelAttribute BookVO bookVO, ModelAndView mav) {
logger.info("bookVO : " + bookVO.toString());
//true : update성공, false : update실패
boolean isUpdateSuccess = this.bookService.update(bookVO);
if(isUpdateSuccess) { //성공
//상세페이지로 이동
mav.setViewName("redirect:/detail?bookId="+bookVO.getBookId());
}else {//실패
//책 수정화면으로 돌아가기
//방법1)
//mav.setViewName("redirect:/update?bookId="+bookVO.getBookId());
//방법2)
Map<String,String> map = new HashMap<String, String>();
map.put("bookID", "" + bookVO.getBookId());
mav = this.update(map);
}
return mav;
}
//책 목록 리스트 + 검색 기능 추가
//http://localhost:8090/list?keyword=야호
//map : {"keyword":"야호"} 이렇게 들어간다~
@RequestMapping(value="/list", method=RequestMethod.GET)
//public ModelAndView allList(ModelAndView mav, @RequestParam String keyword) { //방법 1. 야호를 String 매개변수로 받음
public ModelAndView allList(ModelAndView mav, @RequestParam Map<String, Object> map) {//방법2. value를 Object로 해서 모든 형태의 데이터를 받을 수 있게 함
//ModelAndView mav = new ModelAndView로 객체 생성안해도 파라미터에 ModelAndView mav 넣으면 만들어짐
List<BookVO> allList = this.bookService.allList(map);
//데이터를 VIEW(jsp)에 전달할 수 있도록 mav 객체에 add함
mav.addObject("data",allList);
//forwarding
mav.setViewName("book/list"); //조립하기, servlet-context의 prefix에 /WEB-INF/views/ 마지막에 /붙으므로 book앞에 /안넣어도 됨
return mav;
}
// <form method="post" action="/delete">
//<input type="hidden" name="bookId" value="${data.bookId}" />
@RequestMapping(value = "/delete", method = RequestMethod.POST)
public ModelAndView deletePost(@RequestParam Map<String,Object> map, ModelAndView mav) {
//map : {bookId=3} 처럼 키와 값으로 구성되어 있음
boolean isDeleteSuccess = this.bookService.delete(map);
if(isDeleteSuccess) { //성공
//목록 화면으로 redirect(요청흐름을 이동시킴. 단, 데이터는 이동 못함)
mav.setViewName("redirect:/list");
}else { //실패
//상세 화면으로 redirect
mav.setViewName("redirect:/detail?bookId="+map.get("bookId").toString());
}
return mav;
}
//주문하면 아래 메서드로 매핑
@RequestMapping(value="/make", method=RequestMethod.GET)
public ModelAndView make() {
//라이더에 태워서 짬뽕 배송
ModelAndView mav = new ModelAndView();
//짬뽕 위치 알려주기
mav.setViewName("book/make");
//배송하기
return mav;
}
//RequestMapping 어노테이션 : 웹 브라우저의 요청에 실행되는 자바 메소드를 지정해줌
//method : 속성. http 요청 메소드를 의미함
// 1) GET : 데이터를 변경하지 않는 경우 사용
// 2) POST : 데이터가 변경될 경우 사용
//웹 브라우저에 화면을 보여줄 뿐 데이터의 변경이 일어나지 않으므로 GET 메소드를 사용한 것임
//jjajang() 메소드는 웹 브라우저에서 /jjajang 주소가 GET 방식으로 입력되었을 때 book/jjajang 경로의 뷰를 보여줌
@RequestMapping(value="/jjajang", method=RequestMethod.GET)
public ModelAndView jjajang(ModelAndView mav) { //라이더 생성
// book/jjajang : 뷰의 경로
mav.setViewName("book/jjajang");
return mav;
}
}
package kr.or.ddit;
import java.util.List;
import java.util.Map;
import org.mybatis.spring.SqlSessionTemplate;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Repository;
//매퍼 xml을 실행해주는 클래스.
//@Repository 어노테이션을 붙여서 이 클래스는 데이터에 접근하는 클래스라는 것을
//Spring에게 알려줌
//Spring이 데이터를 관리하는 클래스라고 인지해서 자바 빈(java bean)으로 등록해서 관리
@Repository
public class BookDao {
//sqlSessionTemplate 사용
/*
* new 키워드를 통해 직접 생성 안함.
* 의존성 주입(Dependency Injection - DI)을 통해 주입받음.
* 스프링은 미리 만들어놓은 sqlSessionTemplate 타입 객체를 BookDao 객체에 주입
* 이 과정은 자동으로 스프링에서 실행되며, 개발자가 직접 객체를 생성하지 않음(Inversion of Control - IoC)
*/
@Autowired
SqlSessionTemplate sqlSessionTemplate;
public int insert(BookVO bookVO) {
//book_SQL.xml파일에서
//namespace="book"
//id="insert"
//book.inert : 매퍼 쿼리 명
//bookVO : 두번째 인수.. 쿼리에 전달할 데이터(String, int, VO, Map 등이 올 수 있음)
return this.sqlSessionTemplate.insert("book.insert",bookVO);
}
//책 상세보기
public BookVO detail(BookVO bookVO) {
//.selectOne 메소드 : 1행을 가져올 때 사용
//결과 행 수가 0이면 null반환
//결과 행 수가 2 이상일 때 TooManyResultxException 예외 발생
//(namespace.id, 파라미터0)
return sqlSessionTemplate.selectOne("book.detail",bookVO);
}
//책 수정하기
public boolean update(BookVO bookVO) {
//(namespace.id, 파라미터)
//update 후에 영향받은 행의 수를 받음
int result = this.sqlSessionTemplate.update("book.update",bookVO);
//0보다 크다는 것은 update가 성공했다는 의미
return result > 0;
}
//책 목록
//map : {"keyword":"야호"}
public List<BookVO> allList(Map<String, Object> map){
//.selectList() 메소드는 결과 집합의 목록을 반환
// List 타입으로 읽어들일 수 있음
return this.sqlSessionTemplate.selectList("book.allList", map);
}
//책 삭제하기
public boolean delete(Map<String,Object> map) {
//RDBMS(Relational DataBase Manegement System)에서 (테이블을 relation이라고도 함!)
//delete 구문은 update 구문처럼 where 조건에 일치하는
//모든 행을 삭제하므로 영향을 받은 행의 수는 0 혹은 1 이상이 됨
int result = this.sqlSessionTemplate.delete("book.delete", map);
//result > 0는 삭제가 잘 되었다는 의미..
return result > 0;
}
}
package kr.or.ddit;
import java.util.List;
import java.util.Map;
/*
* 서비스 클래스는 비즈니스 클래스(기능)가 위치하는 곳임.
* 스프링 MVC 구조에서 Controller - Service - DAO
* 컨트롤러와 DAO를 연결하는 역할도 함
*
* 스프링은 직접 클래스를 생성하는 것을 지양(안함)하고,
* 인터페이스를 통해 접근하는 것을 권장하는 프레임워크.
*/
public interface BookService {
//메소드 시그니쳐 처리
//book 테이블로 insert
public int insert(BookVO bookVO);
//book 상세보기
BookVO detail(BookVO bookVO);
//book 수정하기
boolean update(BookVO bookVO);
//book 목록보기
public List<BookVO> allList(Map<String, Object> map);
//책 삭제하기
boolean delete(Map<String, Object> map);
}
package kr.or.ddit;
import java.util.List;
import java.util.Map;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
//@Service : 어노테이션.. 스프링에게 이 클래스는 서비스 클래스임을 알려줌
//스프링이 자바 빈(java bean)으로 등록하여 관리
@Service
public class BookServiceImpl implements BookService {
private static final Logger logger = LoggerFactory.getLogger(BookServiceImpl.class);
//이미 자바빈으로 관리되고 있는 BookDao 객체를 @Autowired로 가져다 쓴다
//IoC(제어의 역전) : 객체의 생성주기를 개발자가 하는 것이 아니라 스프링 프레임워크가 알아서 함
//DI(의존성 주입)
@Autowired
BookDao bookDao;
//부모 객체의 메소드를 재정의
@Override
public int insert(BookVO bookVO) {
logger.info("bookVO : " + bookVO.toString());
int affectRowcount = this.bookDao.insert(bookVO); //인서트된 행의 수를 int로 받음
if(affectRowcount == 1) { //입력이 성공
//xml에서 selectkey에서 세팅된 그 값(max(book_id)+1)
return bookVO.getBookId();
}
//입력 실패
return 0;
}
//책 상세보기
@Override
public BookVO detail(BookVO bookVO) {
return this.bookDao.detail(bookVO);
}
//책 수정하기
@Override
public boolean update(BookVO bookVO) {
return this.bookDao.update(bookVO);
}
//책 목록
@Override
public List<BookVO> allList(Map<String, Object> map) {
return this.bookDao.allList(map);
}
//책 삭제하기
@Override
public boolean delete(Map<String,Object> map) {
return this.bookDao.delete(map);
}
}
<%@ page language="java" contentType="text/html; charset=UTF-8"%>
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
<!DOCTYPE html>
<html>
<head>
<title>책 생성하기</title>
</head>
<body>
<h1>책 생성하기</h1>
<form method="post" action="/create">
<p>제목 : <input type="text" name="title"></p>
<p>카테고리 : <input type="text" name="category"></p>
<p>가격 : <input type="text" name="price"></p>
<p><input type="submit" value="저장"></p>
</form>
</body>
</html>
<%@ page language="java" contentType="text/html; charset=UTF-8"%>
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
<!--
BookController에서 mav 객체에 data라는 이름으로 select검색 결과를 넣었으므로
mav.addObject("data",bookVO)
달러{data.title} 형식으로 사용하면 됨
-->
<!DOCTYPE html>
<html>
<head>
<title>책 상세</title>
</head>
<body>
<h1>책 상세</h1>
<p>제목 : ${data.title}</p>
<p>카테고리 : ${data.category}</p>
<p>가격 : ${data.price}</p>
<p>입력일 : ${data.insertDate}</p>
<p><a href="/update?bookId=${data.bookId}">수정</a></p>
<p><a href="/list">목록으로</a></p>
<form method="post" action="/delete">
<input type="hidden" name="bookId" value="${data.bookId}" />
<input type="submit" value="삭제"/>
</form>
</body>
</html>
<%@ page language="java" contentType="text/html; charset=UTF-8"%>
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
<!--
BookController에서 mav 객체에 data라는 이름으로 select검색 결과를 넣었으므로
mav.addObject("data",bookVO)
달러{data.title} 형식으로 사용하면 됨
-->
<!DOCTYPE html>
<html>
<head>
<title>책 수정하기</title>
</head>
<body>
<h1>책 수정</h1>
<!-- action이 없으면../update를 요청 단, method는 post -->
<form method="post">
<input type="hidden" name="bookId" value="${data.bookId}" />
<p>제목 : <input type="text" name="title" value="${data.title}" required /></p>
<p>카테고리 : <input type="text" name="category" value="${data.category}" required /></p>
<p>가격 : <input type="text" name="price" value="${data.price}" /></p>
<p><input type="submit" value="저장" />
<input type="button" value="취소" onclick="javascript:location.href='/detail?bookId=${data.bookId}'"/></p>
</form>
</body>
</html>
<%@ page language="java" contentType="text/html; charset=UTF-8"%>
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
<!DOCTYPE html>
<html>
<head>
<title>책 목록</title>
<script type="text/javascript">
function fn_create(){
location.href="/create";
}
</script>
</head>
<body>
<h1>책 목록</h1>
<p>
<!--
form 태그의 기본 HTTP 메소드는 GET
action 속성을 생략하면 현재 url(/list)을 요청
-->
<!-- <form method="get" action="/list"> -->
<form>
<input type="text" name="keyword" value="${param.keyword}" />
<input type="submit" value="검색" />
</form>
</p>
<table border="1">
<thead>
<tr>
<th>제목</th>
<th>카테고리</th>
<th>가격</th>
</tr>
</thead>
<tbody>
<!-- data 객체의 타입 : List<BookVO> -->
<c:forEach var="list" items="${data}">
<tr>
<td><a href="/detail?bookId=${list.bookId}">${list.title}</a></td>
<td>${list.category}</td>
<td>${list.price}</td>
</tr>
</c:forEach>
</tbody>
</table>
<br>
<input type="button" value="책 입력" onclick="fn_create();" />
</body>
</html>
<?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="book">
<!-- * selectKey?
일련번호 처리시 많이 사용
마이바티스는 쿼리 실행 시 파라미터를 치환해줌
selectKey 전 : {"title":"검은태양","category":"드라마","price":10000}
selectKey 후 : {"bookId":1, "title":"검은태양","category":"드라마","price":10000}
-->
<insert id="insert" parameterType="kr.or.ddit.BookVO">
<!-- 중요!! -->
<selectKey order="BEFORE" keyProperty="bookId" resultType="integer">
SELECT NVL(MAX(BOOK_ID),0)+1 FROM BOOK
</selectKey>
INSERT INTO BOOK(BOOK_ID,TITLE,CATEGORY,PRICE,INSERT_DATE)
VALUES(#{bookId},#{title},#{category},#{price},SYSDATE)
</insert>
<!-- 책 상세보기 -->
<!-- 1:다 관계가 있다면 resultType을 사용하자-->
<select id="detail" parameterType="bookVO" resultType="bookVO">
SELECT BOOK_ID,TITLE,CATEGORY,PRICE,INSERT_DATE
FROM BOOK
WHERE BOOK_ID = #{bookId}
</select>
<!-- 책 수정하기, update태그는 UPDATE 쿼리를 실행하기 위한 마이바티스 태그 -->
<update id="update" parameterType="bookVO">
UPDATE BOOK
SET TITLE = #{title}, CATEGORY = #{category}, PRICE = #{price}
WHERE BOOK_ID = #{bookId}
</update>
<!-- 책 목록보기 -->
<!--
map : {keyword=야호} 로 들어오면
아래 쿼리의 keyword가 야호로 교체됨
WHERE 1 = 1은 관습적인 구문. 1 = 1은 항상 TRUE이고
- 조건이 2개 이상일 경우 처음 시작하는 조건은 WHERE로 시작하고 두번째로 시작하는 조건은 AND 여야 함
- 매번 첫번째 조건인지 검사하는 것은 번거롭기 때문에 무조건 WHERE 1 = 1을 써둔 후 나머지 조건을 AND로 이어붙임
- 동적쿼리(<if test~~~) : 쿼리의 내용이 파라미터가 아니라 마이바티스의 규칙에 의해 변경되는 것
-->
<select id="allList" parameterType="hashMap" resultType="bookVO">
SELECT BOOK_ID,TITLE,CATEGORY,PRICE,INSERT_DATE
FROM BOOK
WHERE 1 = 1
<if test="keyword!=null and keyword!=''">
AND (TITLE LIKE '%' || #{keyword} || '%' OR
CATEGORY LIKE '%' || #{keyword} || '%')
</if>
ORDER BY BOOK_ID DESC
</select>
<!-- 책 삭제, detail.jsp에서 작성한 name="bookId" -->
<delete id="delete" parameterType="hashMap">
DELETE FROM BOOK
WHERE BOOK_ID = #{bookId}
</delete>
</mapper>