Bigfat

[Web] 글쓰기 화면 만들기 (게시판 구현) 본문

Java/web

[Web] 글쓰기 화면 만들기 (게시판 구현)

kyou 2017. 4. 5. 13:47

브라우저로 글쓰기 화면을 출력하고, 게시글을 DB에 삽입해보자

  먼저 모델 2 구조로 게시판을 구현하고, 구현이 대부분 끝난 시점부터 스프링 프레임워크로 넘어가면서 다시 게시판을 구현하도록 한다.


  글쓰기 화면에서 글을 쓰고 저장하는 기능을 구현해보자. 일단 프로젝트가 어떤 구조로 진행될지 미리 확인해보자.


  싱글톤 패턴을 이용하여 데이터베이스 접근하기에서 만들었던 BBS테이블, 시퀀스와 BBSDto클래스, OracleDBConnector클래스, BBSOracleDao클래스를 그대로 사용하도록 한다. 단 OracleDBConnector클래스의 getConnection()메서드에 주석 처리해둔 core.log를 사용하여 쿼리문과 결과를 콘솔에 찍도록 한다. core.log를 사용하기 위해서는 http://log4sql.sourceforge.net/에서 log4sql.jar를 다운받아 톰캣 폴더의 lib폴더에 넣어줘야 한다.

package com.edu.bbs;

import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.SQLException;

/**
 * OracleDBConnector.java
 * Oracle Connection을 구현한 클래스
 * 여러 데이터베이스 사용을 위해 커넥션을 클래스로 분리한다.
 */
public class OracleDBConnector {
	//	private static OracleDBConnector orclDbc = new OracleDBConnector();
	private static OracleDBConnector orclDbc;
	
	private OracleDBConnector() {}
	
	public static OracleDBConnector getInstacne() {
		if(orclDbc == null) {
			orclDbc = new OracleDBConnector();
		}
		return orclDbc;
	}
	
	public Connection getConnection() throws ClassNotFoundException, SQLException  {
//		Class.forName("oracle.jdbc.driver.OracleDriver");
		// core.log를 사용하게되면 쿼리 결과를 콘솔에 찍을 수 있다.
		Class.forName("core.log.jdbc.driver.OracleDriver");
		String url = "jdbc:oracle:thin:@localhost:1521:XE";
		Connection conn = DriverManager.getConnection(url, "Kyou", "1234");
		return conn;
	}
}


  web.xml은 서블릿 생성하기(web.xml)에서 작성한 것을 그대로 사용한다.

<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://java.sun.com/xml/ns/javaee" xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_3_0.xsd" id="WebApp_ID" version="3.0">
  <display-name>bbs</display-name>
  <welcome-file-list>
    <welcome-file>index.jsp</welcome-file>
  </welcome-file-list>
  
  <servlet>
  	<servlet-name>bbsServlet</servlet-name>
  	<servlet-class>com.edu.bbs.BBSServlet</servlet-class>
  </servlet>
  <servlet-mapping>
  	<servlet-name>bbsServlet</servlet-name>
  	<url-pattern>*.bbs</url-pattern>
  </servlet-mapping>
</web-app>


  지난번에 작성한 BBSServlet클래스에 코드를 추가하자.

package com.edu.bbs;

import java.io.FileReader;
import java.io.IOException;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Properties;

import javax.servlet.RequestDispatcher;
import javax.servlet.ServletConfig;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;


public class BBSServlet extends HttpServlet {

	private static final long serialVersionUID = 1L;
	Properties properties;
	HashMap<String, BBSService> bbsMap;		// Upcasting : 부모 자리에 자식 올 수 있다.
	BBSService bbsService;
	
	@Override
	protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
		System.out.println("GET으로 요청");
		handling(req, resp);
	}

	@Override
	protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
		System.out.println("POST로 요청");
		handling(req, resp);
	}

	public void handling(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
		String servletPath = req.getServletPath();		// 서블릿경로만 뽑아냄, 하위에 쿼리 등 파라미터는 가져오지 않는다.
		
		String view = bbsMap.get(servletPath).bbsService(req, resp);
		if(view != null) {		// view가 null일 경우는 파일 다운로드에 페이지 이동 없는 것과 같은 경우
			RequestDispatcher reqDispatcher = req.getRequestDispatcher(view);
			reqDispatcher.forward(req, resp);		// 부모자리에 자식 올 수 있다(ServletRequest > HttpServletRequest)
		}
	}

	@Override
	public void init(ServletConfig config) throws ServletException {
		System.out.println("Init...");
		properties = new Properties();
		bbsMap = new HashMap<>();
		String configPath = config.getInitParameter("bbsProperties");	// web.xml
		try {
			properties.load(new FileReader(configPath));
			Iterator<Object> iterator = properties.keySet().iterator();
			while(iterator.hasNext()) {
				String key = (String)iterator.next();
				String value = properties.getProperty(key);
				
//				Class instanceValue = Class.forName(value);
				Class<?> instanceValue = Class.forName(value);
				bbsService = (BBSService)instanceValue.newInstance();
				bbsMap.put(key, bbsService);
			}
		} catch (Exception e) {
			e.printStackTrace();
		}
	}
	
}


  BBSServlet클래스의 init()메서드에서 읽어들이는 bbs.properties파일을 만들자. 이때 주의해야할 점은 공백이 있어서는 안된다. bbs.properties라는 파일을 읽어들여 http://localhost/bbs/writeForm.bbs로 접근했을 때 BBSServlet의 init()메서드가 동작하면서 com.edu.bbs패키지 아래 WriteFormImpl클래스를 리플랙션으로 메모리에 올린 후 HashMap에 담아둔다.

  WAS가 구동된다고 해서 init메서드가 실행되는 것은 아니다. 클라이언트, 즉 브라우저에서 서버에 요청했을 때(HttpServletRequest 인스턴스가 생성되었을 때) init메서드가 실행된다. 서버 실행 시 필요한 작업이 있다면 Listener를 이용하면 된다. 나중에 web.xml에 listener엘리먼트를 추가하도록 하고 지금은 넘어가자.

#bbs.properties
/writeForm.bbs=com.edu.bbs.WriteFormImpl
/write.bbs=com.edu.bbs.WriteImpl


  web.xml에 Servlet 엘리먼트 안에 <init-param>을 정의해주자. 이제 bbs.properties파일을 bbsProperties라는 이름으로 접근 가능다. bbsProperties의 경로는 개발환경에 맞게 변경하도록 하자.

<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://java.sun.com/xml/ns/javaee" xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_3_0.xsd" id="WebApp_ID" version="3.0">
  <display-name>bbs</display-name>
  <welcome-file-list>
    <welcome-file>index.jsp</welcome-file>
  </welcome-file-list>
  
  <servlet>
    <servlet-name>bbsServlet</servlet-name>
    <servlet-class>com.edu.bbs.BBSServlet</servlet-class>
    <init-param>
      <param-name>bbsProperties</param-name>
      <param-value>D:\Dev\education\bbs\WebContent\bbs.properties</param-value>
    </init-param>
  </servlet>
  <servlet-mapping>
    <servlet-name>bbsServlet</servlet-name>
    <url-pattern>*.bbs</url-pattern>
  </servlet-mapping>
</web-app>


  클라이언트의 요청을 처리하기 전에, 우선 BBSService인터페이스를 생성한다. 글쓰기 화면, 기능을 포함한 게시판 관련 클래스들을 BBSService인터페이스를 상속받아 인터페이스의 메서드들을 오버라이딩하도록 한다. 이는 각기 다른 클래스에서 공통적인 메서드를 사용함으로써 협업이나 유지보수에 도움을 준다.

package com.edu.bbs;

import java.io.IOException;

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

public interface BBSService {

	public String bbsService(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException;

}


  이제 우리는 http://localhost/bbs/writeForm.bbs로 클라이언트에서 접근했을 때, 요청에 대한 처리를 해보자. 로그인 기능이 아직 없으므로 세션에 임의의 ID를 심는다.

package com.edu.bbs;

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

public class WriteFormImpl implements BBSService {

	@Override
	public String bbsService(HttpServletRequest req, HttpServletResponse resp) throws ServletException {
		// 로그인 유무를 확인
		HttpSession session = req.getSession();
		session.setAttribute("id", "kim");		// 임의로 세션에 id를 심는다.
//		if(session.getAttribute("id") == null) {
//			return "/login.jsp";
//		}
		
		return "/writeForm.jsp";
	}

}


  WriteFormImpl클래스의 리턴 값인 writeForm.jsp을 만들자. 파일 업로드 기능은 나중에 추가하도록 한다. ${}으로 값을 표현하는 것을 볼 수 있는데 EL(Expression Language)이라 부르며 별도의 JAR를 추가해줘야 사용할 수 있다. javax.el-api-3.0.0.jar를 다운로드하여 WEB-INF/lib에 넣어주자.

<%@ page language="java" contentType="text/html; charset=UTF-8"
    pageEncoding="UTF-8"%>
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>BBS Write</title>
</head>
<body>
	<!-- enctype="application/x-www-form-urlencoded" 속성이 default로 잡혀있음 -->
	<!-- <form action="/bbs/write.bbs" method="post" enctype="application/x-www-form-urlencoded"> -->
	<form action="/bbs/write.bbs" method="post">
	    <input type="hidden" name="pageNum" value="${pageNum}">${pageNum}
	    <input type="hidden" name="depth" value="${article.depth}">
	    <input type="hidden" name="pos" value="${article.pos}">
	    <input type="hidden" name="groupId" value="${article.groupId}">
	    
	    <div align="center">
			<!-- HTML5에서는 태그 속성을 바로 명시하지 않고, CSS를 작성하여 붙여준다. -->
			<table border="2" width="200">
				<tr>
					<td>글쓴이 : </td>
					<td>${id}</td>
				</tr>
				<tr>
					<td>제목 : </td>
					<td><input type="text" name="title"></td>
				</tr>
				<tr>
					<td colspan="2">
						<textarea cols="50" rows="20" name="content"></textarea>
					</td>
				</tr>
				<tr>
					<td>첨부 : </td>
					<td><input type="file" name="fileName"></td>
				</tr>
				<tr>
					<td><input type="submit" value="글쓰기"></td>
					<td><input type="reset" value="글쓰기취소"></td>
				</tr>
			</table>
		</div>
	</form>
</body>
</html>


  화면에서 '글쓰기'버튼(submit)을 클릭하면 /bbs/write.bbs를 요청하게 되고, 서블릿은 WriteImpl클래스의 bbsService()메서드를 동작하고 return값인 /writeForm.bbs를 화면에 보여준다. 

package com.edu.bbs;

import java.io.IOException;

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

public class WriteImpl implements BBSService {

	@Override
	public String bbsService(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
		req.setCharacterEncoding("utf-8");
		BBSDto article = new BBSDto();
		
		article.setId(req.getSession().getAttribute("id").toString());
		article.setTitle(req.getParameter("title"));
		article.setContent(req.getParameter("content"));
		
		try {
			if(BBSOracleDao.getInstance().insertArticle(article) == 1) {
				System.out.println("게시글이 삽입되었습니다.");
			} else {
				System.out.println("게시글 삽입이 실패하였습니다.");
			}
		} catch (Exception e) {
			e.printStackTrace();
		}

//		return "/list.bbs";
		return "/writeForm.bbs";
	}

}


  http://localhost/bbs/writeForm.bbs로 접속해보자. 아래와 같은 화면이 뜨고, 제목과 내용을 입력한 후 '글쓰기'버튼을 누른다.


  DB에 레코드가 삽입된 것을 확인할 수 있고, 콘솔에서도 log4sql을 사용하였기 때문에 쿼리와 결과를 확인 가능하다.


  그런데 WriteImpl클래스에서 return값으로 /writeForm.bbs를 줬음에도 '글쓰기'버튼 클릭 후 URL이 http://localhost/bbs/write.bbs인 것을 확인할 수 있을 것이다. 또한 이 상태에서 새로고침(F5)를 누르면, 계속 같은 제목, 내용의 레코드를 삽입하고 있는 것을 DB에서 확인할 수 있다. 이를 이중 submit이라 부르며 방지해야한다. WriteImpl클래스의 코드를 아래와 같이 변경하자.

package com.edu.bbs;

import java.io.IOException;

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

public class WriteImpl implements BBSService {

	@Override
	public String bbsService(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
		req.setCharacterEncoding("utf-8");
		BBSDto article = new BBSDto();
		
		article.setId(req.getSession().getAttribute("id").toString());
		article.setTitle(req.getParameter("title"));
		article.setContent(req.getParameter("content"));
		
		try {
			if(BBSOracleDao.getInstance().insertArticle(article) == 1) {
				System.out.println("게시글이 삽입되었습니다.");
			} else {
				System.out.println("게시글 삽입이 실패하였습니다.");
			}
		} catch (Exception e) {
			e.printStackTrace();
		}

//		return "/list.bbs";
//		return "/writeForm.bbs";
		
		// 이중 submit 방지
		resp.sendRedirect("/bbs/writeForm.bbs");
		return null;
	}

}


  이제 글을 몇 번을 쓰더라도 URL은 http://localhost/bbs/writeForm.bbs로 돌아오며, 이중 submit이 발생하지 않을 것이다. 다음번 글목록을 출력하는 화면을 만들기 위해 게시글을 조금 저장해놓도록 하자.



[MVC패턴과 모델 1, 2 참고]

[JSP Expression Language(표현 언어 또는 익스프레션 언어) 참고]