[Spring] Book 예제

Whatever·2022년 1월 27일
0

Spring(스프링)

목록 보기
5/29

BookController.java

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

BookDao.java

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

BookService.java

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);
}

BookServiceImple.java

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);
	}
}

[VIEW]

create.jsp

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

결과화면 :

detail.jsp

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

결과화면 :

update.jsp

<%@ 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="저장" />&nbsp;
	<input type="button" value="취소" onclick="javascript:location.href='/detail?bookId=${data.bookId}'" />
</form>
</body>
</html>

결과화면 :

list.jsp

<%@ 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이거나 일대다(조인)관계일 때 사용한다.

SQL.xml

book_SQL.xml

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

  

0개의 댓글