관리 메뉴

거니의 velog

(13) 스프링으로 답변형 게시판 만들기 2 본문

Java_Spring Framework part2

(13) 스프링으로 답변형 게시판 만들기 2

Unlimited00 2023. 11. 21. 10:12

4. 게시판 목록 표시하기

* 게시판 글목록창은 이전에 이미 실습해 봤다. 이번에는 스프링에서 마이바티스와 타일즈를 이용해 게시판 글목록창을 나타내 보자.


(1) 자바 클래스 구현하기

1. 먼저 src/main/java 패키지 하위에 board 패키지를 만든 후 아래 그림처럼 패키지를 구성한다. 그리고 JSP는 이전에 실습한 게시판 관련 JSP 파일을 재사용할 것이므로 프로젝트 pro17의 board07 폴더에 있는 JSP 파일을 모두 복사해 views/board 폴더에 붙여 넣는다.

2. 컨트롤러 클래스인 BoardControllerImpl를 다음과 같이 작성한다. 브라우저에서 요청하면 모든 글 정보를 조회한 후 ModelAndView 객체에 바인딩하여 JSP로 전달한다.

@Controller("boardController")
public class BoardControllerImpl implements BoardController {
	private static final String ARTICLE_IMAGE_REPO = "C:\\board\\article_image";
	@Autowired
	private BoardService boardService;
	@Autowired
	private ArticleVO articleVO;

	@Override
	@RequestMapping(value = "/board/listArticles.do", method = { RequestMethod.GET, RequestMethod.POST })
	public ModelAndView listArticles(HttpServletRequest request, HttpServletResponse response) throws Exception {
		String viewName = (String) request.getAttribute("viewName"); // 인터셉터에서 전달된 뷰이름을 가져온다.
		List articlesList = boardService.listArticles(); // 모든 글 정보를 조회한다.
		ModelAndView mav = new ModelAndView(viewName);
		mav.addObject("articlesList", articlesList); // 조회한 글 정보를 바인딩한 후 JSP로 전달한다.
		return mav;
	}
 }

3. 서비스 클래스에서는 boardDAO의 selectAllArticlesList() 메서드를 호출한다.

@Service("boardService")
@Transactional(propagation = Propagation.REQUIRED)
public class BoardServiceImpl implements BoardService {
	@Autowired
	BoardDAO boardDAO;

	public List<ArticleVO> listArticles() throws Exception {
		List<ArticleVO> articlesList = boardDAO.selectAllArticlesList(); // boardDAO의 selectAllArticlesList() 메서드를 호출한다.
		return articlesList;
	}
 }

4. DAO 클래스는 다시 매퍼 파일은 board.xml에서 지정한 id로 SQL문을 실행한다.

@Repository("boardDAO")
public class BoardDAOImpl implements BoardDAO {
	@Autowired
	private SqlSession sqlSession;

	@Override
	public List selectAllArticlesList() throws DataAccessException {
		// id가 selectAllArticlesList인 SQL문을 요청한다.
		List<ArticleVO> articlesList = articlesList = sqlSession.selectList("mapper.board.selectAllArticlesList");
		return articlesList;
	}
}

5. 글 정보를 저장할 ArticleVO 클래스를 다음과 같이 작성한다.

package com.myspring.pro30.board.vo;

import java.io.UnsupportedEncodingException;
import java.net.URLDecoder;
import java.net.URLEncoder;
import java.sql.Date;

import org.springframework.stereotype.Component;

@Component("articleVO")
public class ArticleVO {
	private int level;
	private int articleNO;
	private int parentNO;
	private String title;
	private String content;
	private String imageFileName;
	private String id;
	private Date writeDate;

	public ArticleVO() {
		System.out.println("ArticleVO 생성자");
	}

	public int getArticleNO() {
		return articleNO;
	}

	public void setArticleNO(int articleNO) {
		this.articleNO = articleNO;
	}

	public int getParentNO() {
		return parentNO;
	}

	public void setParentNO(int parentNO) {
		this.parentNO = parentNO;
	}

	public int getLevel() {
		return level;
	}

	public void setLevel(int level) {
		this.level = level;
	}

	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 String getImageFileName() {
		try {
			if (imageFileName != null && imageFileName.length() != 0) {
				imageFileName = URLDecoder.decode(imageFileName, "UTF-8");
			}
		} catch (UnsupportedEncodingException e) {
			e.printStackTrace();
		}
		return imageFileName;
	}

	public void setImageFileName(String imageFileName) {
		try {
			if (imageFileName != null && imageFileName.length() != 0) {
				this.imageFileName = URLEncoder.encode(imageFileName, "UTF-8");
			}
		} catch (UnsupportedEncodingException e) {
			e.printStackTrace();
		}
	}

	public String getId() {
		return id;
	}

	public void setId(String id) {
		this.id = id;
	}

	public Date getWriteDate() {
		return writeDate;
	}

	public void setWriteDate(Date writeDate) {
		this.writeDate = writeDate;
	}

}

(2) JSP 파일 구현하기

* 이번에는 조회한 글을 표시할 JSP를 작성할 차례이다. 

1. 글목록창에 해당하는 listArticles.jsp를 다음과 같이 작성한다.

<%@ page language="java" contentType="text/html; charset=UTF-8"
    pageEncoding="UTF-8"
    isELIgnored="false" %>
<%@ taglib prefix="fmt" uri="http://java.sun.com/jsp/jstl/fmt" %>
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
<c:set var="contextPath" value="${pageContext.request.contextPath}" />
<%
  request.setCharacterEncoding("UTF-8");
%>
<!DOCTYPE html>
<html>

<head>
    <style>
        .cls1 {
            text-decoration: none;
        }

        .cls2 {
            text-align: center;
            font-size: 30px;
        }
    </style>
    <meta charset="UTF-8">
    <title>글목록창</title>
</head>
<script>
    function fn_articleForm(isLogOn, articleForm, loginForm) {
        if (isLogOn != '' && isLogOn != 'false') {
            location.href = articleForm;
        } else {
            alert("로그인 후 글쓰기가 가능합니다.")
            location.href = loginForm + '?action=/board/articleForm.do';
        }
    }
</script>

<body>
    <table align="center" border="1" width="80%">
        <tr height="10" align="center" bgcolor="lightgreen">
            <td>글번호</td>
            <td>작성자</td>
            <td>제목</td>
            <td>작성일</td>
        </tr>
        <c:choose>
            <c:when test="${articlesList ==null }">
                <tr height="10">
                    <td colspan="4">
                        <p align="center">
                            <b><span style="font-size:9pt;">등록된 글이 없습니다.</span></b>
                        </p>
                    </td>
                </tr>
            </c:when>
            <c:when test="${articlesList !=null }">
                <c:forEach var="article" items="${articlesList }" varStatus="articleNum">
                    <tr align="center">
                        <td width="5%">${articleNum.count}</td>
                        <td width="10%">${article.id }</td>
                        <td align='left' width="35%">
                            <span style="padding-right:30px"></span>
                            <c:choose>
                            	<%-- level이 1보다 크면 답글을 표시한다. --%>
                                <c:when test='${article.level > 1 }'>
                                    <c:forEach begin="1" end="${article.level }" step="1">
                                        <span style="padding-left:20px"></span>
                                    </c:forEach>
                                    <span style="font-size:12px;">[답변]</span>
                                    <a class='cls1' href="${contextPath}/board/viewArticle.do?articleNO=${article.articleNO}">${article.title}</a>
                                </c:when>
                                <%-- level이 1보다 작으면 부모 글을 표시한다. --%>
                                <c:otherwise>
                                    <a class='cls1' href="${contextPath}/board/viewArticle.do?articleNO=${article.articleNO}">${article.title }</a>
                                </c:otherwise>
                            </c:choose>
                        </td>
                        <td width="10%">${article.writeDate}</td>
                    </tr>
                </c:forEach>
            </c:when>
        </c:choose>
    </table>
    <!-- <a  class="cls1"  href="#"><p class="cls2">글쓰기</p></a> -->
    <a class="cls1" href="javascript:fn_articleForm('${isLogOn}','${contextPath}/board/articleForm.do', 
                                                    '${contextPath}/member/loginForm.do')">
        <p class="cls2">글쓰기</p>
    </a>
</body>

</html>

2. 왼쪽 메뉴의 각 항목을 클릭할 경우 해당 목록창으로 이동하도록 작성한다.

[side.jsp]

<%@ page language="java" contentType="text/html; charset=UTF-8"
    pageEncoding="UTF-8"
    isELIgnored="false" %>
<%@ taglib prefix="fmt" uri="http://java.sun.com/jsp/jstl/fmt" %>
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
<%
  request.setCharacterEncoding("UTF-8");
%>
<c:set var="contextPath" value="${pageContext.request.contextPath}" />
<!DOCTYPE html>

<html>

<head>
    <style>
        .no-underline {
            text-decoration: none;
        }
    </style>
    <meta charset="UTF-8">
    <title>사이드 메뉴</title>
</head>

<body>
    <h1>사이드 메뉴</h1>
    <!-- 
	<h1>
		<a href="#"  class="no-underline">회원관리</a><br>
	  <a href="#"  class="no-underline">게시판관리</a><br>
	  <a href="#"  class="no-underline">상품관리</a><br>
   </h1> 
    -->

    <h1>
    	<%-- 회원관리 클릭 시 회원목록 창으로 이동한다. --%>
        <a href="${contextPath}/member/listMembers.do" class="no-underline">회원관리</a><br>
    	<%-- 게시판관리 클릭 시 글목록 창으로 이동한다. --%>
        <a href="${contextPath}/board/listArticles.do" class="no-underline">게시판관리</a><br>
        <a href="#" class="no-underline">상품관리</a><br>
    </h1>

</body>

</html>

[servlet-context.xml]

<?xml version="1.0" encoding="UTF-8"?>
<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:mvc="http://www.springframework.org/schema/mvc"
	xmlns:context="http://www.springframework.org/schema/context"
	xsi:schemaLocation="http://www.springframework.org/schema/mvc http://www.springframework.org/schema/mvc/spring-mvc.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.xsd">

	<!-- DispatcherServlet Context: defines this servlet's request-processing 
		infrastructure -->

	<!-- Enables the Spring MVC @Controller programming model -->
	<annotation-driven />

	<!-- Handles HTTP GET requests for /resources/** by efficiently serving 
		up static resources in the ${webappRoot}/resources directory -->
	<resources mapping="/resources/**" location="/resources/" />

	<!-- 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:bean>

 -->
    <beans:bean id="tilesConfigurer" class="org.springframework.web.servlet.view.tiles2.TilesConfigurer">
		<beans:property name="definitions">
			<beans:list>
				<beans:value>classpath:tiles/*.xml</beans:value>
			</beans:list>
		</beans:property>
		<beans:property name="preparerFactoryClass"
			          value="org.springframework.web.servlet.view.tiles2.SpringBeanPreparerFactory" />
	</beans:bean>
	<beans:bean id="viewResolver"
		class="org.springframework.web.servlet.view.UrlBasedViewResolver">
		<beans:property name="viewClass" value="org.springframework.web.servlet.view.tiles2.TilesView" />
	</beans:bean>
    <context:component-scan	base-package="com.myspring.pro30" />
    
   <mvc:interceptors>
    	<mvc:interceptor>
	      <mvc:mapping path="/*/*.do"/>
	   <beans:bean class="com.myspring.pro30.common.interceptor.ViewNameInterceptor" />
	</mvc:interceptor>
</mvc:interceptors>

	<!-- 멀티파트 리졸버 -->
	<beans:bean id="multipartResolver" 	class="org.springframework.web.multipart.commons.CommonsMultipartResolver">
		<beans:property name="maxUploadSize" value="52428800" />
		<beans:property name="maxInMemorySize" value="52428800" />
		<beans:property name="defaultEncoding" value="utf-8" />
	</beans:bean>
	
</beans:beans>

3. 다음의 주소로 요청하면 메인 페이지가 나타난다.

- http://localhost/pro30/main.do

4. 왼쪽 메뉴에 있는 게시판 관리를 클릭하면 글목록창이 나타난다.


5. 새 글 추가하기

* 이번에는 새 글을 추가해 보자. 단, 새글 쓰기는 로그인 상태에서만 가능하다는 점에 유의하자.


(1) XML 파일 설정하기

1. 파일 업로드와 관련된 설정을 실습해 보자. pom.xml에 파일 첨부 기능과 관련된 라이브러리를 설정한다.

		<!-- 파일 업로드 -->
		<dependency>
			<groupId>commons-fileupload</groupId>
			<artifactId>commons-fileupload</artifactId>
			<version>1.2.1</version>
		</dependency>

		<dependency>
			<groupId>commons-io</groupId>
			<artifactId>commons-io</artifactId>
			<version>1.4</version>
		</dependency>

2. servlet-context.xml에 다중 파일 업로드 기능을 위한 멀티파트 리졸버 빈을 설정한다.

	<!-- 멀티파트 리졸버 -->
	<beans:bean id="multipartResolver"
		class="org.springframework.web.multipart.commons.CommonsMultipartResolver">
		<beans:property name="maxUploadSize" value="52428800" />
		<beans:property name="maxInMemorySize" value="52428800" />
		<beans:property name="defaultEncoding" value="utf-8" />
	</beans:bean>

3. 매퍼 파일인 board.xml에 새 글 추가 기능에 사용할 insert 문을 작성한다.

	<!-- 글 정보를 Map으로 전달한다. -->
	<insert id="insertNewArticle" parameterType="java.util.Map">
		<![CDATA[
			INSERT into t_board(articleNO,  title, content, imageFileName, id)
			VALUES(#{articleNO},#{title},	#{content}, #{imageFileName},	#{id})
		]]>
	</insert>

	<!-- 추가하는 새 글에 대한 글 번호를 가져온다. -->
	<select id="selectNewArticleNO" resultType="int">
		<![CDATA[
			SELECT nvl(max(articleNO), 0) + 1 from t_board		
		]]>
	</select>

4. 글쓰기창을 타일즈 파일인 tiles_board.xml에 설정한다.

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE tiles-definitions PUBLIC
"-//Apache Software Foundation//DTD Tiles Configuration 2.0//EN"
"http://tiles.apache.org/dtds/tiles-config_2_0.dtd">
<tiles-definitions>
	<definition name="/board/listArticles" extends="baseLayout">
	      <put-attribute name="title" value="글목록창" />
	      <put-attribute name="body" value="/WEB-INF/views/board/listArticles.jsp" />
	</definition>
	
	<!-- /board/articleForm 요청에 대해 글쓰기창을 나타낸다. -->
	<definition name="/board/articleForm" extends="baseLayout">
		<put-attribute name="title" value="글쓰기창" />
		<put-attribute name="body" value="/WEB-INF/views/board/articleForm.jsp" />
	</definition>
	
	<definition name="/board/viewArticle" extends="baseLayout">
    <put-attribute name="title" value="글상세창" />
    <put-attribute name="body" value="/WEB-INF/views/board/viewArticle.jsp" />
  </definition>
	
</tiles-definitions>

(2) 자바 클래스 구현하기

* 쇼핑몰에서 상품평을 작성하려면 당연히 로그인 상태에서 글을 작성해야 한다. 그런데 로그인을 하지 않은 상태에서 상품평 작성하기를 클릭하면 먼저 로그인창으로 이동하여 로그인을 한 후 다시 상품평 작성창으로 이동해야 한다. 

* 게시판 글을 작성할 때도 마찬가지이다. 먼저 로그인 상태를 체크한 후 로그인했으면 바로 글쓰기창으로 이동하고 그렇지 않으면 로그인 과정을 거친 후 다시 글쓰기 창으로 이동한다.

* 로그인 과정은 다음과 같다.

(1) 글목록창(listArticles.jsp) 요청 시 미리 세션의 isLogOn 속성을 자바스크립트 함수의 인자로 저장한다.

(2) 글쓰기를 클릭하면 자바스크립트 함수에서 isLogOn 속성 값을 체크하여 true가 아니면 memberController로 로그인 창을 요청하면서
    다음에 수행할 URL을 action 값으로 전송한다.
    
(3) memberCOntroller는 action 값을 세션에 저장한다.

(4) 로그인 창에서 ID와 비밀번호를 입력하여 memberController로 전송한 후 로그인에 성공하면 세션의 action 속성 값을 가져와서
    글쓰기 창으로 바로 이동한다.
    
(5) (2) 에서 isLogOn 속성이 true이면 바로 글쓰기 창으로 이동한다.

* 먼저 로그인을 하지 않은 상태에서 글쓰기를 요청할 경우 로그인을 처리하는 MemberControllerImpl 클래스를 수정해 보자.

1. MemberControllerImpl 클래스를 다음과 같이 작성한다. 로그인창을 나타내 주는 form() 메서드를 이용해 로그인한 다음 다시 나타낼 글쓰기창을 요청하기 위해 변수 action에 요청명을 저장한 후 세션에 다시 바인딩한다. 그리고 로그인 기능을 수행하는 login() 메서드를 이용해 로그인에 성공하면 세션의 action 값을 가져와 글쓰기 창으로 바로 이동한다.

	@Override
	@RequestMapping(value = "/member/login.do", method = RequestMethod.POST)
	public ModelAndView login(@ModelAttribute("member") MemberVO member,
				              RedirectAttributes rAttr, // 리다이렉트 시 매개변수를 전달한다.
		                      HttpServletRequest request, HttpServletResponse response) throws Exception {
	ModelAndView mav = new ModelAndView();
	memberVO = memberService.login(member);
	if(memberVO != null) { // 로그인 성공 시 조건문을 수행한다.
	    HttpSession session = request.getSession();
	    session.setAttribute("member", memberVO);
	    session.setAttribute("isLogOn", true);
	    //mav.setViewName("redirect:/member/listMembers.do");
	    String action = (String)session.getAttribute("action"); // 로그인 성공 시 세션에 저장된 action 값을 가져온다.
	    session.removeAttribute("action");
	    if(action!= null) {
	       mav.setViewName("redirect:"+action);
	    }else {
	       mav.setViewName("redirect:/member/listMembers.do");	
	    } // action 값이 null이 아니면 action 값을 뷰이름으로 지정해 글쓰기창으로 이동한다.

	}else {
	   rAttr.addAttribute("result","loginFailed");
	   mav.setViewName("redirect:/member/loginForm.do"); // 로그인 실패 시 다시 로그인창으로 이동한다.
	}
	return mav;
	}

	@Override
	@RequestMapping(value = "/member/logout.do", method =  RequestMethod.GET)
	public ModelAndView logout(HttpServletRequest request, HttpServletResponse response) throws Exception {
		HttpSession session = request.getSession();
		session.removeAttribute("member");
		session.removeAttribute("isLogOn");
		ModelAndView mav = new ModelAndView();
		mav.setViewName("redirect:/member/listMembers.do");
		return mav;
	}	

	@RequestMapping(value = "/member/*Form.do", method =  RequestMethod.GET)
	private ModelAndView form(@RequestParam(value= "result", required=false) String result,
							  @RequestParam(value= "action", required=false) String action,  // 로그인 후 수행할 글쓰기 요청명을 action에 저장한다. 로그인 성공 후 바로 글쓰기 창으로 이동한다.
						       HttpServletRequest request, 
						       HttpServletResponse response) throws Exception {
		String viewName = (String)request.getAttribute("viewName");
		HttpSession session = request.getSession();
		session.setAttribute("action", action); // 글쓰기창 요청명을 action 속성으로 세션에 저장한다.
		ModelAndView mav = new ModelAndView();
		mav.addObject("result",result);
		mav.setViewName(viewName);
		return mav;
	}

2. 이번에는 글쓰기 기능을 구현할 BoardControllerImpl 클래스를 작성한다. 글쓰기창에서 글 정보를 전송할 때 MultipartHttpServletRequest 클래스로 전달된 글 정보를 articleMap에 저장한다. upload() 메서드를 호출하여 이미지 파일을 temp 폴더에 업로드한 후 이미지 파일 이름을 가져온다. 마지막으로 articleMap에 글 정보를 저장한 후 BoardServiceImpl 클래스의 addNewArticle() 메서드를 호출하면서 전달한다.

	@Override
	@RequestMapping(value = "/board/addNewArticle.do", method = RequestMethod.POST)
	@ResponseBody
	public ResponseEntity addNewArticle(MultipartHttpServletRequest multipartRequest, HttpServletResponse response)
			throws Exception {
		
		multipartRequest.setCharacterEncoding("utf-8");
		Map<String, Object> articleMap = new HashMap<String, Object>(); // 글 정보를 저장하기 위한 articleMap을 생성한다.
		Enumeration enu = multipartRequest.getParameterNames();
		while (enu.hasMoreElements()) {
			String name = (String) enu.nextElement();
			String value = multipartRequest.getParameter(name);
			articleMap.put(name, value);
		} // 글쓰기 창에서 전송된 글 정보를 Map에 key/value로 저장한다.

		String imageFileName = upload(multipartRequest); // 업로드한 이미지 파일 이름을 가져온다.
		HttpSession session = multipartRequest.getSession();
		MemberVO memberVO = (MemberVO) session.getAttribute("member");
		String id = memberVO.getId(); // 세션에 저장된 회원 정보로부터 회원 ID를 가져온다.
		articleMap.put("parentNO", 0);
		articleMap.put("id", id);
		articleMap.put("imageFileName", imageFileName); // 회원 ID, 이미지 파일 이름, 부모 글 번호를 articleMap에 저장한다.

		String message;
		ResponseEntity resEnt = null;
		HttpHeaders responseHeaders = new HttpHeaders();
		responseHeaders.add("Content-Type", "text/html; charset=utf-8");
		try {
			int articleNO = boardService.addNewArticle(articleMap); // 글 정보가 저장된 articleMap을 Service 클래스의 addNewArticle() 메서드로 전달한다.
			if (imageFileName != null && imageFileName.length() != 0) {
				File srcFile = new File(ARTICLE_IMAGE_REPO + "\\" + "temp" + "\\" + imageFileName);
				File destDir = new File(ARTICLE_IMAGE_REPO + "\\" + articleNO);
				FileUtils.moveFileToDirectory(srcFile, destDir, true);
			} // 글 정보를 추가한 후 업로드한 이미지 파일을 글 번호로 명명한 폴더로 이동한다.

			message = "<script>";
			message += " alert('새글을 추가했습니다.');";
			message += " location.href='" + multipartRequest.getContextPath() + "/board/listArticles.do'; ";
			message += " </script>"; // 새 글을 추가한 후 메시지를 전달한다.
			resEnt = new ResponseEntity(message, responseHeaders, HttpStatus.CREATED); // 새 글을 추가한 후 메시지를 전달한다.
		} catch (Exception e) {
			File srcFile = new File(ARTICLE_IMAGE_REPO + "\\" + "temp" + "\\" + imageFileName);
			srcFile.delete();

			message = " <script>";
			message += " alert('오류가 발생했습니다. 다시 시도해 주세요');');";
			message += " location.href='" + multipartRequest.getContextPath() + "/board/articleForm.do'; ";
			message += " </script>"; // 오류 발생 시 오류 메시지를 전달한다.
			resEnt = new ResponseEntity(message, responseHeaders, HttpStatus.CREATED);
			e.printStackTrace();
		}
		
		return resEnt;
		
	}
    
    // 글쓰기 창을 나타낸다.
	@RequestMapping(value = "/board/*Form.do", method = RequestMethod.GET)
	private ModelAndView form(HttpServletRequest request, HttpServletResponse response) throws Exception {
		String viewName = (String) request.getAttribute("viewName");
		ModelAndView mav = new ModelAndView();
		mav.setViewName(viewName);
		return mav;
	}
    
	// 업로드한 파일 이름을 얻은 후 반환한다.
	private String upload(MultipartHttpServletRequest multipartRequest) throws Exception {
		String imageFileName = null;
		Iterator<String> fileNames = multipartRequest.getFileNames();

		while (fileNames.hasNext()) {
			String fileName = fileNames.next();
			MultipartFile mFile = multipartRequest.getFile(fileName);
			imageFileName = mFile.getOriginalFilename();
			File file = new File(ARTICLE_IMAGE_REPO + "\\" + "temp" + "\\" + fileName);
			if (mFile.getSize() != 0) { // File Null Check
				if (!file.exists()) { // 경로상에 파일이 존재하지 않을 경우
					file.getParentFile().mkdirs(); // 경로에 해당하는 디렉토리들을 생성
					mFile.transferTo(new File(ARTICLE_IMAGE_REPO + "\\" + "temp" + "\\" + imageFileName)); // 임시로 저장된
																											// multipartFile을
																											// 실제 파일로 전송
				}
			}

		}
		return imageFileName;
	}

3. Service 클래스에서는 컨트롤러에서 전달된 articleMap을 다시 DAO의 insertNewArticle() 메서드 인자로 전달한다.

[BoardServiceImpl.java]

@Service("boardService")
@Transactional(propagation = Propagation.REQUIRED)
public class BoardServiceImpl implements BoardService {
	@Autowired
	BoardDAO boardDAO;

	@Override
	public int addNewArticle(Map articleMap) throws Exception {
		return boardDAO.insertNewArticle(articleMap);
	}
    
    ...

4. DAO에서는 새 글에 대한 글 번호를 조회한 후 전달된 articleMap에 글 번호를 설정한다. 그리고 insert() 메서드를 호출하면서 articleMap을 해당 id의 insert 문으로 전달한다.

[BoardDAOImpl.java]

@Repository("boardDAO")
public class BoardDAOImpl implements BoardDAO {
	@Autowired
	private SqlSession sqlSession;

	@Override
	public int insertNewArticle(Map articleMap) throws DataAccessException {
		int articleNO = selectNewArticleNO(); // 새 글에 대한 글 번호를 가져온다.
		articleMap.put("articleNO", articleNO); // 글 번호를 articleMap에 저장한다.
		sqlSession.insert("mapper.board.insertNewArticle", articleMap); // id에 대한 insert문을 호출하면서 articleMap을 전달한다.
		return articleNO;
	}
    
	private int selectNewArticleNO() throws DataAccessException {
		return sqlSession.selectOne("mapper.board.selectNewArticleNO"); // 새 글 번호를 가져온다.
	}

(5) JSP 파일 구현하기

* 글목록창에서는 로그인 상태(isLogOn)를 미리 가져온다. 따라서 사용자가 게시판의 글쓰기 링크를 클릭하면 작성자의 로그인 여부에 따라 구분하여 화면에 나타낸다.

1. 글목록창에서 회원 로그인 상태(isLogOn)를 함수 인자 값으로 미리 저장해 놓는다. 글쓰기를 클릭하면 자바스크립트 함수를 호출하면서 로그인 상태 여부에 따라 각각 다른 요청을 수행한다.

[listArticles.jsp]

<%@ page language="java" contentType="text/html; charset=UTF-8"
    pageEncoding="UTF-8"
    isELIgnored="false" %>
<%@ taglib prefix="fmt" uri="http://java.sun.com/jsp/jstl/fmt" %>
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
<c:set var="contextPath" value="${pageContext.request.contextPath}" />
<%
  request.setCharacterEncoding("UTF-8");
%>
<!DOCTYPE html>
<html>

<head>
    <style>
        .cls1 {
            text-decoration: none;
        }

        .cls2 {
            text-align: center;
            font-size: 30px;
        }
    </style>
    <meta charset="UTF-8">
    <title>글목록창</title>
</head>
<script>
    function fn_articleForm(isLogOn, articleForm, loginForm) {
        if (isLogOn != '' && isLogOn != 'false') {
            location.href = articleForm; // 로그인 상태이면 글쓰기창으로 이동한다.
        } else {
            alert("로그인 후 글쓰기가 가능합니다.")
            // 로그아웃 상태이면 action 값으로 다음에 수행할 URL인 /board/articleForm.do를 전달하면서 로그인창으로 이동한다.
            location.href = loginForm + '?action=/board/articleForm.do';
        }
    }
</script>

<body>
    <table align="center" border="1" width="80%">
        <tr height="10" align="center" bgcolor="lightgreen">
            <td>글번호</td>
            <td>작성자</td>
            <td>제목</td>
            <td>작성일</td>
        </tr>
        <c:choose>
            <c:when test="${articlesList ==null }">
                <tr height="10">
                    <td colspan="4">
                        <p align="center">
                            <b><span style="font-size:9pt;">등록된 글이 없습니다.</span></b>
                        </p>
                    </td>
                </tr>
            </c:when>
            <c:when test="${articlesList !=null }">
                <c:forEach var="article" items="${articlesList }" varStatus="articleNum">
                    <tr align="center">
                        <td width="5%">${articleNum.count}</td>
                        <td width="10%">${article.id }</td>
                        <td align='left' width="35%">
                            <span style="padding-right:30px"></span>
                            <c:choose>
                                <c:when test='${article.level > 1 }'>
                                    <c:forEach begin="1" end="${article.level }" step="1">
                                        <span style="padding-left:20px"></span>
                                    </c:forEach>
                                    <span style="font-size:12px;">[답변]</span>
                                    <a class='cls1' href="${contextPath}/board/viewArticle.do?articleNO=${article.articleNO}">${article.title}</a>
                                </c:when>
                                <c:otherwise>
                                    <a class='cls1' href="${contextPath}/board/viewArticle.do?articleNO=${article.articleNO}">${article.title }</a>
                                </c:otherwise>
                            </c:choose>
                        </td>
                        <td width="10%">${article.writeDate}</td>
                    </tr>
                </c:forEach>
            </c:when>
        </c:choose>
    </table>
    
    <!-- <a  class="cls1"  href="#"><p class="cls2">글쓰기</p></a> -->
    
    <!-- 현재 로그인 상태를 함수 인자로 미리 전달한다. -->
    <!-- 로그인 상태일 경우 이동할 글쓰기창 요청 URL을 인자로 전달한다. -->
    <!-- 로그인 상태가 아닐 경우 로그인창 요청 URL를 전달한다. -->
    <a class="cls1" href="javascript:fn_articleForm('${isLogOn}', '${contextPath}/board/articleForm.do', '${contextPath}/member/loginForm.do')">
        <p class="cls2">글쓰기</p>
    </a>
</body>

</html>

2. 글쓰기창에서는 글 정보와 함께 이미지 파일을 첨부하여 컨트롤러로 전송한다.

[articleForm.jsp]

<%@ page language="java" contentType="text/html; charset=UTF-8"
     pageEncoding="UTF-8"
    isELIgnored="false" %>
<%@ taglib prefix="fmt" uri="http://java.sun.com/jsp/jstl/fmt" %>
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
<c:set var="contextPath" value="${pageContext.request.contextPath}" />
<%
  request.setCharacterEncoding("UTF-8");
%>

<head>
    <meta charset="UTF-8">
    <title>글쓰기창</title>
    <script src="http://code.jquery.com/jquery-latest.min.js"></script>
    <script type="text/javascript">
        function readURL(input) {
            if (input.files && input.files[0]) {
                var reader = new FileReader();
                reader.onload = function(e) {
                    $('#preview').attr('src', e.target.result);
                }
                reader.readAsDataURL(input.files[0]);
            }
        }

        function backToList(obj) {
            obj.action = "${contextPath}/board/listArticles.do";
            obj.submit();
        }

        var cnt = 1;

        function fn_addFile() {
            $("#d_file").append("<br>" + "<input type='file' name='file" + cnt + "' />");
            cnt++;
        }
    </script>
    <title>글쓰기창</title>
</head>

<body>
    <h1 style="text-align:center">글쓰기</h1>
    <form name="articleForm" method="post" action="${contextPath}/board/addNewArticle.do" enctype="multipart/form-data">
        <table border="0" align="center">
            <tr>
                <td align="right"> 작성자</td>
                <!-- 로그인하면 작성자의 이름을 표시한다. -->
                <td colspan=2 align="left"><input type="text" size="20" maxlength="100" value="${member.name }" readonly /> </td>
            </tr>
            <tr>
                <td align="right">글제목: </td>
                <td colspan="2"><input type="text" size="67" maxlength="500" name="title" /></td>
            </tr>
            <tr>
                <td align="right" valign="top"><br>글내용: </td>
                <td colspan=2><textarea name="content" rows="10" cols="65" maxlength="4000"></textarea> </td>
            </tr>
            <tr>
                <td align="right">이미지파일 첨부: </td>
                <td> <input type="file" name="imageFileName" onchange="readURL(this);" /></td>
                <td><img id="preview" src="#" width=200 height=200 /></td>


                <td align="right">이미지파일 첨부</td>
                <td align="left"> <input type="button" value="파일 추가" onClick="fn_addFile()" /></td>


            </tr>
            <tr>
                <td colspan="4">
                    <div id="d_file"></div>
                </td>
            </tr>
            <tr>
                <td align="right"> </td>
                <td colspan="2">
                    <input type="submit" value="글쓰기" />
                    <input type=button value="목록보기" onClick="backToList(this.form)" />
                </td>
            </tr>
        </table>
    </form>
</body>

</html>

3. 브라우저로 요청하여 글목록창을 나타낸 후 글쓰기를 클릭한다.

- http://localhost/pro30/main.do

4. 로그인 상태가 아닐 경우 로그인창으로 이동하여 로그인을 클릭한다.

5. 로그인에 성공하면 다시 글쓰기창으로 이동하여 글 정보를 입력하고 글쓰기를 클릭한다.

6. 다음과 같이 새 글 제목이 목록에 나타난다.