04. Spring 웹 사이트 만들어보기!(feat. 스프링 MVC 하루만에 배우기)

min seung moon·2021년 5월 6일
1

1. 책 상세 화면 만들기

01. 책 상세 화면 개요

  • 책 상세 정보를 보여주기 위한 화면을 만든다
  • 브라우저에서 /detail?bookId=1 주소에 접속하면 책 정보를 확인할 수 있는 화면을 보여준다

02. 책 상세 쿼리 작성

  • 책 상세 화면을 조회하는 쿼리를 작성(Select)
select 컬럼들 from 테이블명 where 조건

select title, category, price, insert_date from book where book_id =1
  • RDBMS에서 유의해야 할 것은 RDBMS는 데이터를 집합으로 다룬다는 것
    • 윕의 쿼리에서 where book_d = 1을 제외하면 조건에 해당하는 모든 행이 다 나오게 된다
    • 따라서 반드시 영향을 받는 행을 제한해 줄 필요가 있다
  • 매퍼 XML에 쿼리를 옮긴다
    • src/main/resources/sqlmap/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="hashMap" useGeneratedKeys="true" keyProperty="book_id">
	<![CDATA[
	insert into book
	(title, category, price)
	values
	(#{title}, #{category}, #{price})
	]]>
</insert>
<select id="select_detail" parameterType="hashMap" resultType="hashMap">
	<![CDATA[
		select
		title,
		category,
		price,
		insert_date
		from
		book
		where
		book_id = #{bookId}
	]]>
</select>
</mapper>
  • <select 태그는 조회(SELECT) 쿼리를 실행하기 위한 마이바티스 태그다
  • resultType은 SELECT 쿼리가 실행된 후 반환ㄱ밧을 담을 컨테이너 타입을 말한다

03. 책 상세 DAO 메소드 작성

  • src/main/java/smaple/spring/yse/BookDao.java

    3
package sample.spring.yse;

import java.util.Map;

import org.mybatis.spring.SqlSessionTemplate;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Repository;

@Repository
public class BookDao {
	@Autowired
	SqlSessionTemplate sqlSessionTemplate;
	
	public int insert(Map<String, Object> map) {
		return this.sqlSessionTemplate.insert("book.insert", map);
	}
	
	public Map<String, Object> selectDetail(Map<String, Object> map) {
		return this.sqlSessionTemplate.selectOne("book.select_detail", map);
	}
}
  • sqlSessionTemplate의 selectOne 메소드는 데이터를 한개만 가져올 때 사용한다
    • 만약 쿼리 결과 행 수가 0개면 slectOne 메소드는 null을 반환
    • 쿼리 결과가 여러 개면 TooManyException 예외를 던진다
  • 우리가 작성한 쿼리는 조건이 PK이고 PK는 무조건 행이 유일한(Unique)을 보장하기 때문에 결과는 0개 아니면 1개다, 따라서 selectOne을 사용한다
  • 쿼리 실행 반환값이 Map<String, Object> 타입이다
    • 이는 매퍼 XML의 resultType과 일치해야 한다

04. 책 상세 서비스 클래스 메소드 생성

  • src/main/java/sample/spring/yse/BookServicempl.java
package sample.spring.yse;

import java.util.Map;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

@Service
public class BookServiceImpl implements BookService {
	@Autowired
	BookDao bookDao;
	
	@Override
	public String create(Map<String, Object> map) {
		int affectRowCount = this.bookDao.insert(map);
		if(affectRowCount == 1) {
			return map.get("book_id").toString();
		}
		return null;
	}
	
	@Override
	public Map<String, Object> detail(Map<String, Object> map) {
		return this.bookDao.selectDetail(map);
	}
}
  • 메소드는 시그니쳐 생성도 잊지말자!

06. 쿼리 스트링

  • 책 상세 화면의 URL는 /detail?bookId=1 형식이다
    • 주소창을 통해 파라미터가 서버로 전달되는 형태를 쿼리 스트링(Query String)이라고 부른다
    • HTTP 규격에서 쿼리 스트링은 URL(Uniform Resource Locator)끝에 ?로 시작한다
    • 각 항목은 &로 이어지며, 개발 항목의 키와 값은 =로 구분하게 된다
  • 예를 들어 /sample/test?aa=1&b=2웹 주소가 있다고 하자!
    • URL : /sample/test
    • 쿼리 스트링 : ?aa=1&b=2
    • 쿼리 스트링의 시작 : ?
    • 쿼리 스트링의 항목 구분 : &
    • 쿼리 스트링의 항목들 : a=1, b=2
    • URI : /sample/test?aa=1&b=2

07. 책 상세 컨트롤러 메소드 추가

  • src/main/java/sample/spring/yse/BookController.java
package sample.spring.yse;

import java.util.Map;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
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;

@Controller
public class BookController {
	@Autowired
	BookService bookservice;
	
	@RequestMapping(value = "/create", method = RequestMethod.GET)
	public ModelAndView create() {
		return new ModelAndView("book/create");
	}
	
	@RequestMapping(value = "/create", method = RequestMethod.POST)
	public ModelAndView createPost(@RequestParam Map<String, Object> map) {
		ModelAndView mav = new ModelAndView();
		
		String bookId = this.bookservice.create(map);
		if (bookId == null) {
			mav.setViewName("redirect:/create");
		}else {
			mav.setViewName("redirect:/detail?BookId="+bookId);
		}
		
		return mav;
	}
	
	@RequestMapping(value = "/detail", method = RequestMethod.GET)
	public ModelAndView detail(@RequestParam Map<String, Object> map) {
		Map<String, Object> detailMap = this.bookservice.detail(map);
		
		ModelAndView mav = new ModelAndView();
		mav.addObject("data", detailMap);
		String bookId = map.get("bookId").toString();
		mav.addObject("bookId", bookId);
		mav.setViewName("/book/detail");
		return mav;
	}
}
  • @RequestParam 어노테이션을 의해 쿼리 스트링 파라미터를 읽을 수 있다
    • 스프링은 http 메소드를 구분하지 않고 파라미터를 GET, POST 동일한 방법으로 읽을 수 있게 한다
  • 데이터베이스에서 조회한 결과를 detailMap 변수에 담는다
  • ModelAndView 타입 객체 mav에 뷰로 전달할 데이터를 담는다
    • data 라는 이름으로 쿼리의 결과를 담았다
  • 책의 PK인 bookId도 mav 객체에 담는다
    • 이 때 bookId의 값은 HTTP 쿼리 스트링 파라미터에서 가지고 왔다

08. 책 상세 뷰 작성

  • detail.jsp 뷰 파일 생성한다
  • src/main/webapp/WEB-INF/views/book/detail.jsp
<%@ page language="java" contentType="text/html; charset=UTF-8"
    pageEncoding="UTF-8"%>
<%@ taglib prefix="fmt" uri="http://java.sun.com/jsp/jstl/fmt" %>
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>책 상세</title>
</head>
<body>
	<h1>책 상세</h1>
	<p>제목 : ${ data.title } </p>
	<p>카테고리 ${data.category } </p>
	<p>가격 : <fmt:formatNumber type="number" maxFractionDigits="3" value="${data.price }" /> </p>
	<p>입력일 : <fmt:formatDate value="${data.insert_date }" pattern="yyyy.MM.dd HH:mm:ss"/> </p>
	
	<p>
		<a href="/update?bookId=${bookId }">수정</a>
	</p>
	<form method="post" action="/delete">
		<input type="hidden" name="bookId" value="${bookId }" />
		<input type="submit" value="삭제" />
	</form>
	<p>
		<a href="/list">목록으로</a>
	</p>
</body>
</html>
  • 컨트롤러에서 보내준 데이터를 뷰에 표현하려면 JSTL(JSP Standard Tag Library)을 사용하면된다
    • JSTL은 maven에 사용할 수 있게 설정되어 있다
    • pom.xml
    <dependency>
    	<groupId>javax.servlet</groupId>
            <artifactId>jstl</artifactId>
      	<version>1.2</version>
    </dependency>
  • 컨트롤러에서 전달받은 데이터를 보여주는 방법은 ${데이터이름} 형식으로 사용하면 된다
  • 컨트롤러에서 mav 객체에 data라는 이름으로 데이터베이스의 정보를 넣었기 때문에 ${data.title} 형식으로 사용하게 된다
  • 참고로 컨트롤러 코드는 아래와 같다
    • src/main/java/sample/spring/yse/BookController.java
    mav.addObject("data",datilMap);
  • fmt는 데이터 포멧터 태그 라이브럴리로, 원본 데이터의 형식을 바꿔즈는 역할을 한다
    • fmt의 사용을 위해서 포맷터를 선언한다
  • 숫자를 3자리마다 ,표시하기 위해서는 아래와 같이 한다
    <p>가격 : <fmt:formatNumber type="number" maxFractionDigits="3" value="${data.price }" /> </p>
  • 날짜 형식을 변경하기 위해서는 아래와 같이 한다
    <p>입력일 : <fmt:formatDate value="${data.insert_date }" pattern="yyyy.MM.dd HH:mm:ss"/> </p>
  • 수정 페이지로 이동을 하기 위해서는 html에서 링크를 거는 태그인 a 태그를 이용한다
    • 수정 페이지 화면은 단순히 화면을 보여주기만 하기 때문에 GET 메소드를 사용하고, <a태그를 이용해서 링크를 걸 경유 HTTP 기본 메소드인 GET으로 링크가 걸리기 때문이다
  • bookId의 경우 컨트롤러의 mav에서 data에 담아서 보낸것이 아니라 따로 키와 값을 설정했으므로 ${bookId} 형식으로 가지고 온다
  • 목로긍로 이동하는 기능도 마찬가지로 a 태그로 링크를 걸어둔다
  • 삭제는 조금 다르다
    • 삭제는 데이터를 변경시키기 때문에 http POST 메소드로 하는게 좋다
    • 따라서 form 태그를 이용한다
    • /detail URL에 bookId 파라미터를 함께 보냄으로써 데이터를 삭제하는HTML 태그다
  • <form> 태그의 action 속성은 서버의 URI를 지칭한다
    • action 속성이 생략될 경우 브랑줘의 기본값은 현재 주소다
    • 현재 주소창의 주소와 다른 URI로 서버에 전달을 해야 할 경우 action 속성을 명시적으로 설정하면 된다
  • type="hidden" 태그는 이름처럼 숨은 태그다
    • 사용자에게 보이지는 않지만 서버로 전달하거나 숨겨놓고 값을 사용해야할 때 사용된다

09. 책 상세 화면 확인하기

  • 서버를 클릭하고 ctrl+alt+d를 눌러 디버깅 모드로 시작하거나 ctrl + alt + s를 눌러 서버모드로 시작
  • 브라우저에 https://localhost:8080/detail?bookId=1 주소로 접속하여 화면이 나오는지 확인

2. 책 수정 화면 만들기

01. 책 수정 화면 개요

  • 책 수정 정보를 보여주기 위한 화면을 만든다
  • 브라우저에서 /update?bookId=1 주소에 접속하면 책 정보를 수정할 수 있는 화면을 보여준다

02. 책 수정 화면 컨틀롤러 메소드 추가

  • src/main/java/sample/spring/yse/BookController.java
package sample.spring.yse;

import java.util.Map;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
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;

@Controller
public class BookController {
	@Autowired
	BookService bookservice;
	
	@RequestMapping(value = "/create", method = RequestMethod.GET)
	public ModelAndView create() {
		return new ModelAndView("book/create");
	}
	
	@RequestMapping(value = "/create", method = RequestMethod.POST)
	public ModelAndView createPost(@RequestParam Map<String, Object> map) {
		ModelAndView mav = new ModelAndView();
		
		String bookId = this.bookservice.create(map);
		if (bookId == null) {
			mav.setViewName("redirect:/create");
		}else {
			mav.setViewName("redirect:/detail?BookId="+bookId);
		}
		
		return mav;
	}
	
	@RequestMapping(value = "/detail", method = RequestMethod.GET)
	public ModelAndView detail(@RequestParam Map<String, Object> map) {
		Map<String, Object> detailMap = this.bookservice.detail(map);
		
		ModelAndView mav = new ModelAndView();
		mav.addObject("data", detailMap);
		String bookId = map.get("bookId").toString();
		mav.addObject("bookId", bookId);
		mav.setViewName("/book/detail");
		return mav;
	}
	
	@RequestMapping(value = "/update", method = RequestMethod.GET)
	public ModelAndView update(@RequestParam Map<String, Object> map) {
		Map<String, Object> detailMap = this.bookservice.detail(map);
		
		ModelAndView mav = new ModelAndView();
		mav.addObject("data", detailMap);
		mav.setViewName("/book/update");
		return mav;
	}
}
  • 책 수정 화면은 책 입력화면 + 책 상세 화면이다
    • 책 입력 화면의 화면 형식을 그대로 따라가지만 데이터베이스에 저장된 데이터만 그려주면 된다
    • 따라서 책 데이터는 상세 화면 서비스에 가직 오고 뷰는 책 입력 화면을 복사한다

03. 책 수정 화면 뷰 작성

  • 수정 뷰 화면은 생성화면을 복사한다
    • package explore에서 src => main => wemapp => WEB-INF => views => book => create.jsp 파일을 복사한다
    • ctrl + c 키를 누르거나 우클릭 후 copy 클릭하면 된다


<%@ page pageEncoding="UTF-8" contentType="text/html;charset=utf-8"%>
<html>
	<head>
		<title>책 수정</title>
	</head>
	<body>
		<h1>책 수정</h1>
		<form method="POST">
			<p>제목 : <input type="text" name="title"  value="${data.title }"/> </p>
			<p>카테고리 : <input type="text" name="category" value="${data.category }"/> </p>
			<p>가격 : <input type="text" name="price" value="${data.price }"/> </p>
			<p><input type="submit" value="저장" /> </p>
		</form>
	</body>
</html>
  • 각 항목들 수정하기!

04. 수정화면 확인하기!

3. 책 수정 기능 만들기

01. 책 수정 기능 개요

  • 책 수정을 위한 기능을 만든다
  • 책 수정 화면 /update?bookId=1에서 저장 버튼을클릭하는 경우 기존 책의 정보를 갱신한다

02. 책 수정 기능 쿼리 작성

  • 책 상세 화면을 수정하는 쿼리를 작성한다

    • 데이터베이스에서 데이터를 수정하는 쿼리는 UPDATE 다
    UPDATE 테이블명 SET 컬럼들 where 조건
    
    UPDATE book SET title='제목', category='IT', price=10000 where book_id = 1
  • 수정 쿼리를 작성한다

    • src/main/resource/sqlmap/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="hashMap" useGeneratedKeys="true" keyProperty="book_id">
	<![CDATA[
	insert into book
	(title, category, price)
	values
	(#{title}, #{category}, #{price})
	]]>
</insert>
<select id="select_detail" parameterType="hashMap" resultType="hashMap">
	<![CDATA[
		select
		title,
		category,
		price,
		insert_date
		from
		book
		where
		book_id = #{bookId}
	]]>
</select>
<update id="update" parameterType="hashMap">
	<![CDATA[
		update book set
		title = #{title},
		category = #{category},
		price = #{price}
		where
		book_id = #{bookId}
	]]>
</update>
</mapper>
  • <update 태그는 수정(UPDATE) 쿼리를 실행하기 위한 마이바티스 태그다

03. 책 수정 기능 DAO 메소드 작성

  • src/main/java/sample/spring/yse/BookDao.java
package sample.spring.yse;

import java.util.Map;

import org.mybatis.spring.SqlSessionTemplate;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Repository;

@Repository
public class BookDao {
	@Autowired
	SqlSessionTemplate sqlSessionTemplate;
	
	public int insert(Map<String, Object> map) {
		return this.sqlSessionTemplate.insert("book.insert", map);
	}
	
	public Map<String, Object> selectDetail(Map<String, Object> map) {
		return this.sqlSessionTemplate.selectOne("book.select_detail", map);
	}
	
	public int update(Map<String, Object> map) {
		return this.sqlSessionTemplate.update("book.update", map);
	}
}
  • sqlSessionTemplate 객체의 update 메소드는 insert 메소드와 사용법이 동일하다
    • 첫 번째 파라미터는 쿼리 ID, 두번째 파라미터는 쿼리 파라미터이며 반환값은영향받은 행 수이다
    • 책 갱신 코드의 경우 PK인 biij_id를 ㅏ라미터로 하므로 무조건 성공(1) 혹은 실패(0)가 나오겠지만, 다른 경우에도 늘 그런 것은 아니다
    • RDBMS에서 update 구무은 조건에 일치하는 모든 행을 갱신하기 때문에 영향받은 행 수는 '0' 혹은 '1이상'이 된다

04. 책 수정 기능 서비스 클래스 메소드 생성

  • src/main/java/sample/spring/yse/BookServiceImpl.java


package sample.spring.yse;

import java.util.Map;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

@Service
public class BookServiceImpl implements BookService {
	@Autowired
	BookDao bookDao;
	
	@Override
	public String create(Map<String, Object> map) {
		int affectRowCount = this.bookDao.insert(map);
		if(affectRowCount == 1) {
			return map.get("book_id").toString();
		}
		return null;
	}
	
	@Override
	public Map<String, Object> detail(Map<String, Object> map) {
		return this.bookDao.selectDetail(map);
	}
	
	@Override
	public boolean edit(Map<String, Object> map) {
		int affectRowCount = this.bookDao.update(map);
		return affectRowCount == 1;
	}
}
  • 수정의 경우 입력과는 다르게 PK를 가져오거나 하는 절차가 필요 없으므로 그저 1개의 행이 제대로 영향 받았는지만 검사하면 된다

05. 책 수정 기능 컨트롤러 메소드 추가

  • src/main/java/sample/spring/yse/BookController.java
package sample.spring.yse;

import java.util.Map;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
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;

@Controller
public class BookController {
	@Autowired
	BookService bookservice;
	
	@RequestMapping(value = "/create", method = RequestMethod.GET)
	public ModelAndView create() {
		return new ModelAndView("book/create");
	}
	
	@RequestMapping(value = "/create", method = RequestMethod.POST)
	public ModelAndView createPost(@RequestParam Map<String, Object> map) {
		ModelAndView mav = new ModelAndView();
		
		String bookId = this.bookservice.create(map);
		if (bookId == null) {
			mav.setViewName("redirect:/create");
		}else {
			mav.setViewName("redirect:/detail?BookId="+bookId);
		}
		
		return mav;
	}
	
	@RequestMapping(value = "/detail", method = RequestMethod.GET)
	public ModelAndView detail(@RequestParam Map<String, Object> map) {
		Map<String, Object> detailMap = this.bookservice.detail(map);
		
		ModelAndView mav = new ModelAndView();
		mav.addObject("data", detailMap);
		String bookId = map.get("bookId").toString();
		mav.addObject("bookId", bookId);
		mav.setViewName("/book/detail");
		return mav;
	}
	
	@RequestMapping(value = "/update", method = RequestMethod.GET)
	public ModelAndView update(@RequestParam Map<String, Object> map) {
		Map<String, Object> detailMap = this.bookservice.detail(map);
		
		ModelAndView mav = new ModelAndView();
		mav.addObject("data", detailMap);
		mav.setViewName("/book/update");
		return mav;
	}
	
	@RequestMapping(value = "update", method = RequestMethod.POST)
	public ModelAndView updatePost(@RequestParam Map<String, Object> map) {
		ModelAndView mav = new ModelAndView();
		
		boolean isUpdateSuccess = this.bookservice.edit(map);
		if(isUpdateSuccess) {
			String bookId = map.get("bookId").toString();
			mav.setViewName("redirect:/detail?bookId="+bookId);
		}else {
			mav = this.update(map);
		}
		
		return mav;
	}
}
  • 책 수정 화면에서 책 수정 기능으로 보내주는 파라미터는 총 4개다
    • 하나는 GET 파라미터로 전달되는 bookId다
    /update?bookId=1
    • 나머지 3개는 <form> 태그를 통해전돨되는 title, category, price다
    • 스프링은 http 메소드가 GET인지 POST인지 상관하지 않고 @RequestMapping 어노테이션이 있으면 무조건 파라미터를 넣어준다
    • 따라서 파라미터 map 안에는 4개 데이터가 들어있다
    • map 데이터 예시는 아래와 같다
    {
    	"bookId" : 1,
    	"title" : "제목 수정",
      	"category" : "IT",
          	"price" : "10000"
    }
  • 정상적으로 데이터가 갱신되었을 경우 확인을 위해 상세 페이지로 이동하면 된다
    • 만약 갱신이 안되었을 경우 GET 메소드로 리다이렉트하는 방법도 있겠지만, 갱신 화면을 바로 다시 보여줄 수 도 있다
    • mav = this.update(map)은 BookContoller.update 메소드이다
    • 수정 화면으로 이동

06. 책 수정 기능 확인

4. 책 삭제 기능 만들기

01. 책 삭제 기능 개요

  • 책 삭제를 위한 기능을 만든다
  • 브라웆에서 /delete 주소에 http POST 메소드로 bookId 파라미터를 전달하면 책 정보가 삭제된다

02. 책 삭제 기능 쿼리 작성

  • 책 정보를 삭제하는 쿼리를 작성한다

    • 데이터베이스에서 데이터를 삭제하는 쿼리는 DELETE다
    DELETE FROM 테이블명 WHERE 조건
    
    DELETE FROM book WHERE book_id = 1
  • src/main/resouce/sqlmap/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="hashMap" useGeneratedKeys="true" keyProperty="book_id">
	<![CDATA[
	insert into book
	(title, category, price)
	values
	(#{title}, #{category}, #{price})
	]]>
</insert>
<select id="select_detail" parameterType="hashMap" resultType="hashMap">
	<![CDATA[
		select
		title,
		category,
		price,
		insert_date
		from
		book
		where
		book_id = #{bookId}
	]]>
</select>
<update id="update" parameterType="hashMap">
	<![CDATA[
		update book set
		title = #{title},
		category = #{category},
		price = #{price}
		where
		book_id = #{bookId}
	]]>
</update>
<delete id="delete" parameterType="hashMap">
	<![CDATA[
		delete from book
		where
		book_id = #{bookId}
	]]>
</delete>
</mapper>
  • <delete 태그는 삭제(DELETE) 쿼리를 실행하기 위한 마이바티스 태그다

03. 책 삭제 기능 DAO 메소드 작성

  • src/main/java/sample/spring/yse/BookDao.java
package sample.spring.yse;

import java.util.Map;

import org.mybatis.spring.SqlSessionTemplate;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Repository;

@Repository
public class BookDao {
	@Autowired
	SqlSessionTemplate sqlSessionTemplate;
	
	public int insert(Map<String, Object> map) {
		return this.sqlSessionTemplate.insert("book.insert", map);
	}
	
	public Map<String, Object> selectDetail(Map<String, Object> map) {
		return this.sqlSessionTemplate.selectOne("book.select_detail", map);
	}
	
	public int update(Map<String, Object> map) {
		return this.sqlSessionTemplate.update("book.update", map);
	}
	
	public int delete(Map<String, Object> map) {
		return this.sqlSessionTemplate.delete("book.delete", map);
	}
}
  • sqlSessionTemplate 객체의 delete 메소드는 update 메소드와 사용법이 동일하다
    • 첫 번째 파라미터는 쿼리ID, 두번째 파라미터는 쿼리 파라미터이며 반환 값은 영향은 행 수 이다
    • 책 수정 코드의 경우 PK인 book_id를 파라미터로 하므로 무조건 성공(1) 혹은 실패(0)이 나온다, 다른 경우에도 늘 그런 것은 아니다
    • RDBMS에서 delete 구문은 update 구분과 똑같이 조건에 일치하는 모든 행을 삭제하기 때문에 영향 받은 행 수는 '0' 혹은 '1 이상'이 된다

04. 책 삭제 기능 서비스 클래스 메소드 생성

  • src/main/java/sample/spring/yse/BookServiceImpl.java


package sample.spring.yse;

import java.util.Map;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

@Service
public class BookServiceImpl implements BookService {
	@Autowired
	BookDao bookDao;
	
	@Override
	public String create(Map<String, Object> map) {
		int affectRowCount = this.bookDao.insert(map);
		if(affectRowCount == 1) {
			return map.get("book_id").toString();
		}
		return null;
	}
	
	@Override
	public Map<String, Object> detail(Map<String, Object> map) {
		return this.bookDao.selectDetail(map);
	}
	
	@Override
	public boolean edit(Map<String, Object> map) {
		int affectRowCount = this.bookDao.update(map);
		return affectRowCount == 1;
	}
	
	@Override
	public boolean remove(Map<String, Object> map) {
		int affectRowCount = this.bookDao.delete(map);
		return affectRowCount == 1;
	}
}
  • 삭제의 경우 수정과 동일하개 한개의 행이 제대로 영향 받았는지만검사하면 된다

05. 책 삭제 기능 컨트롤러 메소드 추가

  • src/main/java/sample/spring/yse/BookController.java
package sample.spring.yse;

import java.util.Map;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
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;

@Controller
public class BookController {
	@Autowired
	BookService bookservice;
	
	@RequestMapping(value = "/create", method = RequestMethod.GET)
	public ModelAndView create() {
		return new ModelAndView("book/create");
	}
	
	@RequestMapping(value = "/create", method = RequestMethod.POST)
	public ModelAndView createPost(@RequestParam Map<String, Object> map) {
		ModelAndView mav = new ModelAndView();
		
		String bookId = this.bookservice.create(map);
		if (bookId == null) {
			mav.setViewName("redirect:/create");
		}else {
			mav.setViewName("redirect:/detail?BookId="+bookId);
		}
		
		return mav;
	}
	
	@RequestMapping(value = "/detail", method = RequestMethod.GET)
	public ModelAndView detail(@RequestParam Map<String, Object> map) {
		Map<String, Object> detailMap = this.bookservice.detail(map);
		
		ModelAndView mav = new ModelAndView();
		mav.addObject("data", detailMap);
		String bookId = map.get("bookId").toString();
		mav.addObject("bookId", bookId);
		mav.setViewName("/book/detail");
		return mav;
	}
	
	@RequestMapping(value = "/update", method = RequestMethod.GET)
	public ModelAndView update(@RequestParam Map<String, Object> map) {
		Map<String, Object> detailMap = this.bookservice.detail(map);
		
		ModelAndView mav = new ModelAndView();
		mav.addObject("data", detailMap);
		mav.setViewName("/book/update");
		return mav;
	}
	
	@RequestMapping(value = "update", method = RequestMethod.POST)
	public ModelAndView updatePost(@RequestParam Map<String, Object> map) {
		ModelAndView mav = new ModelAndView();
		
		boolean isUpdateSuccess = this.bookservice.edit(map);
		if(isUpdateSuccess) {
			String bookId = map.get("bookId").toString();
			mav.setViewName("redirect:/detail?bookId="+bookId);
		}else {
			mav = this.update(map);
		}
		
		return mav;
	}
	
	@RequestMapping(value = "/delete", method = RequestMethod.POST)
	public ModelAndView deletePost(@RequestParam Map<String, Object> map) {
		ModelAndView mav = new ModelAndView();
		
		boolean isDeleteSuccess = this.bookservice.remove(map);
		if(isDeleteSuccess) {
			mav.setViewName("redirect:/list");
		}else {
			String bookId = map.get("bookId").toString();
			mav.setViewName("redirect:/detail?bookId="+bookId);
		}
		
		return mav;
	}
}
  • 삭제가 성공했는지 확인한다
  • 삭제가 성공했으면 상세 페이지가 없으므로 목록으로 리다이렉트한다
  • 삭제가 실패하면 상세페이지로 이동

06. 책 삭제 확인


  • 목록 페이지로 전환되는 것을 확인
    • 아직 책 목록 페이지를 맏늘지 않았으므로 404 오류가 나는 것이 정상이다
  • 개발자 도구 network 탭에서 delete 항목을 선택해보자
    • 상세보기 가장 아래에 보면 From data 섹션이 보이고 bookId : 2이 보인다
    • 바로 http POST 파라미터다
    • 이처럼 브라우저에서 서버로 전달한 파라미터는 브라우저를 통해 확인할 수 있다

5. 책 목록 만들기

01. 책 목록 개요

  • 책 목록 보여주기 위한 기능을 만든다
  • 브라우저에서 /list 주소에 접속하면 책 목록이 보여진다
  • 책목록은 최신순으로 보여지게 된다

02. 책 목록 쿼리 작성

  • src/main/resources/sqlmap/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="hashMap" useGeneratedKeys="true" keyProperty="book_id">
	<![CDATA[
	insert into book
	(title, category, price)
	values
	(#{title}, #{category}, #{price})
	]]>
</insert>
<select id="select_detail" parameterType="hashMap" resultType="hashMap">
	<![CDATA[
		select
		title,
		category,
		price,
		insert_date
		from
		book
		where
		book_id = #{bookId}
	]]>
</select>
<update id="update" parameterType="hashMap">
	<![CDATA[
		update book set
		title = #{title},
		category = #{category},
		price = #{price}
		where
		book_id = #{bookId}
	]]>
</update>
<delete id="delete" parameterType="hashMap">
	<![CDATA[
		delete from book
		where
		book_id = #{bookId}
	]]>
</delete>
<select id="select_list" parameterType="hashMap" resultType="hashMap">
	<![CDATA[
		select
		book_id,
		title,
		category,
		price,
		insert_date
		from
		book
		order by insert_date desc
	]]>
</select>
</mapper>
  • 목록의 경우에도 resultType은 한 행을 담는 타입을 지정한다(책 상세와 동일)
  • 쿼리에서 정렬을 하기 위해선느 order by 구문을 사용
    • 작은 순서부터 정열하기 위해서는 asc 큰 순서부터 정렬은 desc
    • asc는 날짜의 경우 오래된 순부터 정렬된다
    • 그래서 desc로 최신 날짜로 정렬

03. 책 목록 DAO 메소드 작성

  • 책 목록 데이터베이스에 접속하는 메소드를 작성
  • src/main/java/sample/spring/yse/BookDao.java
package sample.spring.yse;

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;

@Repository
public class BookDao {
	@Autowired
	SqlSessionTemplate sqlSessionTemplate;
	
	public int insert(Map<String, Object> map) {
		return this.sqlSessionTemplate.insert("book.insert", map);
	}
	
	public Map<String, Object> selectDetail(Map<String, Object> map) {
		return this.sqlSessionTemplate.selectOne("book.select_detail", map);
	}
	
	public int update(Map<String, Object> map) {
		return this.sqlSessionTemplate.update("book.update", map);
	}
	
	public int delete(Map<String, Object> map) {
		return this.sqlSessionTemplate.delete("book.delete", map);
	}
	
	public List<Map<String, Object>> selectList(Map<String, Object> map) {
		return this.sqlSessionTemplate.selectList("book.select_list", map);
	}
}
  • 쿼리 결과 목록으로 받기 위해서는 sqlSessionTemplate.selectList를 사용할 수 있다
    • 첫번째 파라미터는 쿼리ID, 두번째 ID는 쿼리 파라미터다
  • sqlSessionTemplate.sessionList 메소드는 결과 집합 목록을반환한다
    • 따라서 결과 집합 타입인 Map<String, Object>의 목록 List 타입으로 읽어 들일 수 있다

04. 책 목록 서비스 클래스 메소드 생성

  • src/main/java/sample/spring/yse/BookServiceImpl.java


package sample.spring.yse;

import java.util.List;
import java.util.Map;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

@Service
public class BookServiceImpl implements BookService {
	@Autowired
	BookDao bookDao;
	
	@Override
	public String create(Map<String, Object> map) {
		int affectRowCount = this.bookDao.insert(map);
		if(affectRowCount == 1) {
			return map.get("book_id").toString();
		}
		return null;
	}
	
	@Override
	public Map<String, Object> detail(Map<String, Object> map) {
		return this.bookDao.selectDetail(map);
	}
	
	@Override
	public boolean edit(Map<String, Object> map) {
		int affectRowCount = this.bookDao.update(map);
		return affectRowCount == 1;
	}
	
	@Override
	public boolean remove(Map<String, Object> map) {
		int affectRowCount = this.bookDao.delete(map);
		return affectRowCount == 1;
	}
	
	@Override
	public List<Map<String, Object>> list(Map<String, Object> map) {
		return this.bookDao.selectList(map);
	}
}
  • 책 목록 DAO를 곧바로 리턴하는서비스 메소드를 만든다

05. 책 목록 컨트롤러 메소드 추가

  • src/main/java/sample/spring/yse/BokkController.java
package sample.spring.yse;

import java.util.List;
import java.util.Map;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
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;

@Controller
public class BookController {
	@Autowired
	BookService bookservice;
	
	@RequestMapping(value = "/create", method = RequestMethod.GET)
	public ModelAndView create() {
		return new ModelAndView("book/create");
	}
	
	@RequestMapping(value = "/create", method = RequestMethod.POST)
	public ModelAndView createPost(@RequestParam Map<String, Object> map) {
		ModelAndView mav = new ModelAndView();
		
		String bookId = this.bookservice.create(map);
		if (bookId == null) {
			mav.setViewName("redirect:/create");
		}else {
			mav.setViewName("redirect:/detail?bookId="+bookId);
		}
		
		return mav;
	}
	
	@RequestMapping(value = "/detail", method = RequestMethod.GET)
	public ModelAndView detail(@RequestParam Map<String, Object> map) {
		Map<String, Object> detailMap = this.bookservice.detail(map);
		
		ModelAndView mav = new ModelAndView();
		mav.addObject("data", detailMap);
		String bookId = map.get("bookId").toString();
		mav.addObject("bookId", bookId);
		mav.setViewName("/book/detail");
		return mav;
	}
	
	@RequestMapping(value = "/update", method = RequestMethod.GET)
	public ModelAndView update(@RequestParam Map<String, Object> map) {
		Map<String, Object> detailMap = this.bookservice.detail(map);
		
		ModelAndView mav = new ModelAndView();
		mav.addObject("data", detailMap);
		mav.setViewName("/book/update");
		return mav;
	}
	
	@RequestMapping(value = "update", method = RequestMethod.POST)
	public ModelAndView updatePost(@RequestParam Map<String, Object> map) {
		ModelAndView mav = new ModelAndView();
		
		boolean isUpdateSuccess = this.bookservice.edit(map);
		if(isUpdateSuccess) {
			String bookId = map.get("bookId").toString();
			mav.setViewName("redirect:/detail?bookId="+bookId);
		}else {
			mav = this.update(map);
		}
		
		return mav;
	}
	
	@RequestMapping(value = "/delete", method = RequestMethod.POST)
	public ModelAndView deletePost(@RequestParam Map<String, Object> map) {
		ModelAndView mav = new ModelAndView();
		
		boolean isDeleteSuccess = this.bookservice.remove(map);
		if(isDeleteSuccess) {
			mav.setViewName("redirect:/list");
		}else {
			String bookId = map.get("bookId").toString();
			mav.setViewName("redirect:/detail?bookId="+bookId);
		}
		
		return mav;
	}
	
	@RequestMapping(value = "list")
	public ModelAndView list(@RequestParam Map<String, Object> map) {
		List<Map<String, Object>> list = this.bookservice.list(map);
		
		ModelAndView mav = new ModelAndView();
		mav.addObject("data", list);
		mav.setViewName("/book/list");
		return mav;
	}
}
  • 책 목록을 데이터베이스에서 가지고 온다
  • 데이터를 뷰에 전달할 수 있게 mav 객체에 넣는다
  • /book/list 뷰를 리턴한다

06. 책 목록 뷰 작성

  • 책 목록 뷰를 생성한다
    • src => main => sebapp => WEB-INF => views => book 우클릭 후 new -> file을 선택하고 파일 이름을 list.jsp로 생성하면 된다
  • src/main/webapp/WEB-INF-views/book/list.jsp

<%@ page language="java" contentType="text/html; charset=UTF-8"
    pageEncoding="UTF-8"%>
<%@ taglib prefix="fmt" uri="http://java.sun.com/jsp/jstl/fmt" %>
<%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c" %>
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>책 목록</title>
</head>
<body>
	<h1>책 목록</h1>
	<table>
		<thead>
			<tr>
				<th>제목</th>
				<th>카테고리</th>
				<th>가격</th>
			</tr>
		</thead>
		<tbody>
			<c:forEach var="row" items="${data}">
				<tr>
					<td>
						<a href="/detail?bookId=${row.book_id}">${row.title}</a>
					</td>
					<td>${row.category }</td>
					<td><fmt:formatNumber type="number" maxFractionDigits="3" value="${row.price}"/></td>
				</tr>
			</c:forEach>
		</tbody>
	</table>
	<p>
		<a href="/create">생성</a>
	</p>
</body>
</html>
  • JSTL에서 반복문을 사용하기 위해서는 <c:forEach 구문을 사용할 수 있다
    • <c: 태그를 사용하기 위해서는 태그 라이브러리를 선언해야 한다
  • 반복문은 <c:forEach var="row" items="${data}>처럼 사용
    • items 에는 커늩롤러에서 전달받은 데이터를 넣어준다
      • 반복 가능한 객체면 어디서 생성한객체든 무관
    • var는 목록의 한 행(row)을 나타내는ㄴ 변수명을 넣으면 된다

07. 책 목록 화면 확인

  • http://localhost:8090/list
  • 현재 데이터가 없으므로 빈 목록이 나오는 것이 정상
  • 생성을 눌러서 데이터 삽입!


6. 책 검색 기능 추가하기

01. 책 검색 기능 개요

  • 책 목록을 검색하기 위한 기능을 만든다
  • 목록 화면에서 검색어를 입력하고 검색 버튼 클릭하면 검색 주소로 이동한다
  • 검색 주소는 /list?keyword=검색어 형식이다

02. 책 검색 쿼리 작성

  • 기존의 책 목록 쿼리 select_list를 수정해서 검색 기능을 추가한다
  • src/main/resource/sqkmap/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="hashMap" useGeneratedKeys="true" keyProperty="book_id">
	<![CDATA[
	insert into book
	(title, category, price)
	values
	(#{title}, #{category}, #{price})
	]]>
</insert>
<select id="select_detail" parameterType="hashMap" resultType="hashMap">
	<![CDATA[
		select
		title,
		category,
		price,
		insert_date
		from
		book
		where
		book_id = #{bookId}
	]]>
</select>
<update id="update" parameterType="hashMap">
	<![CDATA[
		update book set
		title = #{title},
		category = #{category},
		price = #{price}
		where
		book_id = #{bookId}
	]]>
</update>
<delete id="delete" parameterType="hashMap">
	<![CDATA[
		delete from book
		where
		book_id = #{bookId}
	]]>
</delete>
<select id="select_list" parameterType="hashMap" resultType="hashMap">
	<![CDATA[
		select
		book_id,
		title,
		category,
		price,
		insert_date
		from
		book
		where 1 = 1
	]]>
	<if test="keyword != null and keyword != ''">
		and (title like CONCAT('%',#{keyword},'%') or category like CONCAT('%',#{keyword},'%'))
	</if>
	order by insert_date desc
</select>
</mapper>
  • 삭제된 코드는 아래와 같다
order by insert_date desc
]]>
  • 추가된 코드는 아래와 같다
where 1 = 1
	]]>
	<if test="keyword != null and keyword != ''">
		and (title like CONCAT('%',#{keyword},'%') or category like CONCAT('%',#{keyword},'%'))
	</if>
	order by insert_date desc
  • where 1 = 1은 관습적인 구문이다
    • 1=1은 늘 참이기 때문에 where 절을 나타낼 때 사용한다
    • 조건이 2개 이상일 경우 처음에시작하는 조건은 where로 시작하고 두번째로시작하는 조건은 and 여야 한다
    • 매 번 첫번째 조건인지 검사하는 것은 번거롭기 때문에 무조건 where 1 = 1을 써 둔 후 나머지 조건들은 and로 이어붙이는 것이다
  • <if문은 마이바티스에서 조건을 나타낸다 test는 조건 규칙을 나타내는 항목이다
    <if test="keyword != null and keyword != ''">
    • 만약 키워드가 있으면 <if> ~ </if> 안의 쿼리문이 데이터 베이스 쿼리에 포함된다
    • 이 처럼 쿼리의 내용이 파라미터가 아니라 마이바티스의 규칙에 의해서 변경되는 것을 동적 쿼리(Dynamic Query)라고 부른다
  • SQL 쿼리 조건에서 포함을 나타내는 구문 like다
    • title like '검색어%' 일 경우는 검색어로 시작한다는 뜻이다
    • title like '%검색어' 일 경우는 검색어로 끝난다는 뜻이다
    • title like '%검색어%' 일 경우는 검색어를 포함한다는 뜻이다
    • 마이바티스에서 쿼리 파라미터에 '표시를 붙이지 않기기 때문에 title like '%#{keyword}%' 형식으로 표기하기는 어렵다, 따라서 마리아디비의 CONCAT함수를 이용해서 문자열을 이어 붙인다
      • 만약 keyword 파라미터의 값이 키워드 일 때 CONCAT('%', #{keyword},'%') 구문은 '%키워드%' 형태로 바뀐다
    • SQL 쿼리 조건에서 or는 "또는"을 나타낸는 구문이다
      • A or B 일 때 A 혹은 B 둘 중 하나만 만족하면 참(true)를 반환
  • <if>문 안의 내용은 제목이나 분류 안에 키워드가 있을 경우를 조건으로 한다는 뜻이다
    and (title like CONCAT('%',#{keyword},'%') or category like CONCAT('%',#{keyword},'%'))
    • 만약 KEYWORD 파라미터 값이 키워드라면 위 구문은 아래와 같은 뜻이 된다
    and (title like '%키워드%' or category like '%키워드%')
    • 따라서 제목에 키워드를 포함하거나, 분류에 키워드를 포함하면 참이다
  • CDATA를 닫는 항목 ]]>가 orderby 항목 아래에서 위로 변경되었다
    • CDATA항목 안에 <if 등의 마이바티스 구무은 RAW 문자열로 취급하여 해석되지 않기 때문에 마이바티스 구문을 실행하기 위해 CDATA 섹션을 닫아준 것이다

03. 책 검색 서비스 레이어

  • 검색어를 전달하는기능만 추가되므로 책 검색 서비스 레이어는 수정하지 않는다

04. 책 검색 컨트롤러 메소드 추가

  • 컨트롤러에 검색 파라미터를 처리하는 부분을 추가한다
  • 키워드 파라미터가 있다면 뷰의 검색 상자에 보여지게 할 것이다
  • src/main/java/sample/spring/yse/BookController.java
    • list 메소드에서 뷰로 키워드 데이터를 전달하는 부분을 수정한다
package sample.spring.yse;

import java.util.List;
import java.util.Map;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
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;

@Controller
public class BookController {
	@Autowired
	BookService bookservice;
	
	@RequestMapping(value = "/create", method = RequestMethod.GET)
	public ModelAndView create() {
		return new ModelAndView("book/create");
	}
	
	@RequestMapping(value = "/create", method = RequestMethod.POST)
	public ModelAndView createPost(@RequestParam Map<String, Object> map) {
		ModelAndView mav = new ModelAndView();
		
		String bookId = this.bookservice.create(map);
		if (bookId == null) {
			mav.setViewName("redirect:/create");
		}else {
			mav.setViewName("redirect:/detail?bookId="+bookId);
		}
		
		return mav;
	}
	
	@RequestMapping(value = "/detail", method = RequestMethod.GET)
	public ModelAndView detail(@RequestParam Map<String, Object> map) {
		Map<String, Object> detailMap = this.bookservice.detail(map);
		
		ModelAndView mav = new ModelAndView();
		mav.addObject("data", detailMap);
		String bookId = map.get("bookId").toString();
		mav.addObject("bookId", bookId);
		mav.setViewName("/book/detail");
		return mav;
	}
	
	@RequestMapping(value = "/update", method = RequestMethod.GET)
	public ModelAndView update(@RequestParam Map<String, Object> map) {
		Map<String, Object> detailMap = this.bookservice.detail(map);
		
		ModelAndView mav = new ModelAndView();
		mav.addObject("data", detailMap);
		mav.setViewName("/book/update");
		return mav;
	}
	
	@RequestMapping(value = "update", method = RequestMethod.POST)
	public ModelAndView updatePost(@RequestParam Map<String, Object> map) {
		ModelAndView mav = new ModelAndView();
		
		boolean isUpdateSuccess = this.bookservice.edit(map);
		if(isUpdateSuccess) {
			String bookId = map.get("bookId").toString();
			mav.setViewName("redirect:/detail?bookId="+bookId);
		}else {
			mav = this.update(map);
		}
		
		return mav;
	}
	
	@RequestMapping(value = "/delete", method = RequestMethod.POST)
	public ModelAndView deletePost(@RequestParam Map<String, Object> map) {
		ModelAndView mav = new ModelAndView();
		
		boolean isDeleteSuccess = this.bookservice.remove(map);
		if(isDeleteSuccess) {
			mav.setViewName("redirect:/list");
		}else {
			String bookId = map.get("bookId").toString();
			mav.setViewName("redirect:/detail?bookId="+bookId);
		}
		
		return mav;
	}
	
	@RequestMapping(value = "list")
	public ModelAndView list(@RequestParam Map<String, Object> map) {
		List<Map<String, Object>> list = this.bookservice.list(map);
		
		ModelAndView mav = new ModelAndView();
		mav.addObject("data", list);
		
		if(map.containsKey("keyword")) {
			mav.addObject("keyword", map.get("keyword"));
		}
			
		mav.setViewName("/book/list");
		
		return mav;
	}
}
  • 수정된 부분은 아래와 같다
if(map.containsKey("keyword")) {
			mav.addObject("keyword", map.get("keyword"));
}
  • 목록 페이지에는 keyword HTTP 파라미터가 있을 수도 있고, 없을 수도 있다, 따라서 파라미터가 있는지 검사가 필요
  • 파라미터가 있다면 뷰에 keyword를 전달

05. 책 검색 뷰 수정

  • 책 검색 뷰를 수정해서 검색창이 보이도록 한다
  • src/main/webapp/WEB-INF/views/book/list.jsp
<%@ page language="java" contentType="text/html; charset=UTF-8"
    pageEncoding="UTF-8"%>
<%@ taglib prefix="fmt" uri="http://java.sun.com/jsp/jstl/fmt" %>
<%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c" %>
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>책 목록</title>
</head>
<body>
	<h1>책 목록</h1>
	<p>
		<form>
			<input type="text" placeholder="검색" name="keyword" value="${keyword}" />
			<input type="submit" value="검색" />
		</form>
	</p>
	<table>
		<thead>
			<tr>
				<th>제목</th>
				<th>카테고리</th>
				<th>가격</th>
			</tr>
		</thead>
		<tbody>
			<c:forEach var="row" items="${data}">
				<tr>
					<td>
						<a href="/detail?bookId=${row.book_id}">${row.title}</a>
					</td>
					<td>${row.category }</td>
					<td><fmt:formatNumber type="number" maxFractionDigits="3" value="${row.price}"/></td>
				</tr>
			</c:forEach>
		</tbody>
	</table>
	<p>
		<a href="/create">생성</a>
	</p>
</body>
</html>
  • 수정된 부분은 아래와 같다
	<p>
		<form>
			<input type="text" placeholder="검색" name="keyword" value="${keyword}" />
			<input type="submit" value="검색" />
		</form>
	</p>
  • 검색창 영역을 추가한다

06. 책 검색 기능 확인

profile
아직까지는 코린이!

1개의 댓글

comment-user-thumbnail
2021년 12월 14일

정말 많은 도움이 돼요 감사합니다!

답글 달기