Study Web Development

3월 4일

이전으로

Spring

5. JDBC

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

Util
  1. 새 패키지 kr.spring.util 생성하고 새 클래스 PagingUtil 생성
package kr.spring.util;

public class PagingUtil {
	private int startCount;	 // 한 페이지에서 보여줄 게시글의 시작 번호
	private int endCount;	 // 한 페이지에서 보여줄 게시글의 끝 번호
	private StringBuffer pagingHtml;// 페이지 표시 문자열

	/**
	 * currentPage : 현재페이지
	 * totalCount : 전체 게시물 수
	 * rowCount : 한 페이지의  게시물의 수
	 * pageCount : 한 화면에 보여줄 페이지 수
	 * pageUrl : 호출 페이지 url
	 * addKey : 부가적인 key 없을 때는 null 처리 (&num=23형식으로 전달할 것)
	 * */
	public PagingUtil(int currentPage, int totalCount, int rowCount,
			int pageCount, String pageUrl) {
		this(null,null,currentPage,totalCount,rowCount,pageCount,pageUrl,null);
	}
	public PagingUtil(int currentPage, int totalCount, int rowCount,
			int pageCount, String pageUrl, String addKey) {
		this(null,null,currentPage,totalCount,rowCount,pageCount,pageUrl,addKey);
	}
	public PagingUtil(String keyfield, String keyword, int currentPage, int totalCount, int rowCount,
			int pageCount,String pageUrl) {
		this(keyfield,keyword,currentPage,totalCount,rowCount,pageCount,pageUrl,null);
	}
	public PagingUtil(String keyfield, String keyword, int currentPage, int totalCount, int rowCount,
			int pageCount,String pageUrl,String addKey) {
		
		String sub_url = "";
		if(keyword != null) sub_url = "&keyfield="+keyfield+"&keyword="+keyword;
		if(addKey != null) sub_url += addKey;
		
		// 전체 페이지 수
		int totalPage = (int) Math.ceil((double) totalCount / rowCount);
		if (totalPage == 0) {
			totalPage = 1;
		}
		// 현재 페이지가 전체 페이지 수보다 크면 전체 페이지 수로 설정
		if (currentPage > totalPage) {
			currentPage = totalPage;
		}
		// 현재 페이지의 처음과 마지막 글의 번호 가져오기.
		startCount = (currentPage - 1) * rowCount + 1;
		endCount = currentPage * rowCount;
		// 시작 페이지와 마지막 페이지 값 구하기.
		int startPage = (int) ((currentPage - 1) / pageCount) * pageCount + 1;
		int endPage = startPage + pageCount - 1;
		// 마지막 페이지가 전체 페이지 수보다 크면 전체 페이지 수로 설정
		if (endPage > totalPage) {
			endPage = totalPage;
		}
		// 이전 block 페이지
		pagingHtml = new StringBuffer();
		if (currentPage > pageCount) {
			pagingHtml.append("<a href="+pageUrl+"?pageNum="+ (startPage - 1) + sub_url +">");
			pagingHtml.append("[이전]");
			pagingHtml.append("</a>");
		}
		//페이지 번호.현재 페이지는 빨간색으로 강조하고 링크를 제거.
		for (int i = startPage; i <= endPage; i++) {
			if (i > totalPage) {
				break;
			}
			if (i == currentPage) {
				pagingHtml.append("&nbsp;<b><span style='color:red;'>");
				pagingHtml.append(i);
				pagingHtml.append("</span></b>");
			} else {
				pagingHtml.append("&nbsp;<a href='"+pageUrl+"?pageNum=");
				pagingHtml.append(i);
				pagingHtml.append(sub_url+"'>");
				pagingHtml.append(i);
				pagingHtml.append("</a>");
			}
			pagingHtml.append("&nbsp;");
		}
		// 다음 block 페이지
		if (totalPage - startPage >= pageCount) {
			pagingHtml.append("<a href="+pageUrl+"?pageNum="+ (endPage + 1) + sub_url +">");
			pagingHtml.append("[다음]");
			pagingHtml.append("</a>");
		}
	}
	public StringBuffer getPagingHtml() {
		return pagingHtml;
	}
	public int getStartCount() {
		return startCount;
	}
	public int getEndCount() {
		return endCount;
	}
}
View
  1. views 폴더에 새 JSP 파일 selectDetail.jsp 생성
<%@ page language="java" contentType="text/html; charset=UTF-8"
    pageEncoding="UTF-8"%>
<!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>${board.title}</h2>
	<p>
		글 번호 : ${board.num}<br>
		작성자 : ${board.name}<br>
		작성일 : ${board.reg_date}
	</p>
	<hr width="100%" size="1" noshade>
	<p>
		${board.content}
	</p>
	<hr width="100%" size="1" noshade>
	<div class="align-center">
		<input type="button" value="수정" onclick="location.href = 'update.do?num=${board.num}';">
		<input type="button" value="삭제" onclick="location.href = 'delete.do?num=${board.num}';">
		<input type="button" value="목록" onclick="location.href = 'list.do';">
	</div>
</div>
</body>
</html>
  1. views 폴더에 새 JSP 파일 updateForm.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>
  1. views 폴더에 새 JSP 파일 deleteForm.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 action="delete.do" modelAttribute="boardVO">
		<form:hidden path="num"/>
		<ul>
			<li>
				<form:label path="passwd">비밀번호</form:label>
				<form:password path="passwd"/>
				<form:errors path="passwd" 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>

6. 로깅 처리

  1. src/main/resources 폴더에서 log4j.xml 오른쪽 클릭하고 Open With-XML Editor 선택해서 다음의 내용을 수정
	<!-- Application Loggers -->
	<logger name="kr.spring">
		<level value="info" />
	</logger>
  1. BoardController 클래스에 다음의 프로퍼티를 추가
	// 로그 처리(로그 대상 지정)
	private static final Logger logger = LoggerFactory.getLogger(BoardController.class);
  1. BoardController 클래스의 process() 메서드를 다음처럼 수정
	// 목록
	@RequestMapping("/list.do")
	public ModelAndView process(@RequestParam(value="pageNum", defaultValue="1") int currentPage) {		
		int count = boardService.getBoardCount();
		
		logger.info("pageNum : " + currentPage);
		logger.info("count : " + count);
		
		// 페이지 처리
		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;
	}
  1. BoardVO 클래스에서 toString() 메서드를 재정의
	@Override
	public String toString() {
		return "BoardVO [num=" + num + ", name=" + name + ", title=" + title + ", passwd=" + passwd + ", content="
				+ content + ", reg_date=" + reg_date + "]";
	}
  1. BoardController 클래스의 submit() 메서드를 다음처럼 수정
	// 글쓰기 폼에서 전송된 데이터 처리
	@PostMapping("/insert.do")
	public String submit(BoardVO boardVO, BindingResult result) {
		logger.info("BoardVO : " + boardVO);
		
		// 유효성 검증
		new BoardValidator().validate(boardVO, result);
		// 유효성 검증 결과 오류가 있으면 폼 호출
		if(result.hasErrors()) {
			return form();
		}
		
		// 글 등록
		boardService.insertBoard(boardVO);
		
		return "redirect:/list.do";
	}
  1. BoardController 클래스의 submitUpdate() 메서드를 다음처럼 수정
	// 수정 폼에서 전송된 데이터 처리
	@PostMapping("/update.do")
	public String submitUpdate(BoardVO boardVO, BindingResult result) {
		logger.info("BoardVO : " + boardVO);
		
		// 유효성 검증
		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";
	}

6-2 로그 레벨

로그 레벨 의미
FATAL 가장 심각한 오류
ERROR 일반적인 오류
WARN 주의를 요하는 경우
INFO 런타임시 관심 있는 내용
DEBUG 시스템 흐름과 관련된 상세 정보
TRACE 가장 상세한 정보

7. 트랜잭션

  1. pom.xml에서 <dependencies> 태그 사이에 다음의 내용을 추가
		<dependency>
			<groupId>com.atomikos</groupId>
			<artifactId>transactions</artifactId>
			<version>3.9.3</version>
		</dependency>
		<dependency>
			<groupId>org.aspectj</groupId>
			<artifactId>aspectjweaver</artifactId>
			<version>1.8.9</version>
		</dependency>

7-2 JDBC 기반 트랜잭션 관리자 설정

7-2-1 bean 설정
  1. root-context.xml에서 <beans> 태그 사이에 다음의 내용을 추가
	<!-- JDBC 기반 트랜잭션 관리자 설정 -->
	<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager" p:dataSource-ref="dataSource"/>

7-3 선언적 트랜잭션 처리

7-3-1 bean 설정
  1. root-context.xml에서 <beans> 태그 사이에 다음의 내용을 추가
	<!-- 선언적 트랜잭션 처리 -->
	<tx:advice id="txAdvice" transaction-manager="transactionManager">
		<tx:attributes>
			<tx:method name="update*"/>
		</tx:attributes>
	</tx:advice>
	<aop:config>
		<aop:pointcut expression="execution(public * kr.spring.board..*ServiceImpl.*(..))" id="servicePublicMethod"/>
		<aop:advisor advice-ref="txAdvice" pointcut-ref="servicePublicMethod"/>
	</aop:config>
  1. 테스트하려면 BoardServiceImpl 클래스에서 updateBoard() 메서드 하단에 boardDAO.insertBoard(board);를 추가하고, BoardDAOImpl 클래스에서 INSERT_SQL을 임의로 오류 발생하도록 변경

7-4 어노테이션 기반 트랜잭션 처리

bean 설정
  1. root-context.xml에서 <tx:advice> 태그와 <aop:config> 태그를 주석 처리
  2. root-context.xml에서 <beans> 태그 사이에 다음의 내용을 추가
	<!-- 어노테이션 방식으로 처리할 때 반드시 명시 -->
	<tx:annotation-driven transaction-manager="transactionManager"/>
7-4-1 메서드에 직접 명시
  1. BoardServiceImpl 클래스에서 updateBoard() 메서드에 @Transactional 어노테이션 적용
  2. 테스트하려면 BoardServiceImpl 클래스에서 updateBoard() 메서드 하단에 boardDAO.insertBoard(board);를 추가하고, BoardDAOImpl 클래스에서 INSERT_SQL을 임의로 오류 발생하도록 변경

8. MyBatis

https://blog.mybatis.org/

SQL Session

프로젝트 생성 및 설정
  1. 새 Spring Legacy Project 생성하여 프로젝트명을 ch06MybatisSqlSession, 최상위 패키지명을 kr.spring.mybatis로 지정하고 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>
		<dependency>
			<groupId>com.atomikos</groupId>
			<artifactId>transactions</artifactId>
			<version>3.9.3</version>
		</dependency>
		<dependency>
			<groupId>org.aspectj</groupId>
			<artifactId>aspectjweaver</artifactId>
			<version>1.8.9</version>
		</dependency>
		<dependency>
			<groupId>org.mybatis</groupId>
			<artifactId>mybatis-spring</artifactId>
			<version>1.2.2</version>
		</dependency>
		<dependency>
			<groupId>org.mybatis</groupId>
			<artifactId>mybatis</artifactId>
			<version>3.2.2</version>
		</dependency>
		<dependency>
			<groupId>org.hibernate</groupId>
			<artifactId>hibernate-validator</artifactId>
			<version>6.0.18.Final</version>
		</dependency>
		<!-- 라이브러리 추가 끝 -->
  1. Java Build Path와 Project Facets에서 버전을 1.8로 변경
  2. 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/webapp/WEB-INF/spring에서 root-context.xml 열고 <beans> 태그를 다음처럼 수정
<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>
	
	<!-- 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>
	
	<!-- JDBC 기반 트랜잭션 관리자 설정 -->
	<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager" p:dataSource-ref="dataSource"/>

	<!-- 어노테이션 방식으로 처리할 때 반드시 명시 -->
	<tx:annotation-driven transaction-manager="transactionManager"/>
</beans>
  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/resources에 새 폴더 messages 생성하고 새 파일 validation.properties 생성
# 어노테이션
NotEmpty.name=작성자는 필수 항목
NotEmpty.title=제목은 필수 항목
NotEmpty.passwd=비밀번호는 필수 항목
NotEmpty.content=내용은 필수 항목

# 직접 reject
invalidPassword=비밀번호 불일치
  1. src/main/resources 폴더에서 log4j.xml 오른쪽 클릭하고 Open With-XML Editor 선택해서 다음의 내용을 수정
	<!-- Application Loggers -->
	<logger name="kr.spring">
		<level value="info" />
	</logger>
  1. webapp 폴더에 하위 폴더로 sql 생성하고 ch05JDBC 프로젝트에서 같은 위치의 table.sql 복사해서 붙여넣기
  2. ch05JDBC 프로젝트의 webapp/resources에서 css 폴더를 복사해서 ch06MybatisSqlSession 프로젝트의 같은 위치에 붙여넣기
  3. webapp 폴더 오른쪽 클릭하고 새 JSP 파일 index.jsp 생성
<%@ page language="java" contentType="text/html; charset=UTF-8"
    pageEncoding="UTF-8"%>
<%
	response.sendRedirect(request.getContextPath() + "/list.do");
%>
  1. kr.spring.mybatis 패키지 삭제
VO
  1. 새 패키지 kr.spring.board.vo 생성하고 새 클래스 BoardVO 생성
package kr.spring.board.vo;

import java.sql.Date;

import javax.validation.constraints.NotEmpty;

public class BoardVO {
	private int num;
	@NotEmpty
	private String name;
	@NotEmpty
	private String title;
	@NotEmpty
	private String passwd;
	@NotEmpty
	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;
	}
	
	@Override
	public String toString() {
		return "BoardVO [num=" + num + ", name=" + name + ", title=" + title + ", passwd=" + passwd + ", content="
				+ content + ", reg_date=" + reg_date + "]";
	}
}
MyBatis 설정
  1. 새 패키지 kr.spring.board.dao 생성하고 새 XML 파일 BoardMapper.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="BoardDAO">
	
</mapper>
  1. config 폴더에 새 XML 파일 SqlMapConfig.xml 생성
<?xml version="1.0" encoding="UTF-8" ?>

<!DOCTYPE configuration PUBLIC "-//mybatis.org//DTD Config 3.0//EN" "HTTP://mybatis.org/dtd/mybatis-3-config.dtd">

<configuration>
	<typeAliases>
		<typeAlias alias="boardVO" type="kr.spring.board.vo.BoardVO"/> <!-- XML에서는 패키지명까지 포함한 전체 이름을 호출해야 자바빈 정보를 읽어올 수 있는데, alias를 부여하면 alias로 자바빈 정보를 읽어올 수 있음 -->
	</typeAliases>
	<mappers>
		<mapper resource="kr/spring/board/dao/BoardMapper.xml"/>
	</mappers>
</configuration>
  1. root-context.xml에서 <beans> 태그 사이에 다음의 내용을 추가
	<!-- MyBatis 설정 -->
	<bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
		<property name="configLocation" value="classpath:config/SqlMapConfig.xml"/>
		<property name="dataSource" ref="dataSource"/>
	</bean>
	<bean id="sqlSession" class="org.mybatis.spring.SqlSessionTemplate">
		<constructor-arg ref="sqlSessionFactory"/>
	</bean>
DAO
  1. kr.spring.board.dao 패키지에 새 인터페이스 BoardDAO 생성
package kr.spring.board.dao;

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

import kr.spring.board.vo.BoardVO;

public interface BoardDAO {
	public void insertBoard(BoardVO board);
	public int getBoardCount();
	public List<BoardVO> getBoardList(Map<String, Object> map); // MyBatis에서는 메서드에 인자를 하나만 명시하므로 여러 개를 전달해야 할 때는 Map 객체 이용
	public BoardVO getBoard(int num);
	public void updateBoard(BoardVO board);
	public void deleteBoard(int num);
}
  1. 새 클래스 BoardDAOImpl 생성
package kr.spring.board.dao;

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;

import kr.spring.board.vo.BoardVO;

@Repository // 자동 스캔 처리
public class BoardDAOImpl implements BoardDAO {
	@Autowired
	private SqlSessionTemplate sqlSession;
	
	@Override
	public void insertBoard(BoardVO board) {
		sqlSession.insert("insertBoard", board);
	}

	@Override
	public int getBoardCount() {
		Integer count = sqlSession.selectOne("getBoardCount");
		return count;
	}

	@Override
	public List<BoardVO> getBoardList(Map<String, Object> map) {
		return sqlSession.selectList("getBoardList", map); // 인자로 XML에 포함된 SQL문 호출에 사용할 식별자와 ?에 바인딩할 데이터가 담긴 Map 객체 전달
	}

	@Override
	public BoardVO getBoard(int num) {
		return sqlSession.selectOne("getBoard", num); // 자바빈 또는 Map 객체가 아니라 값 하나만 전달시, 해당 값이 담긴 변수명 num이 데이터 바인딩시의 식별자로 사용됨
	}

	@Override
	public void updateBoard(BoardVO board) {
		sqlSession.update("updateBoard", board);
	}

	@Override
	public void deleteBoard(int num) {
		sqlSession.delete("deleteBoard", num);
	}
}
  1. BoardMapper.xml<mapper> 태그 사이에 다음의 내용을 추가
	<!-- 목록 -->
	<select id="getBoardList" resultType="boardVO" parameterType="map"> <!-- java.util.Map의 alias가 map -->
		SELECT
			*
		FROM (SELECT
				a.*, ROWNUM AS rnum
			FROM (
				SELECT 
					* 
				FROM tboard
				ORDER BY num DESC) a)
		<![CDATA[
		WHERE rnum >= #{start} AND rnum <= #{end}
		]]> <!-- <는 XML에서 태그를 시작하는 기호라 문법 검사시 에러가 발생하므로, CDATA를 이용하여 문법 검사를 건너뛰도록 처리; SQL문에 Map 객체의 데이터를 바인딩시, #{key} 형식으로 key의 값을 호출 -->
	</select>
Service
  1. 새 패키지 kr.spring.board.service 생성하고 새 인터페이스 BoardService 생성
package kr.spring.board.service;

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

import kr.spring.board.vo.BoardVO;

public interface BoardService {
	public void insertBoard(BoardVO board);
	public int getBoardCount();
	public List<BoardVO> getBoardList(Map<String, Object> map); // MyBatis에서는 메서드에 인자를 하나만 명시하므로 여러 개를 전달해야 할 때는 Map 객체 이용
	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 java.util.Map;

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 // 자동 스캔 처리
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(Map<String, Object> map) {
		return boardDAO.getBoardList(map);
	}

	@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);
	}
}
Controller
  1. 새 패키지 kr.spring.board.controller 생성하고 새 클래스 BoardController 생성
package kr.spring.board.controller;

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.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.servlet.ModelAndView;

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

@Controller
public class BoardController {
	@Autowired
	private BoardService boardService;
	
	// 로그 처리(로그 대상 지정)
	private static final Logger logger = LoggerFactory.getLogger(BoardController.class);
	
	// 목록
	@RequestMapping("/list.do")
	public ModelAndView getList(@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) {
			Map<String, Object> map = new HashMap<String, Object>();
			map.put("start", page.getStartCount());
			map.put("end", page.getEndCount());
			list = boardService.getBoardList(map);
		}
		
		ModelAndView mav = new ModelAndView();
		// view 이름 지정
		mav.setViewName("selectList");
		// 데이터 저장
		mav.addObject("list", list);
		mav.addObject("count", count);
		mav.addObject("pagingHtml", page.getPagingHtml());
		
		return mav;
	}
}
View
  1. 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>

다음으로