package kr.or.ddit;
import java.awt.print.Book;
import java.util.ArrayList;
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);
//컨트롤러에서 Service를 사용할 수 있음
@Autowired
BookService bookService;
//객체는 맞는데 자바빈으로 등록되어 사용됨(스프링은 인터페이스를 선호)
//Model : 컨트롤러가 반환할 데이터(VO객체, List, List<VO>, Map, List<Map>, JSON)를 담당
// 반환? 1) forward(데이터 담기O) 2) redirect(데이터 담기X)
//View : 화면 담당
//localhost:8090/create
@RequestMapping(value="/create", method=RequestMethod.GET) //요청이 Mapping됨
public ModelAndView create() {
ModelAndView mav = new ModelAndView();
//localhost:8090/WEB-INF/views/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로 받을 때에는 ModelAttribute 어노테이션..
@RequestMapping(value="/create", method=RequestMethod.POST) //요청이 Mapping됨
public ModelAndView createPost(ModelAndView mav,
@RequestParam Map<String, Object> map) {
logger.info("map : " + map.toString());
//20220127숙제
//map -> BookVO로 변환(public BookVO mapToVO(map)..메소드로 처리)
//result : insert된 book_id의 값(p.k)
int result = bookService.insert(mapToVO(map));
logger.info("result : " + result);
if(result == 0) { //insert가 안됨 -> 책 입력 화면으로 돌아가라
//데이터를 담지 않고 단순히 되돌아감...
mav.setViewName("redirect:/create");
}else { //insert 성공 -> 상세 페이지를 요청
//?bookId=1 <- 쿼리스트링/파라미터목록
// mav.addObject("bookId", result);
mav.setViewName("redirect:/detail?bookId="+result);
}
return mav;
}
//book 상세 보기
//파라미터 목록(쿼리스트링) : bookId=1
//{bookId,1} => 요청 파라미터를 받을 것이다(RequestParam)
@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);
//책 상세 서비스 로직 사용
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
mav.setViewName("book/update");
return mav;
}
//책 수정 post
@RequestMapping(value="/update", method=RequestMethod.POST)
public ModelAndView updatePost(@ModelAttribute BookVO bookVO,
ModelAndView mav) {
//form의 name속성과 BookVO의 멤버변수명을 같게하면 @ModelAttribute로 받아올 수 있음
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;
}
@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;
}
//책 리스트 보기
//http://localhost:8090/list?keyword=방원
//map : {"keyword":"방원"}
@RequestMapping(value="/list", method=RequestMethod.GET)
public ModelAndView list(ModelAndView mav,
@RequestParam Map<String, Object> map) {
List<BookVO> list = this.bookService.list(map);
//데이터를 VIEW(jsp)에 전달할 수 있도록 mav 객체에 add함
mav.addObject("data", list);
//forwarding
mav.setViewName("book/list");
return mav;
}
// /delete
//<form method="post" action="/delete">
@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;
}
//map -> BookVO로 변환
public BookVO mapToVO(Map<String , Object> map) {
BookVO bookVO = new BookVO();
bookVO.setTitle((String)map.get("title"));
bookVO.setCategory((String)map.get("category"));
bookVO.setPrice(Integer.parseInt((String)map.get("price")));
return bookVO;
}
}
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을 실행해주는 클래스.
//어노테이션을 붙여서 이 클래스는 데이터에 접근하는 클래스라는 것을
//Spring에게 알려줌
//Spring이 데이터를 관리하는 클래스라고 인지해서 자바 빈(java bean)으로 등록해서 관리
@Repository
public class BookDao {
//sqlSessionTemplate 사용 - root-context에서 가져옴
/*
* new 키워드를 통해 직접 생성 안함.
* 의존성 주입(Dependency Injection - DI)을 통해 주입받음.
* 스프링은 미리 만들어 놓은 sqlSessionTemplate 타입 객체를 BookDao 객체에 주입
* 이 과정은 자동으로 스프링에서 실행되며, 개발자가 직접 객체를 생성하지 않음(IoC)
*/
@Autowired
SqlSessionTemplate sqlSessionTemplate;
public int insert(BookVO bookVO) {
//book_SQL.xml파일에서
//namespace="book"
//id="insert"
//book.insert : 매퍼 쿼리 명
//bookVO : 두 번째 인수.. 쿼리에 전달할 데이터(String, int, VO, Map)
return this.sqlSessionTemplate.insert("book.insert", bookVO);
}
//책 상세보기
public BookVO detail(BookVO bookVO) {
//.selectOne 메소드 : 1행을 가져올 때 사용
//결과 행 수가 0이면 null을 반환
//결과 행 수가 2이상일 때 TooManyResultException 예외 발생
//(namespace.id, 파라미터 0
return sqlSessionTemplate.selectOne("book.detail", bookVO);
}
//책 수정하기
public boolean update(BookVO bookVO) {
//(namespace.id, 파라미터)
//update 후에 영향받은 행의 수를 받음
int result = sqlSessionTemplate.update("book.update", bookVO);
//0보다 크다는 것은 update가 성공했다는 의미
return result > 0; //0보다 크면 true를 반환
}
//책 목록보기
public List<BookVO> list(Map<String, Object> map){
//.selectList() 메소드는 결과 집합 목록 반환
//List 타입으로 읽어들일 수 있음
return sqlSessionTemplate.selectList("book.list", map);
}
//책 삭제하기
public boolean delete(Map<String, Object> map) {
//RDBMS(Relational DataBase Management System)에서
//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 상세보기
public BookVO detail(BookVO bookVO);
//book 수정하기
public boolean update(BookVO bookVO);
//book 목록보기
public List<BookVO> list(Map<String, Object> map);
//book 삭제하기
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;
//어노테이션.. 스프링에게 이 클래스는 서비스 클래스임을 알려줌
//스프링이 자바 빈(java bean)으로 등록하여 관리
@Service
public class BookServiceImpl implements BookService {
private static final Logger logger =
LoggerFactory.getLogger(BookServiceImpl.class);
//IoC(제어의 역전)
//DI(의존성 주입)
@Autowired
BookDao bookDao; //bookDao = new BookDao();나 getInstance가 필요없음
//부모 객체의 메소드를 재정의
@Override
public int insert(BookVO bookVO) {
logger.info("bookVO : " + bookVO.toString());
int affectRowCount = this.bookDao.insert(bookVO);
if(affectRowCount == 1) {//입력이 성공
//xml에서 selectKey에서 세팅된 그 값(max+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> list(Map<String, Object> map){
return this.bookDao.list(map);
}
//책 삭제하기
@Override
public boolean delete(Map<String, Object> map) {
return 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" %>
<!--
BookVO
[bookId=3, title=해리포터, category=소설, price=15000, insertDate=2022-01-28 11:21:43.0]
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" %>
<!--
BookVO
[bookId=3, title=해리포터, category=소설, price=15000, insertDate=2022-01-28 11:21:43.0]
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}" required ></p>
<input type="submit" value="저장" />
<input type="button" value="취소" onclick="javascript:location.href='/detail?bookId=${data.bookId}'" />
</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>
</head>
<body>
<script type="text/javascript">
function fn_create(){
location.href="/create";
}
</script>
<h1>책 목록</h1>
<table border="1">
<thead>
<tr>
<th>제목</th>
<th>카테고리</th>
<th>가격</th>
</tr>
</thead>
<c:forEach var="book" items="${data}">
<tbody>
<tr>
<td>${book.title}</td>
<td>${book.category}</td>
<td>${book.price}</td>
</tr>
</tbody>
</c:forEach>
</table>
<input type="button" value="책입력" onclick="fn_create();" />
</body>
</html>
결과화면 :
검색 시 :
SQL - MyBatis
resultMap은 CLOB이거나 일대다(조인)관계일 때 사용한다.
<?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">
<insert id="insert" parameterType="kr.or.ddit.BookVO">
<!-- * selectKey?
일련번호 처리
마이바티스는 쿼리 실행 시 파라미터를 치환해줌
selectKey 전 : {"title":"검은태양", "category":"드라마","price":10000}
selectKey 후 : {"bookId":1,"title":"검은태양", "category":"드라마","price":10000}
-->
<!-- order="BEFORE"쿼리를 실행하기 전에 -->
<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>
<!-- 책 상세보기 -->
<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=방원}
WHERE 1 = 1은 관습적인 구문. 1 = 1은 항상 TRUE이고
- 조건이 2개 이상일 경우 처음 시작하는 조건은 WHERE로 시작하고 두번째로 시작하는 조건은 AND여야 함
- 매번 첫번째 조건인지 검사하는 것은 번거롭기 때문에 무조건 WHERE 1 = 1을 써둔 후 나머지 조건을 AND로 이어붙임
- 동적쿼리 : 쿼리의 내용이 파라미터가 아니라 마이바티스의 규칙에 의해 변경되는 것
-->
<select id="list" 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>
<!-- 책 삭제 -->
<delete id="delete" parameterType="hashMap">
DELETE FROM BOOK
WHERE BOOK_ID = #{bookId}
</delete>
</mapper>