[ Spring 회원 게시판 ]
적용 기술
: javase, jdbc , javascript, jquery , css , bootstrap , Servlet/JSP,JSTL ,
spring ioc , springmvc , mybatis , sql
분석
로그인
로그아웃
목록
페이징
글쓰기
상세보기(조회수)
삭제
수정
1. insert (글쓰기), delete(삭제) , update(수정) 는 POST request method
2. File List
Model
- MemberMapper
- BoardMapper
- MemberService , Impl
- BoardService, Impl
- PostVO , MemberVO
Controller
- MemberController
- BoardController
View
- /resources/ css , js ...
[ ERD ]

> SpringTemplate project를 복사해서 사용한다.
1. paste 후, 오른쪽 마우스 클릭 > Refactor > Rename Maven Artifact 클릭
2. Artiface Id를 프로젝트 명으로 수정
3. pom.xml에서 <artifactId>와<name>를 바꾼 프로젝트 명으로 변경
4. maven update 실행 후 동작 확인
아래의 설정 정보는 아래 포스팅을 참고하세요!
- Maven pom 설정 - pom.xml
- DD (Deploytment Descriptor) 설정 - web.xml
[SpringMVC/MyBatis] SpringMVC Template Project 환경설정
[ 환경 설정 ] 1. Spring Legacy Project 프로젝트 생성 > 'Spring MVC Project' 선택 > 프로젝트 명 작성 (작성한 프로젝트 이름이 실제 브라우저상 보이는 서버프로그램 url 이 된다 *주로 프로젝트 명을 쓴다..
creamilk88.tistory.com
Spring configure 설정
> @Mapper 어노테이션을 이용해 Proxy(대리인) 객체 (DAOImpl)를 생성하게 하는 설정 추가
<mybatis-spring:scan base-package="[mapper path]"/>
- Namespaces에서 'beans', 'context', 'mybatis-spring' 체크
/WEB_INF/spring-model.xml
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:mybatis-spring="http://mybatis.org/schema/mybatis-spring"
xsi:schemaLocation="http://mybatis.org/schema/mybatis-spring http://mybatis.org/schema/mybatis-spring-1.2.xsd
http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-4.3.xsd">
<!-- 1. DBCP(ver.2) 설정 -->
<bean id="dbcp" class="org.apache.commons.dbcp2.BasicDataSource">
<property name="driverClassName" value="oracle.jdbc.OracleDriver"/>
<property name="url" value="jdbc:oracle:thin:@127.0.0.1:1521:xe"/>
<property name="username" value="scott"/>
<property name="password" value="tiger"/>
</bean>
<!--2. SqlSessionFactory 설정 -->
<!-- @Mapper 어노테이션을 이용해 Proxy(대리인) 객체 (DAOImpl)를 생성하게 하는 설정 -->
<mybatis-spring:scan base-package="org.kosta.myproject.model.mapper"/>
<bean id="sqlSessionFactoryBean" class="org.mybatis.spring.SqlSessionFactoryBean">
<!-- DBCP(datebase connection pool) 주입 -->
<property name="dataSource" ref="dbcp"/>
<!-- Mapper 어노테이션을 적용하여, 자동으로 DAO 구현체를 생성할 때는
아래 설정은 필요없다.
<property name="mapperLocations" value="classpath:/mybatis/config/*.xml"/>-->
<!-- Package에 별칭주기 : vo까지 잡아주어 상세히 한다.-->
<property name="typeAliasesPackage" value="org.kosta.myproject.model.vo"/>
<!-- underScore 표기법을 Camel 표기법으로 mapping(변환)해주는 설정 -->
<property name="configuration">
<bean class="org.apache.ibatis.session.Configuration">
<property name="mapUnderscoreToCamelCase" value="true"></property>
</bean>
</property>
</bean>
<!--3. SqlSessionTemplate설정 : 트랜잭션 제어를 지원-->
<bean id="SqlSessionTemplate" class="org.mybatis.spring.SqlSessionTemplate">
<constructor-arg ref="sqlSessionFactoryBean"/>
</bean>
<!--4. IOC 설정 : <context:component-scan> : IOC, DI, DL에 대한 설정-->
<context:component-scan base-package="org.kosta"></context:component-scan>
</beans>
/WEB_INF/spring-web.xml
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:mvc="http://www.springframework.org/schema/mvc"
xsi:schemaLocation="http://www.springframework.org/schema/mvc http://www.springframework.org/schema/mvc/spring-mvc-4.3.xsd
http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
<!--5. SpringMVC 설정-->
<mvc:annotation-driven/>
<!--6. ViewResolver 설정 : client에게 응답하는 view에 대한 설정 -->
<bean id="ViewResolver" class="org.springframework.web.servlet.view.InternalResourceViewResolver">
<property name="prefix" value="/WEB-INF/views/"/>
<property name="suffix" value=".jsp"/>
</bean>
</beans>
/src/main/webapp/WEB-INF/views
View
/board
/list.jsp
<%@ page language="java" contentType="text/html; charset=UTF-8"
pageEncoding="UTF-8"%>
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
<!DOCTYPE html>
<html lang="en">
<head>
<title>board</title>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/css/bootstrap.min.css">
<link rel="stylesheet" href="${pageContext.request.contextPath}/resources/css/board.css">
<script src="https://ajax.googleapis.com/ajax/libs/jquery/3.2.1/jquery.min.js"></script>
<script src="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/js/bootstrap.min.js"></script>
</head>
<body>
<!-- container-fluid: 화면 너비와 상관없이 항상 100% -->
<div class="container-fluid">
<div class="row header">
<div class="col-sm-2" ></div>
<div class="col-sm-8" align="right">
<c:import url="/WEB-INF/views/member/login.jsp"></c:import>
</div>
<div class="col-sm-2" ></div>
</div>
<div class="row main">
<div class="col-sm-2" ></div>
<div class="col-sm-8">
<table class="table table-bordered table-hover boardlist">
<caption>목록</caption>
<thead>
<tr>
<th class="no">NO</th>
<th class="title">제목</th>
<th class="name">이름</th>
<th class="date">작성일</th>
<th class="hit">HIT</th>
</tr>
</thead>
<tbody>
<c:forEach var="pvo" items="${requestScope.lvo.list}">
<tr>
<td>${pvo.no }</td>
<td>
<c:choose>
<c:when test="${sessionScope.mvo!=null}">
<a href="${pageContext.request.contextPath}/post-detail.do?no=${pvo.no }">
${pvo.title}</a>
</c:when>
<c:otherwise>
${pvo.title}
</c:otherwise>
</c:choose>
</td>
<td>${pvo.memberVO.name}</td>
<td>${pvo.timePosted}</td>
<td>${pvo.hits}</td>
</tr>
</c:forEach>
</tbody>
</table><br></br>
<div class="pagingInfo">
<%-- 코드를 줄이기 위해 pb 변수에 pagingBean을 담는다. --%>
<c:set var="pb" value="${requestScope.lvo.pagingBean}"></c:set>
<!--
step2 1) 이전 페이지 그룹이 있으면 화살표 보여준다
페이징빈의 previousPageGroup 이용
2) 이미지에 이전 그룹의 마지막 페이지번호를 링크한다.
hint) startPageOfPageGroup-1 하면 됨
-->
<!-- step1. 1)현 페이지 그룹의 startPage부터 endPage까지 forEach 를 이용해 출력한다
2) 현 페이지가 아니면 링크를 걸어서 서버에 요청할 수 있도록 한다.
현 페이지이면 링크를 처리하지 않는다.
PagingBean의 nowPage
jstl choose 를 이용
예) <a href="DispatcherServlet?command=list&pageNo=...">
-->
<ul class="pagination">
<c:if test="${pb.previousPageGroup}">
<li><a href="${pageContext.request.contextPath}/list.do?pageNo=${pb.startPageOfPageGroup-1}">«</a></li>
</c:if>
<c:forEach var="i" begin="${pb.startPageOfPageGroup}"
end="${pb.endPageOfPageGroup}">
<c:choose>
<c:when test="${pb.nowPage!=i}">
<li><a href="${pageContext.request.contextPath}/list.do?pageNo=${i}">${i}</a></li>
</c:when>
<c:otherwise>
<li class="active"><a href="#">${i}</a></li>
</c:otherwise>
</c:choose>
</c:forEach>
<c:if test="${pb.nextPageGroup}">
<li><a href="${pageContext.request.contextPath}/list.do?pageNo=${pb.endPageOfPageGroup+1}">»</a></li>
</c:if>
</ul>
</div>
<!--
step3 1) 다음 페이지 그룹이 있으면 화살표 보여준다.
페이징빈의 nextPageGroup 이용
2) 이미지에 이전 그룹의 마지막 페이지번호를 링크한다.
hint) endPageOfPageGroup+1 하면 됨
-->
</div>
<div class="col-sm-2" ></div>
</div>
</div>
</body>
</html>
/post-detail.jsp
<%@ page language="java" contentType="text/html; charset=UTF-8"
pageEncoding="UTF-8"%>
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
<!DOCTYPE html>
<html lang="en">
<head>
<title>board</title>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/css/bootstrap.min.css">
<link rel="stylesheet" href="${pageContext.request.contextPath}/resources/css/board.css">
<script src="https://ajax.googleapis.com/ajax/libs/jquery/3.2.1/jquery.min.js"></script>
<script src="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/js/bootstrap.min.js"></script>
<script type="text/javascript">
$(document).ready(function(){
$("#deleteForm").submit(function() {
return confirm("게시글을 삭제하시겠습니까?");
});//deleteForm
$("#updateForm").submit(function() {
return confirm("게시글을 수정하시겠습니까?");
});//updateForm
});//ready
</script>
</head>
<body>
<!-- container-fluid: 화면 너비와 상관없이 항상 100% -->
<div class="container-fluid">
<div class="row header">
<div class="col-sm-2" ></div>
<div class="col-sm-8" align="right">
<c:import url="/WEB-INF/views/member/login.jsp"></c:import>
</div>
<div class="col-sm-2" ></div>
</div>
<div class="row main">
<div class="col-sm-2" ></div>
<div class="col-sm-8">
<table class="table">
<tr >
<td>글번호 ${requestScope.pvo.no }</td>
<td>제목: ${requestScope.pvo.title} </td>
<td>작성자 : ${requestScope.pvo.memberVO.name }</td>
<td>조회수 : ${requestScope.pvo.hits }</td>
<td>${requestScope.pvo.timePosted }</td>
</tr>
<tr>
<td colspan="5" class="content">
<pre>${requestScope.pvo.content}</pre>
</td>
</tr>
<tr>
<td colspan="5" class="btnArea">
<c:if test="${requestScope.pvo.memberVO.id==sessionScope.mvo.id}">
<button form="deleteForm" type="submit">삭제</button>
<button form="updateForm" type="submit">수정</button>
<!-- 삭제 form -->
<form action="deletePost.do" id="deleteForm" method="POST">
<input type="hidden" name="no" value="${requestScope.pvo.no}">
</form>
<!-- 수정 form -->
<form action="updateView.do" id="updateForm" method="POST">
<input type="hidden" name="no" value="${requestScope.pvo.no}">
</form>
</c:if>
</td>
</tr>
</table>
</div>
<div class="col-sm-2" ></div>
</div>
</div>
</body>
</html>
/write.jsp
<%@ page language="java" contentType="text/html; charset=UTF-8"
pageEncoding="UTF-8"%>
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
<!DOCTYPE html>
<html lang="en">
<head>
<title>board</title>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/css/bootstrap.min.css">
<link rel="stylesheet" href="${pageContext.request.contextPath}/resources/css/board.css">
<script src="https://ajax.googleapis.com/ajax/libs/jquery/3.2.1/jquery.min.js"></script>
<script src="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/js/bootstrap.min.js"></script>
</head>
<body>
<!-- container-fluid: 화면 너비와 상관없이 항상 100% -->
<div class="container-fluid">
<div class="row header">
<div class="col-sm-2" ></div>
<div class="col-sm-8" align="right">
<c:import url="/WEB-INF/views/member/login.jsp"></c:import>
</div>
<div class="col-sm-2" ></div>
</div>
<div class="row main">
<div class="col-sm-2" ></div>
<div class="col-sm-8">
<form action="${pageContext.request.contextPath}/write.do" method="post" id="write_form">
<table class="table" >
<tr>
<td>제목
<input type="text" name="title" placeholder="게시글 제목을 입력하세요" required="required">
</td>
</tr>
<tr>
<td>
<textarea cols="90" rows="15" name="content" required="required" placeholder="본문내용을 입력하세요"></textarea>
</td>
</tr>
</table>
<div class="btnArea">
<button type="submit" class="btn" >확인</button>
<button type="reset" class="btn" >취소</button>
</div>
</form>
<!-- jQuery Ajax -->
<script type="text/javascript">
$(document).ready(function() {
//submit 버튼의 아이디를 처리할 때는 form의 아이디로 select한다.
$("#write_form").submit(function() {
//취소를 누르면 return false로 submit되지 않는다.
return confirm("글을 등록하시겠습니까?");
});//submit
});ready
</script>
</div>
<div class="col-sm-2" ></div>
</div>
</div>
</body>
</html>
/update.jsp
<%@ page language="java" contentType="text/html; charset=UTF-8"
pageEncoding="UTF-8"%>
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core"%>
<!DOCTYPE html>
<html lang="en">
<head>
<title>board</title>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<link rel="stylesheet"
href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/css/bootstrap.min.css">
<link rel="stylesheet"
href="${pageContext.request.contextPath}/resources/css/board.css">
<script
src="https://ajax.googleapis.com/ajax/libs/jquery/3.2.1/jquery.min.js"></script>
<script
src="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/js/bootstrap.min.js"></script>
<script type="text/javascript">
$(document).ready(function() {
$("#updateForm").submit(function() {
return true;
});
});
</script>
</head>
<body>
<!-- container-fluid: 화면 너비와 상관없이 항상 100% -->
<div class="container-fluid">
<div class="row header">
<div class="col-sm-2"></div>
<div class="col-sm-8" align="right">
<c:import url="/WEB-INF/views/member/login.jsp"></c:import>
</div>
<div class="col-sm-2"></div>
</div>
<div class="row main">
<div class="col-sm-2"></div>
<div class="col-sm-8">
<form method="post" id="updateForm"
action="${pageContext.request.contextPath}/updatePost.do">
<input type="hidden" name="no" value="${pvo.no}">
<table class="table">
<tr>
<td>제목 <input type="text" name="title"
value="${pvo.title}" required="required">
</td>
</tr>
<tr>
<td><textarea cols="90" rows="15" name="content"
required="required">${pvo.content}</textarea></td>
</tr>
</table>
<div class="btnArea">
<button type="submit" class="btn">수정</button>
<button type="reset" class="btn">취소</button>
</div>
</form>
</div>
<div class="col-sm-2"></div>
</div>
</div>
</body>
</html>
/member
/login.jsp
<%@ page language="java" contentType="text/html; charset=UTF-8"
pageEncoding="UTF-8"%>
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
<script src="//code.jquery.com/jquery.min.js"></script>
<script type="text/javascript">
$(document).ready(function(){
$("#logout").click(function() {
if (confirm("로그아웃 하시겠습니까?"))
location.href = "logout.do";
else
return;
});//logout
});//ready
</script>
<p>
<c:choose>
<c:when test="${sessionScope.mvo==null}">
<form method="post" action="${pageContext.request.contextPath}/login.do">
아이디 <input type="text" name="id" size="7" >
비밀번호 <input type="password" name="password" size="7">
<input type="submit" value="로그인">
</form>
</c:when>
<c:otherwise>
<a href="${pageContext.request.contextPath}/list.do">홈</a>
<a href="${pageContext.request.contextPath}/writeForm.do">| 글쓰기</a>
| ${sessionScope.mvo.name}님 | <a href="#" id="logout">로그아웃</a>
</c:otherwise>
</c:choose>
</p>
/login_result.jsp
<%@ page language="java" contentType="text/html; charset=UTF-8"
pageEncoding="UTF-8" %>
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
<c:choose>
<c:when test="${sessionScope.mvo==null}">
<script type="text/javascript">
alert("로그인 실패!");
location.href="${pageContext.request.contextPath}/list.do";
</script>
</c:when>
<c:otherwise>
<script type="text/javascript">
alert("${sessionScope.mvo.name}님 로그인 ok!");
location.href="${pageContext.request.contextPath}/list.do";
</script>
</c:otherwise>
</c:choose>
/src/main/resource/
/board.css
@CHARSET "utf-8";
.header {
padding-top: 20px;
padding-bottom: 30px;
}
.title {
width: 50%;
}
.boardlist th,.boardlist td,.btnArea,.pagingInfo{
text-align: center;
}
MyBatis Proxy 설정 !
1. MemberMapper.xml의 namespace의 경로는 MemberMapper.java<<interface>>의 package.class명이 동일해야 한다.
2. MemberMapper.java<<interface>>의 메서드명은 MemberMapper.xml의 id와 동일해야 한다.
/src/main/resource/org.kosta.myproject.model.mapper
★ insert 시, has a 관계 입력하기 ★
- resultMap사용 (resultMap은 상속이 가능하다) * vo별칭1은 자식 VO(postVO), vo별칭2는 부모 VO(memberVO)를 지정해준다. * 하나의 resultMap에 postVO instance변수를 모두 넣어도 되지만, 성능을 위해 나누어서 생성하고, 상속받아 사용한다. |
/MemberMapper.xml
<?xml version="1.0" encoding="UTF-8"?>
<!-- Sql Mapper -->
<!DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="org.kosta.springmvc11.model.mapper.MemberMapper">
<sql id="selectMember">
SELECT id, password, name, address
FROM spring_member
</sql>
<select id="login" parameterType="memberVO" resultType="memberVO">
<include refid="selectMember"/>
WHERE id=#{id} AND password=#{password}
</select>
</mapper>
/BoardMapper.xml
<?xml version="1.0" encoding="UTF-8"?>
<!-- Sql Mapper -->
<!DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="org.kosta.springmvc11.model.mapper.BoardMapper">
<!-- 0. 총 게시물 수 -->
<select id="getTotalPostCount" resultType="int">
SELECT COUNT(*)
FROM spring_board_inst
</select>
<!-- has a 관계 표현 -->
<resultMap type="postVO" id="boardRM">
<result property="memberVO.id" column="id"/>
<result property="memberVO.name" column="name"/>
</resultMap>
<!-- 1. 게시물 조회 -->
<select id="getPostList" parameterType="pagingBean" resultMap="boardRM">
SELECT b.no,b.title,b.time_posted, b.hits, b.id, m.name
FROM ( SELECT row_number() over(order by no desc) as rnum,no,title,hits,
to_char(time_posted,'YYYY.MM.DD') as time_posted,id
FROM spring_board_inst) b, spring_member m
WHERE b.id=m.id and rnum between #{startRowNumber} AND #{endRowNumber}
ORDER BY no DESC
</select>
<!-- 2. 게시물 등록 -->
<insert id="write" parameterType="postVO">
<selectKey keyProperty="no" resultType="int" order="BEFORE">
SELECT spring_board_inst_seq.nextval
FROM dual
</selectKey>
INSERT INTO spring_board_inst(no, title, content, time_posted, id)
VALUES(#{no}, #{title}, #{content}, sysdate, #{memberVO.id})
</insert>
<!-- 3. 게시글 상세 보기 -->
<select id="getPostDetail" resultMap="boardRM">
SELECT b.no,b.title,to_char(b.time_posted,'YYYY.MM.DD HH24:MI:SS') as
time_posted, b.content, b.hits,m.id, m.name
FROM spring_board_inst b,spring_member m
where b.id=m.id AND no=#{value}
</select>
<!-- 4. 조회수 증가 -->
<update id="updateHits" parameterType="int">
UPDATE spring_board_inst SET hits=hits+1 WHERE no=#{no}
</update>
<!-- 5. 게시글 수정 -->
<update id="updatePost" parameterType="postVO">
UPDATE spring_board_inst
SET title=#{title}, content=#{content}
WHERE no=#{no}
</update>
<!-- 6. 게시글 삭제 -->
<delete id="deletePost" parameterType="int">
DELETE FROM spring_board_inst
WHERE no=#{value}
</delete>
</mapper>
src/main/java/org.kosta.myproject.model.mapper
- 어노테이션 @Mapper를 명시하면,
스프링 컨테이너에 의해 Proxy구현체 (DAOImpl의 역할)가 생성된다.
/MemberMapper.java<<interface>>
package org.kosta.springmvc11.model.mapper;
import org.apache.ibatis.annotations.Mapper;
import org.kosta.springmvc11.model.vo.MemberVO;
@Mapper
public interface MemberMapper {
// 로그인
public MemberVO login(MemberVO paramVO);
}
/BoardMapper.java<<interface>>
package org.kosta.springmvc11.model.mapper;
import java.util.List;
import org.kosta.springmvc11.model.service.PagingBean;
import org.kosta.springmvc11.model.vo.PostVO;
public interface BoardMapper {
// 0 . 총 게시물 수 조회
int getTotalPostCount();
// 1. 게시물 조회 (PagingBean 객체 매개변수로 설정)
List<PostVO> getPostList(PagingBean pagingBean);
// 2. 게시물 등록
void write(PostVO postVO);
// 3. 게시글 상세보기
PostVO getPostDetail(int no);
// 4. 조회수 증가
void updateHits(int no);
// 5. 게시글 수정
void updatePost(PostVO pvo);
// 6. 게시글 삭제
void deletePost(int i);
}
SQL
1. spring_member 테이블 - 부모테이블

2. spring_board_inst 테이블 - 자식테이블

Model
/src/main/java/org.kosta.myproject.model.vo
/MemberVO.java
package org.kosta.springmvc11.model.vo;
public class MemberVO {
private String id;
private String password;
private String name;
private String address;
public MemberVO() {
super();
}
public MemberVO(String id, String password, String name, String address) {
super();
this.id = id;
this.password = password;
this.name = name;
this.address = address;
}
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 String getAddress() {
return address;
}
public void setAddress(String address) {
this.address = address;
}
@Override
public String toString() {
return "MemberVO [id=" + id + ", password=" + password + ", name="
+ name + ", address=" + address + "]";
}
}
/PostVO.java
package org.kosta.springmvc11.model.vo;
public class PostVO {
private int no;
private String title;
private String content;
private int hits;
private String timePosted;
private MemberVO memberVO;
public PostVO() {
super();
}
public PostVO(int no, String title, String content, int hits,
String timePosted, MemberVO memberVO) {
super();
this.no = no;
this.title = title;
this.content = content;
this.hits = hits;
this.timePosted = timePosted;
this.memberVO = memberVO;
}
public int getNo() {
return no;
}
public void setNo(int no) {
this.no = no;
}
public String getTitle() {
return title;
}
public void setTitle(String title) {
this.title = title;
}
public String getContent() {
return content;
}
public void setContent(String content) {
this.content = content;
}
public int getHits() {
return hits;
}
public void setHits(int hits) {
this.hits = hits;
}
public String getTimePosted() {
return timePosted;
}
public void setTimePosted(String timePosted) {
this.timePosted = timePosted;
}
public MemberVO getMemberVO() {
return memberVO;
}
public void setMemberVO(MemberVO memberVO) {
this.memberVO = memberVO;
}
@Override
public String toString() {
return "PostVO [no=" + no + ", title=" + title + ", content="
+ content + ", hits=" + hits + ", timePosted=" + timePosted
+ ", memberVO=" + memberVO + "]";
}
}
/ListVO.java
package org.kosta.springmvc11.model.vo;
import java.util.List;
import org.kosta.springmvc11.model.service.PagingBean;
/**
* 게시물 리스트 정보와 페이징 정보를 가지고 있는 클래스
* @author inst
*
*/
public class ListVO {
private List<PostVO> list;
private PagingBean pagingBean;
public ListVO() {
super();
// TODO Auto-generated constructor stub
}
public ListVO(List<PostVO> list, PagingBean pagingBean) {
super();
this.list = list;
this.pagingBean = pagingBean;
}
public List<PostVO> getList() {
return list;
}
public void setList(List<PostVO> list) {
this.list = list;
}
public PagingBean getPagingBean() {
return pagingBean;
}
public void setPagingBean(PagingBean pagingBean) {
this.pagingBean = pagingBean;
}
@Override
public String toString() {
return "ListVO [list=" + list + ", pagingBean=" + pagingBean + "]";
}
}
/src/main/java/org.kosta.myproject.model.service
/PagingBean.java
package org.kosta.springmvc11.model.service;
/**
* 페이징 처리를 위한 비즈니스 계층의 클래스 PagingBean method 구현순서 getStartRowNumber()
* getEndRowNumber() getTotalPage() getTotalPageGroup() getNowPageGroup()
* getStartPageOfPageGroup() getEndPageOfPageGroup() isPreviousPageGroup()
* isNextPageGroup()
*
* @author kosta
*
*/
public class PagingBean {
/**
* 현재 페이지
*/
private int nowPage = 1;
/**
* 페이지당 게시물수
*/
private int contentNumberPerPage = 5;
/**
* 페이지 그룹당 페이지수
*/
private int pageNumberPerPageGroup = 4;
/**
* database에 저장된 총게시물수
*/
private int totalContents;
public PagingBean() {
}
public PagingBean(int totalContents) {
this.totalContents = totalContents;
}
public PagingBean(int totalContents, int nowPage) {
this.totalContents = totalContents;
this.nowPage = nowPage;
}
public int getNowPage() {
return nowPage;
}
/**
* 현재 페이지번호에 해당하는 시작 게시물의 row number를 반환 hint : 이전페이지의 마지막 번호 + 1 ((현재페이지-1)
* * 페이지당 게시물수) +1
*
* @return
*/
public int getStartRowNumber() {
return ((nowPage - 1) * contentNumberPerPage) + 1;
}
/**
* 현재 페이지에서 보여줄 게시물 행(row)의 마지막 번호
* 현재페이지*contentNumberPerPage 만약 총게시물수보다<br>
* 연산결과의 번호가 클 경우 총게시물수가 마지막 번호가 되어야 한다 ex) 총게시물수 7 개 총페이지는 2페이지 : 1 2 3 4 5<br>
* | 6 7 | 1page 2page 현재페이지는 2페이지이고 2*5(페이지당 게시물수) 는 10 이고 실제 마지막 번호 7이다 -><br>
* 연산결과가 총게시물수보다 클 경우 총게시물수가 마지막번호가 되어야 함
*
* @return
*/
public int getEndRowNumber() {
int endRowNumber = nowPage * contentNumberPerPage;
if (totalContents < endRowNumber)
endRowNumber = totalContents;
return endRowNumber;
}
/**
* 총 페이지 수를 return한다.<br>
* 1. 전체 데이터(게시물) % 한 페이지에 보여줄 데이터 개수 <br>
* => 0 이면 둘을 / 값이 총 페이지 수<br>
* 2. 전체 데이터(게시물) % 한 페이지에 보여줄 데이터 개수 <br>
* => 0보다 크면 둘을 / 값에 +1을 한 값이 총 페이지 수<br>
* 게시물수 1 2 3 4 5 6 7 8 9 10 11 12<br>
* 1페이지 1~5<br>
* 2페이지 6~10<br>
* 3페이지 11 <br>
* ex) 게시물 32 개 , 페이지당 게시물수 5개-> 7 페이지
*
* @return
*/
public int getTotalPage() {
int num = this.totalContents % this.contentNumberPerPage;
int totalPage = 0;
if (num == 0) {
totalPage = this.totalContents / this.contentNumberPerPage;
} else {
totalPage = this.totalContents / this.contentNumberPerPage + 1;
}
return totalPage;
}
/**
* 총 페이지 그룹의 수를 return한다.<br>
* 1. 총 페이지수 % Page Group 내 Page 수. <br>
* => 0 이면 둘을 / 값이 총 페이지 수<br>
* 2. 총 페이지수 % Page Group 내 Page 수. <br>
* => 0보다 크면 둘을 / 값에 +1을 한 값이 총 페이지 수<br>
* ex) 총 게시물 수 23 개 <br>
* 총 페이지 ? 총 페이지 그룹수 ? <br>
* 페이지 1 2 3 4 5<br>
* 페이지그룹 1234(1그룹) 5(2그룹)<br>
*
*/
public int getTotalPageGroup() {
int num = this.getTotalPage() % this.pageNumberPerPageGroup;
int totalPageGroup = 0;
if (num == 0) {
totalPageGroup = this.getTotalPage() / this.pageNumberPerPageGroup;
} else {
totalPageGroup = this.getTotalPage() / this.pageNumberPerPageGroup + 1;
}
return totalPageGroup;
}
/**
* 현재 페이지가 속한 페이지 그룹 번호(몇 번째 페이지 그룹인지) 을 return 하는 메소드 <br>
* 1. 현재 페이지 % Page Group 내 Page 수 => 0 이면 <br>
* 둘을 / 값이 현재 페이지 그룹. <br>
* 2. 현재 페이지 % Page Group 내 Page 수 => 0 크면 <br>
* 둘을 / 값에 +1을 한 값이 현재 페이지 그룹<br>
* 페이지 1 2 3 4 /5 6 7 8/ 9 10 1그룹 2그룹 3그룹
*
* @return
*/
public int getNowPageGroup() {
int num = this.nowPage % this.pageNumberPerPageGroup;
int nowPageGroup = 0;
if (num == 0) {
nowPageGroup = this.nowPage / this.pageNumberPerPageGroup;
} else {
nowPageGroup = this.nowPage / this.pageNumberPerPageGroup + 1;
}
return nowPageGroup;
}
/**
* 현재 페이지가 속한 페이지 그룹의 시작 페이지 번호를 return 한다.<br>
* Page Group 내 Page 수*(현재 페이지 그룹 -1) + 1을 한 값이 첫 페이지이다.<br>
* (페이지 그룹*페이지 그룹 개수, 그룹의 마지막 번호이므로) <br>
* 페이지 그룹 <br>
* 1 2 3 4 -> 5 6 7 8 -> 9 10 <br>
*
* @return
*/
public int getStartPageOfPageGroup() {
int num = this.pageNumberPerPageGroup * (this.getNowPageGroup() - 1) + 1;
return num;
}
/**
* 현재 페이지가 속한 페이지 그룹의 마지막 페이지 번호를 return 한다.<br>
* 1. 현재 페이지 그룹 * 페이지 그룹 개수 가 마지막 번호이다. <br>
* 2. 그 그룹의 마지막 페이지 번호가 전체 페이지의 마지막 페이지 번호보다 <br>
* 큰 경우는 전체 페이지의 마지막 번호를 return 한다.<br>
* 1 2 3 4 -> 5 6 7 8 -> 9 10
*
* @return
*/
public int getEndPageOfPageGroup() {
int num = this.getNowPageGroup() * this.pageNumberPerPageGroup;
if (this.getTotalPage() < num) {
num = this.getTotalPage();
}
return num;
}
/**
* 이전 페이지 그룹이 있는지 체크하는 메서드 <br>
* 현재 페이지가 속한 페이지 그룹이 1보다 크면 true<br>
* ex ) 페이지 1 2 3 4 / 5 6 7 8 / 9 10 <br>
* 1 2 3 group
*
* @return
*/
public boolean isPreviousPageGroup() {
boolean flag = false;
if (this.getNowPageGroup() > 1) {
flag = true;
}
return flag;
}
/**
* 다음 페이지 그룹이 있는지 체크하는 메서드 <br>
* 현재 페이지 그룹이 마지막 페이지 그룹(<br>
* 마지막 페이지 그룹 == 총 페이지 그룹 수) 보다 작으면 true<br>
* * ex ) 페이지 <br>
* 1 2 3 4 / 5 6 7 8 / 9 10 <br>
* 1 2 3 group
*
* @return
*/
public boolean isNextPageGroup() {
boolean flag = false;
if (this.getNowPageGroup() < this.getTotalPageGroup()) {
flag = true;
}
return flag;
}
public static void main(String args[]) {
PagingBean p = new PagingBean(47, 10);
// 현페이지의 시작 row number 를 조회 46
System.out.println("getBeginRowNumber:" + p.getStartRowNumber());
// 현페이지의 마지막 row number 를 조회 47
System.out.println("getEndRowNumber:" + p.getEndRowNumber());
// 전체 페이지 수 : 10
System.out.println("getTotalPage:" + p.getTotalPage());
// 전체 페이지 그룹 수 : 3
System.out.println("getTotalPageGroup:" + p.getTotalPageGroup());
System.out.println("////////////////////////////");
p = new PagingBean(31, 6);// 게시물수 31 현재 페이지 6
// 현페이지의 시작 row number 를 조회 26
System.out.println("getStartRowNumber:" + p.getStartRowNumber());
// 현페이지의 마지막 row number 를 조회 30
System.out.println("getEndRowNumber:" + p.getEndRowNumber());
// 게시물수 31 -> 총페이지수 7 -> 총페이지그룹->2
// 현재 페이지 그룹 : 2
System.out.println("getNowPageGroup:" + p.getNowPageGroup());
// 페이지 그룹의 시작 페이지 : 5
System.out.println("getStartPageOfPageGroup:" + p.getStartPageOfPageGroup());
// 페이지 그룹의 마지막 페이지 : 7
System.out.println("getEndPageOfPageGroup:" + p.getEndPageOfPageGroup());
// 이전 페이지 그룹이 있는 지 : true
System.out.println("isPreviousPageGroup:" + p.isPreviousPageGroup());
// 다음 페이지 그룹이 있는 지 : false
System.out.println("isNextPageGroup:" + p.isNextPageGroup());
}
}
/BoardService.java <<interface>>
package org.kosta.springmvc11.model.service;
import org.kosta.springmvc11.model.vo.ListVO;
import org.kosta.springmvc11.model.vo.PostVO;
public interface BoardService {
// getPostList() : 페이지 번호가 없을 때는 default 1 page
ListVO getPostList();
// getPostList(String pageNo) 오버로딩
ListVO getPostList(String pageNo);
// write(PostVO postVO) : 글쓰기
void write(PostVO postVO);
// getPostDetailNoHits : 게시판 상세 정보 조회 시, 조회수 증가 X
PostVO getPostDetailNoHits(int no);
// updateHits : 조회 수 증가
void updateHits(int no);
// deletePost : 게시글 삭제
void deletePost(int no);
// updatePost : 게시글 수정
void updatePost(PostVO postVO);
}
/BoardServiceImpl.java
package org.kosta.springmvc11.model.service;
import javax.annotation.Resource;
import org.kosta.springmvc11.model.mapper.BoardMapper;
import org.kosta.springmvc11.model.vo.ListVO;
import org.kosta.springmvc11.model.vo.PostVO;
import org.springframework.stereotype.Service;
@Service
public class BoardServiceImpl implements BoardService {
@Resource
private BoardMapper boardMapper;
// getPostList() : 페이지 번호가 없을 때는 default 1 page
@Override
public ListVO getPostList() {
return getPostList("1");
}
// getPostList(String pageNo) 오버로딩
@Override
public ListVO getPostList(String pageNo) {
int postTotalCount = boardMapper.getTotalPostCount();
PagingBean pagingBean = null;
if (pageNo == null)
pagingBean = new PagingBean(postTotalCount);
else
pagingBean = new PagingBean(postTotalCount, Integer.parseInt(pageNo));
ListVO listVO = new ListVO(boardMapper.getPostList(pagingBean), pagingBean);
return listVO;
}
// write(PostVO) : 글쓰기
@Override
public void write(PostVO postVO) {
boardMapper.write(postVO);
}
// getPostDetailNoHits : 게시판 상세 정보 조회 시, 조회수 증가 X
@Override
public PostVO getPostDetailNoHits(int no) {
return boardMapper.getPostDetail(no);
}
// updateHits : 조회 수 증가
@Override
public void updateHits(int no) {
boardMapper.updateHits(no);
}
// deletePost : 게시글 삭제
@Override
public void deletePost(int no) {
boardMapper.deletePost(no);
}
// updatePost : 게시글 수정
@Override
public void updatePost(PostVO postVO) {
boardMapper.updatePost(postVO);;
}
}
/MemberService.java <<interface>>
package org.kosta.springmvc11.controller;
import org.kosta.springmvc11.model.vo.MemberVO;
public interface MemberSerivce {
MemberVO login(MemberVO memberVO);
}
/MemberServiceImpl.java
package org.kosta.springmvc11.controller;
import javax.annotation.Resource;
import org.kosta.springmvc11.model.mapper.MemberMapper;
import org.kosta.springmvc11.model.vo.MemberVO;
import org.springframework.stereotype.Service;
@Service
public class MemberSerivceImpl implements MemberSerivce {
@Resource
private MemberMapper memberMapper;
@Override
public MemberVO login(MemberVO memberVO) {
return memberMapper.login(memberVO);
}
}
Controller
/src/main/java/org.kosta.myproject.controller
/HomeController.java
package org.kosta.springmvc11.controller;
import javax.annotation.Resource;
import org.kosta.springmvc11.model.service.BoardService;
import org.kosta.springmvc11.model.vo.ListVO;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.servlet.ModelAndView;
@Controller
public class HomeController {
@Resource
private BoardService boardService;
@RequestMapping("home.do")
public ModelAndView home() {
//listVO - ArrayList와 PagingBean이 있다.
ListVO listVO = boardService.getPostList();
return new ModelAndView("board/list", "lvo", listVO);
}
}
/BoardController.java
package org.kosta.springmvc11.controller;
import java.util.ArrayList;
import javax.annotation.Resource;
import javax.servlet.http.HttpSession;
import org.kosta.springmvc11.model.service.BoardService;
import org.kosta.springmvc11.model.vo.MemberVO;
import org.kosta.springmvc11.model.vo.PostVO;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.servlet.ModelAndView;
import org.springframework.web.servlet.mvc.support.RedirectAttributes;
@Controller
public class BoardController {
@Resource
private BoardService boardService;
// list.jsp에서 paging bar 클릭 시 게시판 리스트 페이지 이동
@RequestMapping("list.do")
public ModelAndView list(String pageNo) {
return new ModelAndView("board/list",
"lvo", boardService.getPostList(pageNo));
}
// 글쓰기 폼으로 이동
@RequestMapping("writeForm.do")
public String writeForm(HttpSession session) {
//세션 만료 시 홈으로 - AOP 대상(cross-cutting concern)
if (session.getAttribute("mvo") == null)
return "redirect:home.do";
return "board/write";
}
// 글쓰기 기능
@PostMapping("write.do")
//RedirectAttributes : 쿼리스트링 방식을 간단하게 처리할 수 있는 interface
public String write(PostVO postVO, HttpSession session, RedirectAttributes ra) {
//세션 만료 시 홈으로 - AOP 대상(cross-cutting concern)
if (session.getAttribute("mvo") == null)
return "redirect:home.do";
//세션에서 memberVO 정보 받아와서 postVO객체에 할당
// (작성자 정보를 삽입하기 위해)
MemberVO mvo = (MemberVO) session.getAttribute("mvo");
postVO.setMemberVO(mvo);
//글 작성 동작
boardService.write(postVO);
//RedirectAttributes 방식으로 정보 전달(postDetailNoHits()로 보냄)
ra.addAttribute("no", postVO.getNo());
//재동작 방지를 위해 redirect 방식으로 전송한다.
// 본인이 작성한 글이기 때문에, 카운트가 올라가지 않는 post-detail로 이동
return "redirect:post-detail-no-hits.do";
}
// 본인이 게시한 게시글 상세보기 (조회수 증가 X)
// - 글쓰기, 수정 시 사용
@RequestMapping("post-detail-no-hits.do")
public ModelAndView postDetailNoHits(int no) {
return new ModelAndView("board/post_detail",
"pvo", boardService.getPostDetailNoHits(no));
}
// 타인이 게시글 상세보기 (조회수 증가 O)
/**
* 상세 글 보기
* 1. 상세 글 보기 시에는 조회수가 증가되어야 한다.
* 2. 만약 조회한 글일 경우에는 조회수가 증가되지 않도록 해야 한다.
* (현재는 session을 통해 진행하는 것이다.
* 만약 더 넓게한다면 cookie, db에 저장하여 조회 기록을 남길 수 있다.
* - 주로 DB에 저장한다. ** 자동로그인의 경우는 cookie로 설정한다)
* @return
*/
@RequestMapping("post-detail.do")
public String postDetail(int no, HttpSession session, RedirectAttributes ra) {
//세션 만료 시 홈으로 - AOP 대상(cross-cutting concern)
if (session.getAttribute("mvo") == null)
return "redirect:home.do";
//MemberController의 login 메서드를 확인
@SuppressWarnings("unchecked")
ArrayList<Integer> noList
= (ArrayList<Integer>) session.getAttribute("noList");
//noList에 조회하는 게시글의 번호가 존재하지 않으면
if (noList.contains(no) == false) {
boardService.updateHits(no); //조회 수 증가
noList.add(no); //noList에 조회한 게시글 no 추가
}
// 데이터 전달 (조회한 게시글의 no)
ra.addAttribute("no", no);
return "redirect:post-detail-no-hits.do";
}
@PostMapping("deletePost.do")
public String deletePost(int no, HttpSession session) {
//세션 만료 시 홈으로 - AOP 대상(cross-cutting concern)
if (session.getAttribute("mvo") == null)
return "redirect:home.do";
//게시글 삭제
boardService.deletePost(no);
return "redirect:list.do";
}
// 수정 폼으로 이동
@RequestMapping("updateView.do")
public String updateForm(int no, HttpSession session, Model model) {
//세션 만료 시 홈으로 - AOP 대상(cross-cutting concern)
if (session.getAttribute("mvo") == null)
return "redirect:home.do";
//pvo에 no로 포스팅 정보를 전송해준다.
model.addAttribute("pvo", boardService.getPostDetailNoHits(no));
return "board/update";
}
// 게시글 수정
@PostMapping("updatePost.do")
public ModelAndView updatePost(PostVO updateVO, HttpSession session) {
//세션 만료 시 홈으로 - AOP 대상(cross-cutting concern)
if (session.getAttribute("mvo") == null)
return new ModelAndView("redirect:home.do");
// 게시글 수정
boardService.updatePost(updateVO);
// 수정된 게시글의 번호와 함께 post-detail로 보내기
return new ModelAndView("redirect:post-detail-no-hits.do?no="
+ updateVO.getNo());
}
}
/MemberController.java
package org.kosta.springmvc11.controller;
import java.util.ArrayList;
import javax.annotation.Resource;
import javax.servlet.http.HttpSession;
import org.kosta.springmvc11.model.service.MemberSerivce;
import org.kosta.springmvc11.model.vo.MemberVO;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
@Controller
public class MemberController {
@Resource
MemberSerivce memberService;
@PostMapping("login.do")
public String login(MemberVO paramVO, HttpSession session) {
MemberVO loginVO = memberService.login(paramVO);
if (loginVO != null) {
session.setAttribute("mvo", loginVO);
// 조회수 증가 방지를 위해 세션에 noList를 추가한다.
// noList : 조회한 게시글들의 게시글 no
session.setAttribute("noList", new ArrayList<Integer>());
}
return "member/login_result";
}
@RequestMapping("logout.do")
public String logout(HttpSession session) {
if (session != null) {
session.invalidate(); //세션 만료
}
return "redirect:home.do";
}
}
Test - 단위 테스트
/src/test/java/org.kosta.myproject
/BoardJUnitTest.java
package org.kosta.springmvc11;
import java.util.List;
import javax.annotation.Resource;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.kosta.springmvc11.model.mapper.BoardMapper;
import org.kosta.springmvc11.model.service.PagingBean;
import org.kosta.springmvc11.model.vo.MemberVO;
import org.kosta.springmvc11.model.vo.PostVO;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(
locations={"file:src/main/webapp/WEB-INF/spring-model.xml"})
public class BoardJUnitTest {
/*@Resource
private MemberMapper memberMapper;
@Test
public void memberTest(){
MemberVO paramVO = new MemberVO();
paramVO.setId("java");
paramVO.setPassword("1234");
MemberVO mvo = memberMapper.login(paramVO);
System.out.println("로그인 테스트: " + mvo);
// 출력값 :
// 로그인 테스트: MemberVO [id=java, password=1234, name=아이유, address=판교]
}*/
@Resource
private BoardMapper boardMapper;
@Test
public void boardTest() {
/*
// 0. 총 게시물 수
int totalPostCount = boardMapper.getTotalPostCount();
System.out.println("총 게시물 수 : "+totalPostCount);
// 총 페이지 수를 기준으로 PagingBean 객체 생성
PagingBean pb = new PagingBean(totalPostCount);
// 1. 게시물 리스트
List<PostVO> list = boardMapper.getPostList(pb);
for(PostVO vo:list) {
System.out.println(vo);
}
// 2. 게시물 등록
for (int i = 0; i < 40; i++) {
PostVO postVO = new PostVO();
postVO.setTitle("안녕하세요!"+i);
postVO.setContent("오늘은 날씨가 참 좋습니다!"+i);
postVO.setMemberVO(new MemberVO("java", null, null, null));
boardMapper.write(postVO);
PostVO postVO1 = new PostVO();
postVO1.setTitle("test");
postVO1.setContent("점심시간은 언제나 즐거워!");
postVO1.setMemberVO(new MemberVO("spring", null, null, null));
boardMapper.write(postVO1);
}
System.out.println(boardMapper.getTotalPostCount());
// 3. 게시글 상세보기
System.out.println(boardMapper.getPostDetail(1));
// 4. 조회수 증가
System.out.println(boardMapper.getPostDetail(1).getHits());
boardMapper.updateHits(1);
System.out.println(boardMapper.getPostDetail(1).getHits());
// 5. 게시물 수정
System.out.println(boardMapper.getPostDetail(1));
PostVO pvo = new PostVO();
pvo.setTitle("월요일이 사라졌다");
pvo.setContent("사라진 월요일은 어디로 간것인가?!");
pvo.setNo(1);
boardMapper.updatePost(pvo);
System.out.println(boardMapper.getPostDetail(1));*/
// 6. 게시글 삭제
System.out.println("기존 게시글 수:"+boardMapper.getTotalPostCount());
boardMapper.deletePost(1);
System.out.println("삭제 후 게시글 수:"+boardMapper.getTotalPostCount());
}
}
[ Browser 결과 화면 ]
1. list.jsp 화면 <welcome-file>home.do</welcome-file> 실행 결과

2. paging bar에서 다른 페이지로 이동

3. 로그인 성공 시, alert 후, list.do(header가 변경됨) 로 이동


4. 로그인 실패 시, alert 후, list.do 로 이동

5. 로그아웃 시, alert 후 '확인' 누르면 로그아웃됨 ('취소'누르면 로그인 화면으로 다시 돌아감)

6. 로그인 상태의 홈 화면에서 '글쓰기' 버튼 클릭 시,
'확인' 누르면 글 작성이되고, '취소' 누르면 return false되어 전 화면으로 돌아간다.


7. 글 작성 후, '확인'을 누르면, 본인이 쓴 글의 상세 정보를 볼 수 있다(단, 조회수는 증가하지 않는다.)

8. 본인이 쓴 글을 조회했을 때, '삭제', '수정' 버튼을 볼 수 있고, 조회수가 증가되지 않는다.

9. 타인이 쓴 글을 조회했을 때, '삭제', '수정' 버튼이 없으며, 조회수가 증가된다.

10. '삭제' 버튼을 누르면, confirm 후 게시글이 삭제되고, list로 다시 돌아간다.

11. '수정' 버튼을 누르면, confirm 후 게시글 수정 form이 제공된다. (기존 작성한 글이 적혀있다.)

12. '수정' 버튼을 누르면, 수정 완료 후, 수정한 게시글 상세정보로 이동한다.

'Java Web Programming > 6. Spring | MyBatis' 카테고리의 다른 글
[SpringBoot] 스프링부트 환경설정 (2) | 2020.11.17 |
---|---|
[SpringMVC/MyBatis] Transaction(트랜잭션) 적용 (회원관리) (0) | 2020.11.17 |
[SpringMVC/MyBatis] 마이바티스에도 Proxy(프록시)를 적용해보자! (0) | 2020.11.16 |
[SpringMVC/MyBatis] Ajax 적용해보기 (jQuery) + Session 세션 관리 (0) | 2020.11.13 |
[SpringMVC/MyBatis] SpringMVC 4.0 version 사용해보기 (+ Dynamic SQL - 동적 쿼리, 단위 테스트(JUnit)) (0) | 2020.11.12 |