Study Web Development

3월 3일

이전으로

Spring

4. 스프링 MVC

국제화 처리

  1. servlet-context.xml<beans:beans> 태그를 다음처럼 수정
<beans:beans xmlns="http://www.springframework.org/schema/mvc"
	xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
	xmlns:beans="http://www.springframework.org/schema/beans"
	xmlns:p="http://www.springframework.org/schema/p"
	xmlns:context="http://www.springframework.org/schema/context"
	xsi:schemaLocation="http://www.springframework.org/schema/mvc https://www.springframework.org/schema/mvc/spring-mvc.xsd
		http://www.springframework.org/schema/beans https://www.springframework.org/schema/beans/spring-beans.xsd
		http://www.springframework.org/schema/context https://www.springframework.org/schema/context/spring-context.xsd">
  1. servlet-context.xml<beans:beans> 태그 사이에 다음의 내용을 추가
	<!-- localChangeInterceptor를 이용한 로케일 처리 -->
	<beans:bean id="localeChangeInterceptor" class="org.springframework.web.servlet.i18n.LocaleChangeInterceptor" p:paramName="language"/>
	<interceptors>
		<interceptor>
			<mapping path="/login/login.do"/>
			<beans:ref bean="localeChangeInterceptor"/>
		</interceptor>
	</interceptors>
  1. index.jsp<body> 태그 사이에 다음 내용을 추가
<a href="${pageContext.request.contextPath}/login/login.do?language=ko">/login/login.do?language=ko</a><br>
<a href="${pageContext.request.contextPath}/login/login.do?language=en">/login/login.do?language=en</a><br>
----------------------------<br>

파일 다운로드 처리

  1. WEB-INF에 새 파일 file.txt 생성
  2. 새 패키지 kr.spring.ch10.view 생성하고 새 클래스 DownloadView 생성
package kr.spring.ch10.view;

import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.net.URLEncoder;
import java.util.Map;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import org.springframework.util.FileCopyUtils;
import org.springframework.web.servlet.view.AbstractView;

public class DownloadView extends AbstractView {

	public DownloadView() {
		setContentType("application/download; charset=utf-8");
	}

	@Override
	protected void renderMergedOutputModel(Map<String, Object> model,
			HttpServletRequest request, HttpServletResponse response)
			throws Exception {
		File file = (File) model.get("downloadFile");

		response.setContentType(getContentType());
		response.setContentLength((int) file.length());

		String userAgent = request.getHeader("User-Agent");
		boolean ie = userAgent.indexOf("MSIE") > -1;
		String fileName = null;
		if (ie) {
			fileName = URLEncoder.encode(file.getName(), "utf-8");
		} else {
			fileName = new String(file.getName().getBytes("utf-8"),
					"iso-8859-1");
		}
		response.setHeader("Content-Disposition", "attachment; filename=\""
				+ fileName + "\";");
		response.setHeader("Content-Transfer-Encoding", "binary");
		OutputStream out = response.getOutputStream();
		FileInputStream fis = null;
		try {
			fis = new FileInputStream(file);
			FileCopyUtils.copy(fis, out);
		} finally {
			if (fis != null)
				try {
					fis.close();
				} catch (IOException ex) {
				}
		}
		out.flush();
	}

}
  1. 새 패키지 kr.spring.ch10.controller 생성하고 새 클래스 DownloadController 생성
package kr.spring.ch10.controller;

import java.io.File;

import org.springframework.beans.BeansException;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.context.WebApplicationContext;
import org.springframework.web.servlet.ModelAndView;

@Controller
public class DownloadController implements ApplicationContextAware {
	private WebApplicationContext context;
	
	@RequestMapping("/file.do")
	public ModelAndView download() throws Exception {
		// 상대 경로를 통해서 절대 경로 구하기
		String path = context.getServletContext().getRealPath("/WEB-INF/file.txt");
		
		File downloadFile = new File(path);
		
		return new ModelAndView("download", "downloadFile", downloadFile); // view 이름, 속성명, 속성값을 생성자 인자로 전달
	}
	
	@Override
	public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
		this.context = (WebApplicationContext)applicationContext;
	}
}
  1. servlet-context.xml<beans:beans> 태그 사이에 다음의 내용을 추가
	<!-- view의 이름과 bean의 이름이 같으면 해당 bean을 view로 호출 -->
	<beans:bean id="viewResolver" class="org.springframework.web.servlet.view.BeanNameViewResolver" p:order="0"/>
	
	<!-- 파일 다운로드 -->
	<beans:bean class="kr.spring.ch10.controller.DownloadController"/>
	<beans:bean id="download" class="kr.spring.ch10.view.DownloadView"/>
  1. servlet-context.xml<beans:beans> 태그 사이의 내용을 다음처럼 수정
	<!-- Resolves views selected for rendering by @Controllers to .jsp resources in the /WEB-INF/views directory -->
	<beans:bean class="org.springframework.web.servlet.view.InternalResourceViewResolver">
		<beans:property name="prefix" value="/WEB-INF/views/" />
		<beans:property name="suffix" value=".jsp" />
		<beans:property name="order" value="1"/> <!-- View Resolver가 2개이므로 충돌하지 않도록 우선순위를 서로 다르게 지정 -->
	</beans:bean>
  1. index.jsp<body> 태그 사이에 다음 내용을 추가
<a href="${pageContext.request.contextPath}/file.do">DownloadController</a><br>

어노테이션을 이용한 유효성 검증

  1. pom.xml에서 <dependencies> 태그 사이에 다음의 내용을 추가
		<dependency>
			<groupId>org.hibernate</groupId>
			<artifactId>hibernate-validator</artifactId>
			<version>6.0.18.Final</version>
		</dependency>
  1. 새 패키지 kr.spring.ch11.vo 생성하고 새 클래스 MemberVO 생성
package kr.spring.ch11.vo;

import javax.validation.constraints.Email;
import javax.validation.constraints.NotEmpty;
import javax.validation.constraints.Pattern;
import javax.validation.constraints.Size;

import org.hibernate.validator.constraints.Range;

public class MemberVO {
	@Pattern(regexp="^[0-9a-zA-Z]+$") // 정규표현식으로 패턴 검사
	private String id;
	@Size(min=4, max=10) // 문자열의 길이 지정
	private String password;
	@NotEmpty
	private String name;
	@Range(min=1, max=200)
	private int age;
	@Email @NotEmpty
	private String email;
	
	public String getId() {
		return id;
	}
	public void setId(String id) {
		this.id = id;
	}
	public String getPassword() {
		return password;
	}
	public void setPassword(String password) {
		this.password = password;
	}
	public String getName() {
		return name;
	}
	public void setName(String name) {
		this.name = name;
	}
	public int getAge() {
		return age;
	}
	public void setAge(int age) {
		this.age = age;
	}
	public String getEmail() {
		return email;
	}
	public void setEmail(String email) {
		this.email = email;
	}
	
	@Override
	public String toString() {
		return "MemberVO [id=" + id + ", password=" + password + ", name=" + name + ", age=" + age + ", email=" + email
				+ "]";
	}
}
  1. validation.properties 하단에 다음의 내용을 추가
# 어노테이션을 이용한 유효성 검증시에는 어노테이션명.필드명 형식으로 에러 코드 사용
NotEmpty.id=아이디는 필수 항목
Pattern.id=아이디는 영문자와 숫자만 입력 가능
Size.password=비밀번호는 4자 이상 10자 이하로 입력
NotEmpty.name=이름은 필수 항목
Range.age=나이는 1세 이상 200세 이하로 입력
typeMismatch.age=나이는 숫자만 입력 가능
NotEmpty.email=이메일은 필수 항목
Email.email=이메일 형식에 맞게 입력
  1. 새 패키지 kr.spring.ch11.controller 생성하고 새 클래스 MemberWriteController 생성
package kr.spring.ch11.controller;

import javax.validation.Valid;

import org.springframework.stereotype.Controller;
import org.springframework.validation.BindingResult;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.ModelAttribute;
import org.springframework.web.bind.annotation.PostMapping;

import kr.spring.ch11.vo.MemberVO;

@Controller
public class MemberWriteController {
	// 자바빈 초기화
	@ModelAttribute("vo")
	public MemberVO initCommand() {
		return new MemberVO();
	}
	
	// 폼 호출
	@GetMapping("/member/write.do")
	public String form() {
		return "member/write";
	}
	
	// 폼에서 전송된 데이터 처리
	@PostMapping("/member/write.do")
	public String submit(@ModelAttribute("vo") @Valid MemberVO memberVO, BindingResult result) {
		System.out.println("전송된 데이터 : " +  memberVO);
		
		// 전송된 데이터 유효성 검증 결과 오류가 있으면 폼 호출
		if(result.hasErrors()) {
			return form();
		}
		
		return "redirect:/index.jsp";
	}
}
  1. views 폴더에 하위 폴더로 member 생성하고 새 JSP 파일 write.jsp 생성
<%@ page language="java" contentType="text/html; charset=UTF-8"
    pageEncoding="UTF-8"%>
<%@ taglib uri="http://www.springframework.org/tags/form" prefix="form" %>
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>회원 가입</title>
</head>
<body>
<form:form modelAttribute="vo">
	<form:errors element="div"/>
	<ul>
		<li>
			<form:label path="id">아이디</form:label>
			<form:input path="id"/>
			<form:errors path="id"/>
		</li>
		<li>
			<form:label path="password">비밀번호</form:label>
			<form:password path="password"/>
			<form:errors path="password"/>
		</li>
		<li>
			<form:label path="name">이름</form:label>
			<form:input path="name"/>
			<form:errors path="name"/>
		</li>
		<li>
			<form:label path="age">나이</form:label>
			<form:input path="age"/>
			<form:errors path="age"/>
		</li>
		<li>
			<form:label path="email">이메일</form:label>
			<form:input path="email"/>
			<form:errors path="email"/>
		</li>
	</ul>
	<div>
		<form:button>전송</form:button>
	</div>
</form:form>
</body>
</html>
  1. servlet-context.xml<beans:beans> 태그 사이에 다음의 내용을 추가
	<!-- 어노테이션을 이용한 유효성 검증 -->
	<beans:bean class="kr.spring.ch11.controller.MemberWriteController"/>
  1. index.jsp<body> 태그 사이에 다음 내용을 추가
<a href="${pageContext.request.contextPath}/member/write.do">MemberWriteController</a><br>
정규표현식 예제
정규표현식 의미 적용
\d{6}-[1234]\d{6} 주민등록번호 900101-1234567
\w+@\w+\.\w+ 이메일 test@test.com

JSON 문자열 처리

  1. pom.xml에서 <dependencies> 태그 사이에 다음의 내용을 추가
		<dependency>
			<groupId>com.fasterxml.jackson.core</groupId>
			<artifactId>jackson-databind</artifactId>
			<version>2.9.5</version>
		</dependency>
  1. 새 패키지 kr.spring.ch12.controller 생성하고 새 클래스 PageRank 생성
package kr.spring.ch12.controller;

public class PageRank {
	private int rank;
	private String page;
	
	public PageRank() {}
	public PageRank(int rank, String page) {
		this.rank = rank;
		this.page = page;
	}
	
	public int getRank() {
		return rank;
	}
	public void setRank(int rank) {
		this.rank = rank;
	}
	public String getPage() {
		return page;
	}
	public void setPage(String page) {
		this.page = page;
	}
}
  1. 새 클래스 PageReportController 생성
package kr.spring.ch12.controller;

import java.util.ArrayList;
import java.util.List;

import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;

@Controller
public class PageReportController {
	@RequestMapping("/pageJsonReport.do")
	@ResponseBody // @ResponseBody 어노테이션은 응답 화면을 생성
	public List<PageRank> jsonReport() { // 잭슨 라이브러리 사용시 메서드가 반환한 List나 Map, 자바빈 객체를 JSON 문자열로 변환
		List<PageRank> pageRanks = new ArrayList<PageRank>();
		pageRanks.add(new PageRank(1, "/board/list.do"));
		pageRanks.add(new PageRank(2, "/board/detail.do"));
		pageRanks.add(new PageRank(3, "/board/write.do"));
		pageRanks.add(new PageRank(4, "/board/update.do"));
		
		return pageRanks;
	}
}
  1. servlet-context.xml<beans:beans> 태그 사이에 다음의 내용을 추가
	<!-- JSON 파싱 -->
	<beans:bean class="kr.spring.ch12.controller.PageReportController"/>
  1. index.jsp<body> 태그 사이에 다음 내용을 추가
<a href="${pageContext.request.contextPath}/pageJsonReport.do">PageReportController</a><br>

REST API

  1. 새 패키지 kr.spring.ch13.vo 생성하고 새 클래스 MemberVO 생성
package kr.spring.ch13.vo;

public class MemberVO {
	private String id;
	private String name;
	
	public String getId() {
		return id;
	}
	public void setId(String id) {
		this.id = id;
	}
	public String getName() {
		return name;
	}
	public void setName(String name) {
		this.name = name;
	}	
}
  1. 새 패키지 kr.spring.ch13.controller 생성하고 새 클래스 RestMainController 생성
package kr.spring.ch13.controller;

import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import kr.spring.ch13.vo.MemberVO;

/*
 * @RestController는 @ResponseBody와 @Controller 어노테이션의 조합
 * 일반적으로 REST 컨트롤러를 생성할 때 사용
 */

@RestController
public class RestMainController {
	@RequestMapping("/member") // REST 스타일에서는 요청 URL에 확장자를 사용하지 않음
	public MemberVO getMember() { // 잭슨 라이브러리 사용시 메서드가 반환한 List나 Map, 자바빈 객체를 JSON 문자열로 변환
		MemberVO vo = new MemberVO();
		vo.setId("test");
		vo.setName("실험");
		
		return vo;
	}
	
	/*
	 * @PathVariable 어노테이션은 URL에 포함된 값을 처리하기 위해 사용
	 * {id}는 값이 변수임을 의미하며, @PathVariable은 URL의 변수 {id}의 값이 메서드 인자 id에 바인딩되도록 함
	 */
	@RequestMapping("/member/id/{id}/name/{name}")
	public MemberVO getMember2(@PathVariable String id, @PathVariable String name) {
		MemberVO vo = new MemberVO();
		vo.setId(id);
		vo.setName(name);
		
		return vo;
	}
}
  1. servlet-context.xml<beans:beans> 태그 사이에 다음의 내용을 추가
	<!-- @RestController 사용 -->
	<beans:bean class="kr.spring.ch13.controller.RestMainController"/>
  1. web.xml<servlet-mapping> 태그 사이의 내용을 다음처럼 수정
		<url-pattern>/</url-pattern>
  1. index.jsp<body> 태그 사이에 다음 내용을 추가
<a href="${pageContext.request.contextPath}/member">RestMainController</a><br>
<a href="${pageContext.request.contextPath}/member/id/sleepy/name/도로롱">/member/id/sleepy/name/도로롱</a><br>

5. JDBC

  1. 새 Spring Legacy Project 생성하여 프로젝트명을 ch05JDBC, 최상위 패키지명을 kr.spring.jdbc로 지정하고 Spring MVC Project 선택
  2. pom.xml에서 <properties> 태그 사이의 내용을 다음처럼 변경
		<java-version>1.8</java-version>
		<org.springframework-version>5.0.20.RELEASE</org.springframework-version>
  1. pom.xml에서 <properties> 태그 종료 이후, <dependencies> 태그 시작 이전에 다음의 내용을 추가
	<!-- Oracle 드라이버 설정 시작 -->
	<repositories>
		<repository>
			<id>oracle</id>
			<name>ORACLE JDBC Repository</name>
			<url>https://code.lds.org/nexus/content/groups/main-repo</url>
		</repository>
	</repositories>
	<!-- Oracle 드라이버 설정 끝 -->
  1. pom.xml에서 <dependencies> 태그 사이에 다음의 내용을 추가
		<!-- 라이브러리 추가 시작 -->
		<dependency>
			<groupId>org.springframework</groupId>
			<artifactId>spring-jdbc</artifactId>
			<version>5.0.20.RELEASE</version>
		</dependency>
		<dependency>
			<groupId>commons-dbcp</groupId>
			<artifactId>commons-dbcp</artifactId>
			<version>1.3</version>
		</dependency>
		<dependency>
			<groupId>commons-pool</groupId>
			<artifactId>commons-pool</artifactId>
			<version>1.3</version>
		</dependency>
		<dependency>
			<groupId>commons-collections</groupId>
			<artifactId>commons-collections</artifactId>
			<version>3.1</version>
		</dependency>
		<dependency>
			<groupId>com.oracle.database.jdbc</groupId>
			<artifactId>ojdbc8</artifactId>
			<version>19.7.0.0</version>
		</dependency>
		<!-- 라이브러리 추가 끝 -->
  1. src/main/webapp/WEB-INFweb.xml 열고 <web-app> 태그 사이에 다음의 내용을 추가
	<filter> <!-- POST 방식 전송시 인코딩 처리를 위해 내장된 필터를 사용; 필터는 요청이 DispatcherServlet에 의해 다뤄지기 전,후에 동작 -->
		<filter-name>encodingFilter</filter-name>
		<filter-class>org.springframework.web.filter.CharacterEncodingFilter</filter-class>
		<init-param>
			<param-name>encoding</param-name>
			<param-value>UTF-8</param-value>
		</init-param>
	</filter>
	<filter-mapping> <!-- 컨텍스트 루트 하위의 모든 요청(*.do뿐만 아니라 *.jsp 등 확장자가 다른 경우까지 포함)에 대해 인코딩 필터 처리하도록 설정 -->
		<filter-name>encodingFilter</filter-name>
		<url-pattern>/*</url-pattern>
	</filter-mapping>
  1. src/main/webapp/WEB-INF/spring/appServlet에서 servlet-context.xml 열고 <context:component-scan> 태그를 다음처럼 수정
	<context:component-scan base-package="kr.spring.board.controller" />
  1. servlet-context.xml<beans:beans> 태그 사이에 다음의 내용을 추가
	<!-- messageSource 지정 -->
	<beans:bean id="messageSource" class="org.springframework.context.support.ResourceBundleMessageSource">
		<beans:property name="basenames">
			<beans:list>
				<beans:value>messages.validation</beans:value>
			</beans:list>
		</beans:property>
	</beans:bean>
  1. src/main/resources에 새 폴더 config 생성하고 새 파일 jdbc.properties 생성
jdbc.driverClassName=oracle.jdbc.OracleDriver
jdbc.url=jdbc:oracle:thin:@localhost:1521:xe
jdbc.username=scott
jdbc.password=tiger
  1. src/main/webapp/WEB-INF/spring에서 root-context.xml 열고 <beans> 태그를 다음처럼 수정
<?xml version="1.0" encoding="UTF-8"?>

<beans xmlns="http://www.springframework.org/schema/beans"
	xmlns:context="http://www.springframework.org/schema/context" 
	xmlns:p="http://www.springframework.org/schema/p"
	xmlns:tx="http://www.springframework.org/schema/tx"
	xmlns:aop="http://www.springframework.org/schema/aop"
	xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
	xsi:schemaLocation="http://www.springframework.org/schema/beans
       http://www.springframework.org/schema/beans/spring-beans-4.0.xsd
       http://www.springframework.org/schema/context
       http://www.springframework.org/schema/context/spring-context-4.0.xsd
       http://www.springframework.org/schema/tx
       http://www.springframework.org/schema/tx/spring-tx-4.0.xsd
       http://www.springframework.org/schema/aop
       http://www.springframework.org/schema/aop/spring-aop-4.0.xsd">
       
	<!-- 
	bean 자동 스캔 : servlet-context.xml에서 Controller를 자동 스캔 설정하였으므로 root-context.xml에서는 Controller 자동 스캔 제외(이중 스캔 방지)
	 -->
	<context:component-scan base-package="kr.spring.board">
		<context:exclude-filter type="annotation" expression="org.springframework.stereotype.Controller"/>
	</context:component-scan>

</beans>
  1. src/main/resources에 새 폴더 messages 생성하고 새 파일 validation.properties 생성
required.name=작성자는 필수 항목
required.title=제목은 필수 항목
required.passwd=비밀번호는 필수 항목
required.content=내용은 필수 항목
invalidPassword=비밀번호 불일치
  1. Java Build Path와 Project Facets에서 버전을 1.8로 변경
  2. webapp 폴더 오른쪽 클릭하고 새 JSP 파일 index.jsp 생성
<%@ page language="java" contentType="text/html; charset=UTF-8"
    pageEncoding="UTF-8"%>
<%
	response.sendRedirect(request.getContextPath() + "/list.do");
%>
  1. Servers 탭에서 오른쪽 클릭 후 Add and Remove...에서 ch05JDBC만 남겨 놓고 나머지는 Remove
    • Spring에서는 Servers에 Add된 프로젝트가 전부 구동되기 때문에 서버에 프로젝트가 여러 개 쌓이면 굉장히 느려질 수 있음
  2. Package Explorer에서 ch05JDBC를 제외한 나머지 프로젝트를 선택하고 오른쪽 클릭하여 Close Project 선택
    • egovFrame 초기 구동시 열려 있는 모든 프로젝트를 대상으로 문법 검사 등을 수행하는데, Close Project로 사용하지 않는 프로젝트를 닫아두면 초기 구동 속도가 개선됨
  3. kr.spring.jdbc 패키지 삭제
  4. src/main/webapp/resources 오른쪽 클릭하고 새 폴더 css 생성 후 새 CSS 파일 style.css 생성
@CHARSET "UTF-8";

.page-main{
	width:700px;
	margin:0 auto;
}
.result-display{
	width:400px;
	height:200px;
	/*정중앙에 div를 배치하기 위한 설정*/
	position:absolute;
    top: 50%;
  	left: 50%;
  	transform: translate(-50%, -50%);
    /*테두리*/
	border:1px solid #000;
	/*하위 요소를 수직으로 쌓을 수 있는 공간을 만듬*/
	display: flex;
	/*세로 정렬*/
  	align-items: center;
  	/*가로 정렬*/
  	justify-content: center;
}
.align-right{
	text-align:right;
}
.align-center{
	text-align:center;
}
/*목록*/
table{
	width:100%;
	border:1px solid #000;
	border-collapse:collapse;
}
table td, table th{
	border:1px solid #000;
	padding:5px;
}
/*등록,수정폼*/
form{
	width:500px;
	margin:0 auto;
	border:1px solid #000;
	padding:10px 10px 10px 30px;
}
ul{
	list-style:none;
}
label{
	width:100px;
	float:left;
}
.error-color{
	color:red;
}

5-1 커넥션 풀을 이용한 DataSource 설정

  1. root-context.xml에서 <beans> 태그 사이에 다음의 내용을 추가
	<!-- jdbc.properties 등록 -->
	<context:property-placeholder location="classpath:config/jdbc.properties"/>
	
	<!-- 커넥션 풀을 이용한 DataSource 설정 -->
	<bean id="dataSource" class="org.apache.commons.dbcp.BasicDataSource">
		<property name="driverClassName" value="${jdbc.driverClassName}"/>
		<property name="url" value="${jdbc.url}"/>
		<property name="username" value="${jdbc.username}"/>
		<property name="password" value="${jdbc.password}"/>
		<!-- 최대 커넥션 개수 -->
		<property name="maxActive" value="50"/>
		<!-- 접속이 없을 경우 최대 유지 커넥션 개수 -->
		<property name="maxIdle" value="30"/>
		<!-- 접속이 없을 경우 최소 유지 커넥션 개수 -->
		<property name="minIdle" value="20"/>
		<!-- 최대 대기 시간(초 단위) : 초과시 연결 실패 오류 발생 -->
		<property name="maxWait" value="5"/>
	</bean>

5-2 JdbcTemplate 클래스를 이용한 JDBC 프로그래밍

5-2-1 JdbcTemplate bean 설정
  1. root-context.xml에서 <beans> 태그 사이에 다음의 내용을 추가
	<!-- JdbcTemplate 객체 생성 -->
	<bean name="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
		<property name="dataSource" ref="dataSource"/>
	</bean>
Controller
  1. 새 패키지 kr.spring.board.controller 생성하고 새 클래스 BoardController 생성
package kr.spring.board.controller;

import java.util.List;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.validation.BindingResult;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.ModelAttribute;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.servlet.ModelAndView;

import kr.spring.board.service.BoardService;
import kr.spring.board.validator.BoardValidator;
import kr.spring.board.vo.BoardVO;
import kr.spring.util.PagingUtil;

@Controller // @Controller, @Service, @Repository는 @Component가 확장된 어노테이션이라 자동 스캔 설정했을 때 별도로 @Component 어노테이션 추가할 필요 없음
public class BoardController {
	@Autowired
	private BoardService boardService;
	
	// 자바빈 초기화
	@ModelAttribute
	public BoardVO initCommand() {
		return new BoardVO();
	}
	
	// 글쓰기 폼 호출
	@GetMapping("/insert.do")
	public String form() {
		return "insertForm";
	}

	// 글쓰기 폼에서 전송된 데이터 처리
	@PostMapping("/insert.do")
	public String submit(BoardVO boardVO, BindingResult result) {
		// 유효성 검증
		new BoardValidator().validate(boardVO, result);
		// 유효성 검증 결과 오류가 있으면 폼 호출
		if(result.hasErrors()) {
			return form();
		}
		
		// 글 등록
		boardService.insertBoard(boardVO);
		
		return "redirect:/list.do";
	}

	// 목록
	@RequestMapping("/list.do")
	public ModelAndView process(@RequestParam(value="pageNum", defaultValue="1") int currentPage) {
		int count = boardService.getBoardCount();
		
		// 페이지 처리
		PagingUtil page = new PagingUtil(currentPage, count, 10, 10, "/list.do");
		
		List<BoardVO> list = null;
		if(count>0) {
			list = boardService.getBoardList(page.getStartCount(), page.getEndCount());
		}
		
		ModelAndView mav = new ModelAndView();
		mav.setViewName("selectList");
		mav.addObject("count", count);
		mav.addObject("list", list);
		mav.addObject("pagingHtml", page.getPagingHtml());
		
		return mav;
	}
	
	// 상세
	@RequestMapping("/detail.do")
	public ModelAndView detail(@RequestParam int num) {
		BoardVO board = boardService.getBoard(num);
		
		return new ModelAndView("selectDetail", "board", board); // view 이름, 속성명, 속성값
	}
	
	// 수정 폼 호출
	@GetMapping("/update.do")
	public String formUpdate(@RequestParam int num, Model model) {
		BoardVO vo = boardService.getBoard(num);
		
		model.addAttribute("boardVO", vo);
		
		return "updateForm";
	}
	
	// 수정 폼에서 전송된 데이터 처리
	@PostMapping("/update.do")
	public String submitUpdate(BoardVO boardVO, BindingResult result) {
		// 유효성 검증
		new BoardValidator().validate(boardVO, result);
		// 유효성 검증 결과 오류가 있으면 수정 폼 호출
		if(result.hasErrors()) {
			return "updateForm";
		}
		
		// DB에 저장된 비밀번호 구하기
		BoardVO db_board = boardService.getBoard(boardVO.getNum());
		// 비밀번호 검증
		if(!db_board.getPasswd().equals(boardVO.getPasswd())) { // DB에 저장된 비밀번호와 사용자가 입력한 비밀번호가 불일치하면
			result.rejectValue("passwd", "invalidPassword");
			return "updateForm";
		}
		
		// 글 수정
		boardService.updateBoard(boardVO);
		
		return "redirect:/list.do";
	}
	
	// 삭제 폼 호출
	@GetMapping("/delete.do")
	public String formDelete(@RequestParam int num, Model model) {
		model.addAttribute("boardVO", boardService.getBoard(num));
		
		return "deleteForm";
	}
	
	// 삭제 폼에서 전송된 데이터 처리
	@PostMapping("/delete.do")
	public String submitDelete(BoardVO boardVO, BindingResult result) {
		// 유효성 검증
		new BoardValidator().validate(boardVO, result);
		// 유효성 검증 결과 비밀번호 필드에 오류가 있으면 삭제 폼 호출
		if(result.hasFieldErrors("passwd")) { // hasErrors() 메서드는 모든 필드를 검사하고, hasFieldErros() 메서드는 지정한 필드만 검사
			return "deleteForm";
		}
		
		// DB에 저장된 비밀번호 구하기
		BoardVO db_board = boardService.getBoard(boardVO.getNum());
		// 비밀번호 검증
		if(!db_board.getPasswd().equals(boardVO.getPasswd())) { // DB에 저장된 비밀번호와 사용자가 입력한 비밀번호가 불일치하면
			result.rejectValue("passwd", "invalidPassword");
			return "deleteForm";
		}
		
		// 글 삭제
		boardService.deleteBoard(boardVO.getNum());		
		
		return "redirect:/list.do";
	}
	
}
View
  1. src/main/webapp/WEB-INF/views에서 home.jsp 삭제하고 새 JSP 파일 selectList.jsp 생성
<%@ page language="java" contentType="text/html; charset=UTF-8"
    pageEncoding="UTF-8"%>
<%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c" %>
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>게시판 목록</title>
<link rel="stylesheet" href="${pageContext.request.contextPath}/resources/css/style.css">
</head>
<body>
<div class="page-main">
	<h2>게시판 목록</h2>
	<div class="align-right">
		<input type="button" value="글쓰기" onclick="location.href = 'insert.do';">
	</div>
	<c:if test="${count==0}">
		<div class="result-display">표시할 내용이 없습니다.</div>
	</c:if>
	<c:if test="${count>0}">
	<table>
		<tr>
			<th>번호</th>
			<th>제목</th>
			<th>작성자</th>
			<th>작성일</th>
		</tr>
		<c:forEach var="board" items="${list}">
		<tr>
			<td>${board.num}</td>
			<td><a href="detail.do?num=${board.num}">${board.title}</a></td>
			<td>${board.name}</td>
			<td>${board.reg_date}</td>
		</tr>
		</c:forEach>
	</table>
	<div class="align-center">${pagingHtml}</div>
	</c:if>
</div>
</body>
</html>
  1. views 폴더에 새 JSP 파일 insertForm.jsp 생성
<%@ page language="java" contentType="text/html; charset=UTF-8"
    pageEncoding="UTF-8"%>
<%@ taglib uri="http://www.springframework.org/tags/form" prefix="form" %>
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>글쓰기</title>
<link rel="stylesheet" href="${pageContext.request.contextPath}/resources/css/style.css">
</head>
<body>
<div class="page-main">
	<h2>글쓰기</h2>
	<form:form modelAttribute="boardVO">
		<ul>
			<li>
				<form:label path="name">작성자</form:label>
				<form:input path="name"/>
				<form:errors path="name" cssClass="error-color"/>
			</li>
			<li>
				<form:label path="title">제목</form:label>
				<form:input path="title"/>
				<form:errors path="title" cssClass="error-color"/>
			</li>
			<li>
				<form:label path="passwd">비밀번호</form:label>
				<form:password path="passwd"/>
				<form:errors path="passwd" cssClass="error-color"/>
			</li>
			<li>
				<form:label path="content">내용</form:label>
				<form:textarea path="content"/>
				<form:errors path="content" cssClass="error-color"/>
			</li>
		</ul>
		<div class="align-center">
			<form:button>등록</form:button>
			<input type="button" value="홈으로" onclick="location.href = 'list.do';">
		</div>
	</form:form>
</div>
</body>
</html>
VO
  1. 새 패키지 kr.spring.board.vo 생성하고 새 클래스 BoardVO 생성
package kr.spring.board.vo;

import java.sql.Date;

public class BoardVO {
	private int num;
	private String name;
	private String title;
	private String passwd;
	private String content;
	private Date reg_date;
	
	public int getNum() {
		return num;
	}
	public void setNum(int num) {
		this.num = num;
	}
	public String getName() {
		return name;
	}
	public void setName(String name) {
		this.name = name;
	}
	public String getTitle() {
		return title;
	}
	public void setTitle(String title) {
		this.title = title;
	}
	public String getPasswd() {
		return passwd;
	}
	public void setPasswd(String passwd) {
		this.passwd = passwd;
	}
	public String getContent() {
		return content;
	}
	public void setContent(String content) {
		this.content = content;
	}
	public Date getReg_date() {
		return reg_date;
	}
	public void setReg_date(Date reg_date) {
		this.reg_date = reg_date;
	}
}
Validator
  1. 새 패키지 kr.spring.board.validator 생성하고 새 클래스 BoardValidator 생성
package kr.spring.board.validator;

import org.springframework.validation.Errors;
import org.springframework.validation.ValidationUtils;
import org.springframework.validation.Validator;

import kr.spring.board.vo.BoardVO;

public class BoardValidator implements Validator {
	@Override
	public boolean supports(Class<?> clazz) {
		return BoardVO.class.isAssignableFrom(clazz);
	}

	@Override
	public void validate(Object target, Errors errors) {
		ValidationUtils.rejectIfEmptyOrWhitespace(errors, "name", "required"); // errors, 필드명, 에러 코드를 전달하면 자바빈에서 해당 필드가 비어 있거나 공백인지 여부를 확인하여 reject 처리
		ValidationUtils.rejectIfEmptyOrWhitespace(errors, "title", "required");
		ValidationUtils.rejectIfEmptyOrWhitespace(errors, "passwd", "required");
		ValidationUtils.rejectIfEmptyOrWhitespace(errors, "content", "required");
	}
}
5-2-2 DAO 클래스에서의 JdbcTemplate 객체 사용
  1. webapp 폴더에 하위 폴더로 sql 생성하고 새 파일 table.sql 생성
CREATE TABLE tboard(
	num NUMBER NOT NULL PRIMARY KEY,
	name VARCHAR2(30) NOT NULL,
	title VARCHAR2(150) NOT NULL,
	passwd VARCHAR2(10) NOT NULL,
	content VARCHAR2(4000) NOT NULL,
	reg_date DATE NOT NULL
);

CREATE SEQUENCE tboard_seq;
  1. 새 패키지 kr.spring.board.dao 생성하고 새 인터페이스 BoardDAO 생성
package kr.spring.board.dao;

import java.util.List;

import kr.spring.board.vo.BoardVO;

public interface BoardDAO {
	public void insertBoard(BoardVO board);
	public int getBoardCount();
	public List<BoardVO> getBoardList(int startRow, int endRow);
	public BoardVO getBoard(int num);
	public void updateBoard(BoardVO board);
	public void deleteBoard(int num);
}
  1. 새 클래스 BoardDAOImpl 생성
package kr.spring.board.dao;

import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.List;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.jdbc.core.RowMapper;
import org.springframework.stereotype.Repository;

import kr.spring.board.vo.BoardVO;

@Repository // DAO 전용 어노테이션; 자동 스캔되기 위해 필요
public class BoardDAOImpl implements BoardDAO {
	private static final String INSERT_SQL 
			= "INSERT INTO tboard (num, name, title, passwd, content, reg_date) "
			+ "VALUES (tboard_seq.NEXTVAL, ?, ?, ?, ?, SYSDATE)";
	private static final String SELECT_COUNT_SQL
			= "SELECT COUNT(*) FROM tboard";
	private static final String SELECT_LIST_SQL
			= "SELECT * FROM (SELECT a.*, ROWNUM AS rnum FROM "
			+ "(SELECT * FROM tboard ORDER BY reg_date DESC) a) WHERE rnum>=? AND rnum<=?";
	private static final String SELECT_DETAIL_SQL
			= "SELECT * FROM tboard WHERE num=?";
	private static final String UPDATE_SQL
			= "UPDATE tboard SET name=?, title=?, content=? WHERE num=?";
	private static final String DELETE_SQL 
			= "DELETE FROM tboard WHERE num=?";
	
	// ResultSet 처리를 위해 RowMapper가 구현된 익명 클래스 객체 생성
	private RowMapper<BoardVO> rowMapper = new RowMapper<BoardVO>() { // RowMapper 인터페이스를 구현한 익명 클래스를 정의하고(=RowMapper<BoardVO>() {}), 그 익명 클래스의 객체를 생성하여(=new) 변수 rowMapper에 할당
		public BoardVO mapRow(ResultSet rs, int rowNum) throws SQLException { // RowMapper 인터페이스의 추상 메서드 mapRow()를 구현
			BoardVO board = new BoardVO();
			board.setNum(rs.getInt("num"));
			board.setName(rs.getString("name"));
			board.setTitle(rs.getString("title"));
			board.setPasswd(rs.getString("passwd"));
			board.setContent(rs.getString("content"));
			board.setReg_date(rs.getDate("reg_date"));
			
			return board;
		}
	};
	
	@Autowired
	private JdbcTemplate jdbcTemplate;
	
	@Override
	public void insertBoard(BoardVO board) {
		jdbcTemplate.update(INSERT_SQL, 
				new Object[] {board.getName(), board.getTitle(), board.getPasswd(), board.getContent()}); // 인자로 SQL문과 ?에 바인딩될 데이터가 담긴 Object 배열을 전달
	}

	@Override
	public int getBoardCount() {
		return jdbcTemplate.queryForObject(SELECT_COUNT_SQL, Integer.class); // 인자로 SQL문과 반환하는 데이터의 자료형을 전달
	}

	@Override
	public List<BoardVO> getBoardList(int startRow, int endRow) {
		List<BoardVO> list = jdbcTemplate.query(SELECT_LIST_SQL, 
				new Object[] {startRow, endRow}, rowMapper); // 인자로 SQL문, ?에 바인딩될 데이터가 담긴 Object 배열, RowMapper가 구현된 익명 클래스 객체를 전달
		return list;
	}

	@Override
	public BoardVO getBoard(int num) {
		BoardVO board = jdbcTemplate.queryForObject(SELECT_DETAIL_SQL, 
				new Object[] {num}, rowMapper); // 인자로 SQL문, ?에 바인딩될 데이터가 담긴 Object 배열, RowMapper가 구현된 익명 클래스 객체를 전달
		return board;
	}

	@Override
	public void updateBoard(BoardVO board) {
		jdbcTemplate.update(UPDATE_SQL, 
				new Object[] {board.getName(), board.getTitle(), board.getContent(), board.getNum()}); // 인자로 SQL문과 ?에 바인딩될 데이터가 담긴 Object 배열을 전달
	}

	@Override
	public void deleteBoard(int num) {
		jdbcTemplate.update(DELETE_SQL, new Object[] {num}); // 인자로 SQL문과 ?에 바인딩될 데이터가 담긴 Object 배열을 전달
	}
}
Service
  1. 새 패키지 kr.spring.board.service 생성하고 새 인터페이스 BoardService 생성
package kr.spring.board.service;

import java.util.List;

import kr.spring.board.vo.BoardVO;

public interface BoardService {
	public void insertBoard(BoardVO board);
	public int getBoardCount();
	public List<BoardVO> getBoardList(int startRow, int endRow);
	public BoardVO getBoard(int num);
	public void updateBoard(BoardVO board);
	public void deleteBoard(int num);
}
  1. 새 클래스 BoardServiceImpl 생성
package kr.spring.board.service;

import java.util.List;

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

import kr.spring.board.dao.BoardDAO;
import kr.spring.board.vo.BoardVO;

@Service // Service 전용 어노테이션; 자동 스캔되기 위해 필요
public class BoardServiceImpl implements BoardService {
	@Autowired
	private BoardDAO boardDAO;
	
	@Override
	public void insertBoard(BoardVO board) {
		boardDAO.insertBoard(board);
	}

	@Override
	public int getBoardCount() {
		return boardDAO.getBoardCount();
	}

	@Override
	public List<BoardVO> getBoardList(int startRow, int endRow) {
		return boardDAO.getBoardList(startRow, endRow);
	}

	@Override
	public BoardVO getBoard(int num) {
		return boardDAO.getBoard(num);
	}

	@Override
	public void updateBoard(BoardVO board) {
		boardDAO.updateBoard(board);
	}

	@Override
	public void deleteBoard(int num) {
		boardDAO.deleteBoard(num);
	}
}

다음으로