[스마트인재개발원] AJAX를 이용한 프론트엔드와 백엔드 사이의 통신

Lenny·2022년 2월 12일
0

스마트인재개발원

목록 보기
5/9

(죽여주는 노래 첨부합니다. ❤️아이러브키아누리브스❤️)

오늘은 프론트엔드와 백엔드 사이의 통신에 대해서 다뤄볼 것이다.

Let's Start

간단한 게시판 예제를 통하여 어떤식으로 통신하게 되는지 알아볼 것이다.

오늘 활약할 배우들을 소개하겠다.

src 폴더에는 백엔드 파일들이, WebContent 폴더에는 프론트엔드 파일들이 담겨져있다.
차근차근 패키지부터 파일까지 의미와 역할을 파악하고 들어가보도록 하자.

Backend

  • com.controller 패키지 : 백엔드 로직을 처리하는 친구들이다. (MVC 패턴의 Controller 기억나제?)
  • loginService : 로그인 성공 및 실패에 대한 로직이 담겨있는 파일
  • replyService : 댓글 등록 관련 로직이 담겨있는 파일
  • com.model 패키지 : MVC 패턴의 Model 관련 코드가 담겨있는 패키지이다. Model 은 대체적으로 DB랑 관련있는 객체들이 담겨있는 패키지라고 이해하면 이해가 쉽다.
  • BoardDAO : 게시글 및 댓글을 가져오는 코드들이 담겨있는 파일이다.

??? : 주인장님 BoardDAO 인데 댓글 가져오기는 왜 넣었음? 맞을래요?

내가 BoardDAO안에 넣고 싶어서 넣었겠어?! 게시글 안에 댓글들이 있잖아!!!!!!!!!!

  • BoardVO : 게시글의 벨류 오브젝트이다. 게시글 번호 / 게시글 이름 / 작성자 / 위치 / 게시글 내용 / 조회수 등의 정보들이 들어있다.

  • MemberVO : 유저의 벨류 오브젝트. 아이디 / 비밀번호 / 닉네임 등의 정보가 들어있다.

  • ReplyVO : 댓글 벨류 오브젝트. 댓글번호 / 게시글번호 / 댓글내용 / 작성자 등의 정보가 들어있다.

Frontend

  • board_list.jsp : 게시글 리스트
  • board_login.jsp : 로그인 페이지
  • board_main.jsp : 게시글 상세 정보 페이지

프론트엔드단은 .. 파일 이름만 보고도 어떤 페이지인지 충분히 유추를 할 수 있다.

그러면 이제 본격적으로 ResBoard 프로젝트를 해부해보자.
코드의 흐름대로 설명할것임!

캡처로 하기엔 너무 징글맞아서 오늘은 특별히 코드블럭을 사용할 것이다.

board_login.jsp

<%@ page language="java" contentType="text/html; charset=EUC-KR"
	pageEncoding="EUC-KR"%>
<!DOCTYPE html>
<html>
<head>
<meta charset="EUC-KR">
<title>Insert title here</title>
<link
	href="//maxcdn.bootstrapcdn.com/bootstrap/4.0.0/css/bootstrap.min.css"
	rel="stylesheet" id="bootstrap-css">
<script
	src="//maxcdn.bootstrapcdn.com/bootstrap/4.0.0/js/bootstrap.min.js"></script>
<script
	src="//cdnjs.cloudflare.com/ajax/libs/jquery/3.2.1/jquery.min.js"></script>
<link rel="stylesheet" href="asset/css/main.css">
<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link
	href="https://fonts.googleapis.com/css2?family=Do+Hyeon&display=swap"
	rel="stylesheet">
<style>
body {
	background-image: url('asset/img/bg.jpg');
	font-family: 'Do Hyeon', sans-serif;
	font-size: 30px;
}

#check {
	font-size: 20px;
	color: red;
}
</style>
</head>
<body>
	<div class="wrapper fadeInDown">
		<div id="formContent">

			<!-- Login Form -->
			<form action="#">
				<input type="text" id="id" class="fadeIn second" name="id"
					placeholder="login" style="margin-top: 30px"> <input
					type="password" id="password" class="fadeIn third" name="pw"
					placeholder="password">
				<p id="check"></p>
				<input type="button" id="submit" class="fadeIn fourth"
					value="Log In">
			</form>

		</div>
	</div>
	<script>
		const btn_submit = document.querySelector("#submit");
		const user_id = document.querySelector("#id");
		const user_pw = document.querySelector("#password");
		const checkMessage = document.querySelector("#check");
		
		const handleBtn = (e) => {
			if(user_id.value === "" || user_pw.value === "") {
				checkMessage.innerText = "아이디 / 비밀번호를 입력해주세요.";
			} else {
				// 바닐라 JS 통신
				// 1. JSON 형식 데이터 만들기
				const data = {"id" : user_id.value, "pw":user_pw.value};
				
				// 2. 데이터를 보내줄 서버 페이지랑 통신
				let xhr = new XMLHttpRequest();
				
				// 요청방식, 요청경로 지정
				xhr.open("post", "loginService");
				// 전송 데이터의 형식 지정
				xhr.setRequestHeader('Content-Type', 'application/json;charset=UTF-8');
				// 요청 & 전송 데이터 보내기
				xhr.send(JSON.stringify(data));
				
				xhr.onreadystatechange = function() {
					if(xhr.readyState === XMLHttpRequest.DONE){ // 요청 성공
						if(xhr.status === 200) {
							console.log("응답 성공"); 
							console.log(xhr.responseText) // 응답 데이터 확인 (responseXML)
							if(xhr.responseText === "success") {
								location.href = "board_list.jsp";
							} else {
								checkMessage.innerText = "아이디 / 비밀번호를 확인해주세요.";
							}
						} else {
							console.log("응답 실패");
							// fail 응답
						}
					} else {
						console.log("요청 실패");
					}
				}
				
			}
		}
		
		btn_submit.addEventListener("click", handleBtn);
	</script>
</body>
</html>

오우쒯... 징그러운건 코드블럭도 마찬가지였군.

HTML 관련한 내용은 생략하도록 할것이다.
로그인 페이지에서는 스크립트 부분을 보면 되겠군!

<script>
		const btn_submit = document.querySelector("#submit");
		const user_id = document.querySelector("#id");
		const user_pw = document.querySelector("#password");
		const checkMessage = document.querySelector("#check");
		
		const handleBtn = (e) => {
			if(user_id.value === "" || user_pw.value === "") {
				checkMessage.innerText = "아이디 / 비밀번호를 입력해주세요.";
			} else {
				// 바닐라 JS 통신
				// 1. JSON 형식 데이터 만들기
				const data = {"id" : user_id.value, "pw":user_pw.value};
				
				// 2. 데이터를 보내줄 서버 페이지랑 통신
				let xhr = new XMLHttpRequest();
				
				// 요청방식, 요청경로 지정
				xhr.open("post", "loginService");
				// 전송 데이터의 형식 지정
				xhr.setRequestHeader('Content-Type', 'application/json;charset=UTF-8');
				// 요청 & 전송 데이터 보내기
				xhr.send(JSON.stringify(data));
				
				xhr.onreadystatechange = function() {
					if(xhr.readyState === XMLHttpRequest.DONE){ // 요청 성공
						if(xhr.status === 200) {
							console.log("응답 성공"); 
							console.log(xhr.responseText) // 응답 데이터 확인 (responseXML)
							if(xhr.responseText === "success") {
								location.href = "board_list.jsp";
							} else {
								checkMessage.innerText = "아이디 / 비밀번호를 확인해주세요.";
							}
						} else {
							console.log("응답 실패");
							// fail 응답
						}
					} else {
						console.log("요청 실패");
					}
				}
				
			}
		}
		
		btn_submit.addEventListener("click", handleBtn);
	</script>

코드가 매우매우 길지만, 사실 이 코드에서 하는건 뭐 없다.
그냥 로그인 버튼을 누르면 input 태그의 입력한 아이디와 패스워드를 post 방식으로 loginService 서블릿 파일에 보내는 역할을 하는 코드이다.

저 코드가 어떻게 동작하는지는 모두 주석으로 적어두었다. 이보다 더 깔끔한 해부는 없다. 다음으로 넘어간다. 다음은 loginService 이다.

loginService

package com.controller;

/**
 * Servlet implementation class loginService
 */
@WebServlet("/loginService")
public class loginService extends HttpServlet {
	
	final String DB_URL = "jdbc:oracle:thin:@127.0.0.1:1521:xe";
	final String DB_ID = "hr";
	final String DB_PW = "hr";
	
	protected void service(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
		
		Connection conn = null;
		PreparedStatement psmt = null;
		ResultSet rs = null;
		
		// application/x-www-form-urlencoded ( getParameter 로 받아오려면 )
//		String id = request.getParameter("id");
//		String pw = request.getParameter("pw");
		
		// GSON 라이브러리 사용
		
		StringBuffer sb = new StringBuffer(); // 읽어온 데이터 저장
		String line = null; // 버퍼안의 데이터를 읽을 때 (임시 저장)
		
		BufferedReader reader = request.getReader(); // 요청 데이터를 읽어 올 때 사용
		
		while((line = reader.readLine()) != null) { // 읽을 데이터가 있을 때 반복 수행
			sb.append(line); // 읽어온 데이터를 stringbuffer에 추가
		}
		
		
		JsonParser parser = new JsonParser(); // 파싱 (문자열 -> JSON)
		JsonElement element = parser.parse(sb.toString()); // 버퍼 데이터 문자열로 변경 후  JSON으로 변환
		
		String id = element.getAsJsonObject().get("id").getAsString(); // 키 값이 id인 데이터
		String pw = element.getAsJsonObject().get("pw").getAsString(); // 키 값이 pw인 데이터
		
		
//		System.out.println("id : " + id);
//		System.out.println("pw : " + pw);
		
		// 로그인 가능할 경우 콘솔창에 로그인 성공 출력
		// 로그인 불가능할 경우 로그인 실패 출력
		
		try {
			Class.forName("oracle.jdbc.driver.OracleDriver");

			conn = DriverManager.getConnection(DB_URL, DB_ID, DB_PW);

			request.setCharacterEncoding("euc-kr");


			String query = "select * from mem where id= ?";
			psmt = conn.prepareStatement(query);
			psmt.setString(1, id);
			
			rs = psmt.executeQuery();

			PrintWriter out = response.getWriter(); // 응답 스트림 만드는거임
			
			if (rs.next()) {
				String rs_id = rs.getString(1);
				String rs_pw = rs.getString(2);
				String rs_nick = rs.getString(3);
				// DB에서 읽어온 사용자 데이터 -> MemberVO 객체 초기화
				MemberVO memberVO = new MemberVO(rs_id, rs_pw, rs_nick); 
				
				
				// 세션
				if(pw.equals(rs_pw)) {
					System.out.println("로그인 성공");
					HttpSession session = request.getSession();
					
					// 세션이 삭제되기 전까지는 사용자 정보 기억
					session.setAttribute("member", memberVO);
					
					// success 응답
					out.print("success");
				} else {
					System.out.println("로그인 실패");
					out.print("fail");
				}
			} else {
				System.out.println("로그인 실패");
			}

		} catch (Exception e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		} finally {
			try {
				// 런타임 오류 예외처리 (실행을 해야만 오류가 발생 했는지 알 수 있기 때문에..)
				rs.close();
				psmt.close();
				conn.close();
			} catch (Exception e) {
				e.printStackTrace();
			}
		}	
	}
}

여기서는 GSON 라이브러리를 사용해야한다.

GSON 이란?

Gson은 JSON의 자바 오브젝트의 직렬화, 역직렬화를 해주는 오픈 소스 자바 라이브러리이다. - 위키백과

쉽게말해서, 프론트단에서 JSON 형태의 데이터를 보내주면, 이 데이터를 다룰 수 있게끔 도와주는 라이브러리이다.

위 코드에서 Gson 라이브러리를 사용하는 부분은 이 부분임.

JsonParser parser = new JsonParser(); // 파싱 (문자열 -> JSON)
JsonElement element = parser.parse(sb.toString()); // 버퍼 데이터 문자열로 변경 후  JSON으로 변환
		
String id = element.getAsJsonObject().get("id").getAsString(); // 키 값이 id인 데이터
String pw = element.getAsJsonObject().get("pw").getAsString(); // 키 값이 pw인 데이터

JsonParser 와 JsonElement 객체가 Gson 라이브러리의 객체이다. 이 코드 위에 있는 부분들은 버퍼를 이용하여 값을 읽어들이는 코드이다. 주석을 읽어보면 동작원리가 적혀있기 때문에 쉽게 이해 할 수 있다. 이런식으로 프론트엔드에서 보내는 Json 데이터를 Gson 라이브러리로 자바에서 핸들링 할 수 있게 바꿔주고, 변환된 데이터를 이용하여 그 이후 작업을 하는것이다. (로그인 처리)

board_list.jsp

<%@ page language="java" contentType="text/html; charset=EUC-KR"
	pageEncoding="EUC-KR"%>
<%@ page import="java.util.*"%>
<%@ page import="com.model.BoardDAO"%>
<%@ page import="com.model.BoardVO"%>
<!DOCTYPE html>
<html>
<head>
<meta charset="EUC-KR">
<title>Insert title here</title>
<link rel="stylesheet"
	href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.5/css/bootstrap.min.css"
	integrity="sha512-dTfge/zgoMYpP7QbHy4gWMEGsbsdZeCXz7irItjcC3sPUFtf0kuFbDz/ixG7ArTxmDjLXDmezHubeNikyKGVyQ=="
	crossorigin="anonymous">
<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link
	href="https://fonts.googleapis.com/css2?family=Do+Hyeon&display=swap"
	rel="stylesheet">
<style>
body {
	background-image: url('asset/img/bg.jpg');
	font-family: 'Do Hyeon', sans-serif;
	font-size: 30px;
}

.container {
	background-color: whitesmoke;
	margin-top: 100px;
	padding-top: 20px;
}
</style>
</head>
<body>
	<%
	BoardDAO boardDAO = new BoardDAO();
	ArrayList<BoardVO> list = boardDAO.getList();
	%>
	<div class="container">
		<table class="table">
			<tr>
				<th>글 제목</th>
				<th>작성자</th>
				<th>조회수</th>
			</tr>
			<%
			for (int i = list.size() - 1; i >= 0; i--) {
			%>
			<tr>
				<td><a href="board_main.jsp?num=<%=list.get(i).getNum()%>"><%=list.get(i).getName()%></a></td>
				<td><%=list.get(i).getWriter()%></td>
				<td><%=list.get(i).getViews()%></td>
			</tr>
			<%
			}
			%>
		</table>
	</div>

</body>
</html>

게시글 리스트를 출력하는 코드이다. DB의 게시글 관련 테이블에 저장된 정보를 출력한다.

이런식으로~ 코드를 보면 BoardDAO 객체를 만들고, BoardVO의 ArrayList에서 boardDAO에 만든 리스트를 받아오는 코드를 이용하여, 차례대로 데이터를 출력해주고있다.

그러면 이제 boardDAO 코드를 관찰해보자!

BoardDAO

package com.model;

import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.util.ArrayList;

import javax.servlet.http.HttpSession;

public class BoardDAO {

	final String DB_URL = "jdbc:oracle:thin:@127.0.0.1:1521:xe";
	final String DB_ID = "hr";
	final String DB_PW = "hr";

	public ArrayList<BoardVO> getList() {

		ArrayList<BoardVO> boardList = new ArrayList<BoardVO>();
		Connection conn = null;
		PreparedStatement psmt = null;
		ResultSet rs = null;

		try {
			Class.forName("oracle.jdbc.driver.OracleDriver");

			conn = DriverManager.getConnection(DB_URL, DB_ID, DB_PW);

			String query = "select num, name, writer, views from board";
			psmt = conn.prepareStatement(query);

			rs = psmt.executeQuery();

			while (rs.next()) {
				int getNum = rs.getInt(1);
				String getName = rs.getString(2);
				String getWriter = rs.getString(3);
				int getViews = rs.getInt(4);

				BoardVO boardVO = new BoardVO(getNum, getName, getWriter, getViews);
				boardList.add(boardVO);
			}

		} catch (Exception e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		} finally {
			try {
				// 런타임 오류 예외처리 (실행을 해야만 오류가 발생 했는지 알 수 있기 때문에..)
				rs.close();
				psmt.close();
				conn.close();
			} catch (Exception e) {
				e.printStackTrace();
			}
		}
		return boardList;
	}

	public BoardVO getOneList(int num) {

		Connection conn = null;
		PreparedStatement psmt = null;
		ResultSet rs = null;
		BoardVO boardVO = null;

		try {
			Class.forName("oracle.jdbc.driver.OracleDriver");

			conn = DriverManager.getConnection(DB_URL, DB_ID, DB_PW);

			String query = "select name, writer, location, content from board where num = ? ";
			psmt = conn.prepareStatement(query);
			psmt.setInt(1, num);

			rs = psmt.executeQuery();

			if (rs.next()) {
				String getName = rs.getString(1);
				String getWriter = rs.getString(2);
				String getLocation = rs.getString(3);
				String getContent = rs.getString(4);

				boardVO = new BoardVO(getName, getWriter, getLocation, getContent);
			}

		} catch (Exception e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		} finally {
			try {
				// 런타임 오류 예외처리 (실행을 해야만 오류가 발생 했는지 알 수 있기 때문에..)
				rs.close();
				psmt.close();
				conn.close();
			} catch (Exception e) {
				e.printStackTrace();
			}
		}
		return boardVO;
	}
	
	public ArrayList<ReplyVO> getReply(int num) {
		ArrayList<ReplyVO> replyList = new ArrayList<ReplyVO>();
		Connection conn = null;
		PreparedStatement psmt = null;
		ResultSet rs = null;

		try {
			Class.forName("oracle.jdbc.driver.OracleDriver");

			conn = DriverManager.getConnection(DB_URL, DB_ID, DB_PW);

			String query = "select content, writer from reply where boardnum = ?";
			psmt = conn.prepareStatement(query);
			psmt.setInt(1, num);

			rs = psmt.executeQuery();

			while (rs.next()) {
				String getContent = rs.getString(1);
				String getWriter = rs.getString(2);


				ReplyVO replyVO = new ReplyVO(getContent, getWriter);
				replyList.add(replyVO);
			}

		} catch (Exception e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		} finally {
			try {
				// 런타임 오류 예외처리 (실행을 해야만 오류가 발생 했는지 알 수 있기 때문에..)
				rs.close();
				psmt.close();
				conn.close();
			} catch (Exception e) {
				e.printStackTrace();
			}
		}
		return replyList;
	}
}

요 요 요 BoardDAO 파일에는 3가지 메소드가 선언되어있다. 하나씩 뜯어보자.

getList()

public ArrayList<BoardVO> getList() {

		ArrayList<BoardVO> boardList = new ArrayList<BoardVO>();
		Connection conn = null;
		PreparedStatement psmt = null;
		ResultSet rs = null;

		try {
			Class.forName("oracle.jdbc.driver.OracleDriver");

			conn = DriverManager.getConnection(DB_URL, DB_ID, DB_PW);

			String query = "select num, name, writer, views from board";
			psmt = conn.prepareStatement(query);

			rs = psmt.executeQuery();

			while (rs.next()) {
				int getNum = rs.getInt(1);
				String getName = rs.getString(2);
				String getWriter = rs.getString(3);
				int getViews = rs.getInt(4);

				BoardVO boardVO = new BoardVO(getNum, getName, getWriter, getViews);
				boardList.add(boardVO);
			}

		} catch (Exception e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		} finally {
			try {
				// 런타임 오류 예외처리 (실행을 해야만 오류가 발생 했는지 알 수 있기 때문에..)
				rs.close();
				psmt.close();
				conn.close();
			} catch (Exception e) {
				e.printStackTrace();
			}
		}
		return boardList;
	}

리스트를 가져오는 코드이다. 반환타입은 BoardVO의 ArrayList이다.
DAO를 많이 써봤으면 이제 이정도 코드를 읽는건 껌의경지에 이르렀다고 할 수 있다.

SQL query문을 보면 바인딩 변수가 하나도 없는걸 알 수 있다. 그리고 그냥 가져오기만 하면되니까 select 이다. board 테이블의 게시글 번호, 제목, 작성자, 조회수를 조회한다. 그리고 조회해서 나온 데이터들을 이용하여 VO 객체를 만들고, 그 객체들을 ArrayList에 담는다.
그다음에 그 BoardVO가 담긴 ArrayList를 리턴해준다.

EASY

그리고 아까 본 board_list.jsp 에서는 이 메소드를 사용하여 VO객체들을 가져오고 페이지에 뿌려주는거임

<tr>
	<td><a href="board_main.jsp?num=<%=list.get(i).getNum()%>"><%=list.get(i).getName()%></a></td>
	<td><%=list.get(i).getWriter()%></td>
	<td><%=list.get(i).getViews()%></td>
</tr>

a링크를 보면 쿼리스트링으로 num 값을 넘겨서 데이터를 전달하고있죠?

이게 게시판의 기본이다.
그러면 쿼리스트링으로 보내준 num을 저 board_main.jsp 에서 get하고, 그 get 한 num 을 가져다가 게시글DB에 또 접근을 하겠죠? 그러면 그 num에 맞는 게시글의 상세 정보를 불러오는 메소드를 이용하여 화면에 뿌려주면 되겠죠? 실제로 이렇게 동작한다. 우선 다른 메소드들도 살펴보도록 하겠노라.

getOneList(int num)

	public BoardVO getOneList(int num) {

		Connection conn = null;
		PreparedStatement psmt = null;
		ResultSet rs = null;
		BoardVO boardVO = null;

		try {
			Class.forName("oracle.jdbc.driver.OracleDriver");

			conn = DriverManager.getConnection(DB_URL, DB_ID, DB_PW);

			String query = "select name, writer, location, content from board where num = ? ";
			psmt = conn.prepareStatement(query);
			psmt.setInt(1, num);

			rs = psmt.executeQuery();

			if (rs.next()) {
				String getName = rs.getString(1);
				String getWriter = rs.getString(2);
				String getLocation = rs.getString(3);
				String getContent = rs.getString(4);

				boardVO = new BoardVO(getName, getWriter, getLocation, getContent);
			}

		} catch (Exception e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		} finally {
			try {
				// 런타임 오류 예외처리 (실행을 해야만 오류가 발생 했는지 알 수 있기 때문에..)
				rs.close();
				psmt.close();
				conn.close();
			} catch (Exception e) {
				e.printStackTrace();
			}
		}
		return boardVO;
	}
	

이게 방금 말한 게시글 상세 정보 출력에 도움을 줄 메소드임.

SQL query문을 보면 게시글이름, 작성자, 위치, 내용을 board 테이블에서 가져오고 있음.
조건절을 보면 num 이 보이나? 그렇다. num으로 가져올 게시글을 구분하고 있는것이다. 그 외엔 별거 없다. 그 밑으로는 그냥 해당 num에 해당하는 정보들을 가져와서 그것들을 토대로 boardVO 객체를 만들고, 리턴해주는것 뿐.

getReply(int num)

	public ArrayList<ReplyVO> getReply(int num) {
		ArrayList<ReplyVO> replyList = new ArrayList<ReplyVO>();
		Connection conn = null;
		PreparedStatement psmt = null;
		ResultSet rs = null;

		try {
			Class.forName("oracle.jdbc.driver.OracleDriver");

			conn = DriverManager.getConnection(DB_URL, DB_ID, DB_PW);

			String query = "select content, writer from reply where boardnum = ?";
			psmt = conn.prepareStatement(query);
			psmt.setInt(1, num);

			rs = psmt.executeQuery();

			while (rs.next()) {
				String getContent = rs.getString(1);
				String getWriter = rs.getString(2);


				ReplyVO replyVO = new ReplyVO(getContent, getWriter);
				replyList.add(replyVO);
			}

		} catch (Exception e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		} finally {
			try {
				// 런타임 오류 예외처리 (실행을 해야만 오류가 발생 했는지 알 수 있기 때문에..)
				rs.close();
				psmt.close();
				conn.close();
			} catch (Exception e) {
				e.printStackTrace();
			}
		}
		return replyList;
	}

이건 댓글을 가져오는 함수인데 getList랑 로직이 매우매우매우 비슷하다.
역시나 그렇듯 SQL query문을 보자.
그러면 내용, 작성자를 reply 테이블에서 가져오고있다.
boardnum 참고해서 말이다.

이떄 boardnum은 뭐겠는가? 게시글 번호이다. 해당 게시글 번호를 참고하여 그 게시글에 등록된 댓글들을 몽 땅 가져 오는 것이다. 깜
빡하고 말을 안했는데 그러면 당연히 댓글들의 ArrayList를 리턴해줘야겠쥬?

이제 이 메소드들을 알아봤으니까 대망의 board_main.jsp 를 살펴보도록 하자.

<%@ page language="java" contentType="text/html; charset=EUC-KR"
	pageEncoding="EUC-KR"%>
<%@ page import="java.util.*"%>
<%@ page import="java.lang.*"%>
<%@ page import="com.model.BoardDAO"%>
<%@ page import="com.model.BoardVO"%>
<%@ page import="com.model.ReplyVO"%>
<!DOCTYPE html>
<html>
<head>
<meta charset="EUC-KR">
<title>Insert title here</title>
<link rel="stylesheet"
	href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.5/css/bootstrap.min.css"
	integrity="sha512-dTfge/zgoMYpP7QbHy4gWMEGsbsdZeCXz7irItjcC3sPUFtf0kuFbDz/ixG7ArTxmDjLXDmezHubeNikyKGVyQ=="
	crossorigin="anonymous">
<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link
	href="https://fonts.googleapis.com/css2?family=Do+Hyeon&display=swap"
	rel="stylesheet">
<style>
body {
	background-image: url('asset/img/bg.jpg');
	padding-left: 200px;
	padding-right: 200px;
	font-family: 'Do Hyeon', sans-serif;
}

.card-body {
	background-color: whitesmoke;
}

p {
	padding-top: 5px;
	padding-left: 20px;
	font-size: 30px;
}
</style>
</head>
<body>
	<%
	int num = Integer.parseInt(request.getParameter("num"));

	BoardDAO boardDAO = new BoardDAO();
	BoardVO boardVO = boardDAO.getOneList(num);
	ArrayList<ReplyVO> replyList = boardDAO.getReply(num);
	%>

	<div class="card-body"
		style="margin-top: 100px; margin-bottom: 10px; height: 50px">
		<p><%=boardVO.getName()%>
			<%=boardVO.getWriter()%></p>
	</div>

	<!-- 장소, 및 지도 -->
	<div id="map" style="width: 100%; height: 350px;"></div>

	<!-- 댓글작성 -->
	<div class="card mb-2">
		<div class="card-body">
			<p>
				한줄 평 :
				<%=boardVO.getContent()%></p>
			<ul class="list-group list-group-flush">
				<li class="list-group-item"><textarea class="form-control"
						id="exampleFormControlTextarea1" rows="3"></textarea>
					<button type="button" class="btn btn-dark mt-3"
						onclick="addReply()">post reply</button></li>
			</ul>
			<ul class="list-group list-group-flush" id="reply">
				<%for(int i = 0; i < replyList.size(); i++) { %>
					<li class="list-group-item"><span><%=replyList.get(i).getContent() %><br>작성자 : <%=replyList.get(i).getWriter() %></span></li>
				<%} %>
			</ul>
		</div>
	</div>
	<script type="text/javascript"
		src="//dapi.kakao.com/v2/maps/sdk.js?appkey=2de9aa5294c4c06481a48da6aeec3103&libraries=services"></script>
	<script>
		let ta = document.querySelector('textarea');
		let replyDiv = document.querySelector("#reply");
		function addReply() {
			// 바닐라 JS 통신
			// 1. JSON 형식 데이터 만들기
			const data = {"boardnum" : <%=num %>, "reply" : ta.value};
			console.log(data);
			
			// 2. 데이터를 보내줄 서버 페이지랑 통신
			let xhr = new XMLHttpRequest();
			
			// 요청방식, 요청경로 지정
			xhr.open("post", "replyService");
			// 전송 데이터의 형식 지정
			xhr.setRequestHeader('Content-Type', 'application/json;charset=UTF-8');
			// 요청 & 전송 데이터 보내기
			xhr.send(JSON.stringify(data));
			
			xhr.onreadystatechange = function() {
				if(xhr.readyState === XMLHttpRequest.DONE){ // 요청 성공
					if(xhr.status === 200) {
						console.log("응답 성공"); 
						console.log(xhr.responseText) // 응답 데이터 확인 (responseXML)
						if(xhr.responseText === "success") {
							
						} else {
							
						}
					} else {
						console.log("응답 실패");
						// fail 응답
					}
				} else {
					console.log("요청 실패");
				}
			}
			
		}
	
	
	
	
		// KAKAO MAP
		// 마커를 클릭하면 장소명을 표출할 인포윈도우 입니다
		var infowindow = new kakao.maps.InfoWindow({
			zIndex : 1
		});

		var mapContainer = document.getElementById('map'), // 지도를 표시할 div 
		mapOption = {
			center : new kakao.maps.LatLng(37.566826, 126.9786567), // 지도의 중심좌표
			level : 3
		// 지도의 확대 레벨
		};

		// 지도를 생성합니다    
		var map = new kakao.maps.Map(mapContainer, mapOption);

		// 장소 검색 객체를 생성합니다
		var ps = new kakao.maps.services.Places();

		// 키워드로 장소를 검색합니다
		ps.keywordSearch("<%=boardVO.getLocation() %>" , placesSearchCB);

		// 키워드 검색 완료 시 호출되는 콜백함수 입니다
		function placesSearchCB(data, status, pagination) {
			if (status === kakao.maps.services.Status.OK) {

				// 검색된 장소 위치를 기준으로 지도 범위를 재설정하기위해
				// LatLngBounds 객체에 좌표를 추가합니다
				var bounds = new kakao.maps.LatLngBounds();

				for (var i = 0; i < data.length; i++) {
					displayMarker(data[i]); // 마커 표시
					bounds.extend(new kakao.maps.LatLng(data[i].y, data[i].x));
				}

				// 검색된 장소 위치를 기준으로 지도 범위를 재설정합니다
				map.setBounds(bounds);
			}
		}

		// 지도에 마커를 표시하는 함수입니다
		function displayMarker(place) {

			// 마커를 생성하고 지도에 표시합니다
			var marker = new kakao.maps.Marker({
				map : map,
				position : new kakao.maps.LatLng(place.y, place.x)
			});

			// 마커에 클릭이벤트를 등록합니다
			kakao.maps.event.addListener(marker, 'mouseover', function() {
				// 마커를 클릭하면 장소명이 인포윈도우에 표출됩니다
				infowindow
						.setContent('<div style="padding:5px;font-size:12px;"><img src="asset/img/food1.jpg" width="100px" height="100px" /></div>');
				infowindow.open(map, marker);
			});
			
			kakao.maps.event.addListener(marker, 'mouseout', function() {
				infowindow.close();
			});
		}
	</script>
</body>
</html>

이 페이지는 코드가 좀 많이 길다.

카카오 지도 API 코드도 섞여있고, AJAX 통신 코드도 섞여있고 아무튼 뭐 다 있다.
그래서 맨 위에 있는 스클립틀릿부터 천천히 뜯어서 설명할거임.

	<%
	int num = Integer.parseInt(request.getParameter("num"));

	BoardDAO boardDAO = new BoardDAO();
	BoardVO boardVO = boardDAO.getOneList(num);
	ArrayList<ReplyVO> replyList = boardDAO.getReply(num);
	%>

아까 a 링크로 쿼리스트링 넣어준 거 혹시 기억이 날까?
그 때 쿼리스트링으로 보내준 num 값을 여기서 받아주는거임!!
참고로 이 페이지에 대해 다시 언급하면 게시글의 상세 정보 페이지임!!
그 아래 세줄의 코드는 그냥 뭐.. dao 코드를 쓰기위해 객체를 만들고, 게시글 상세 정보를 확인하기위해 boardVO 객체를 만들고 num을 이용하여 getOneList 메소드를 호출시켜 VO 객체 안에 우리가 지금 클릭한 게시글 정보를 갱신함.
그리고 또 num 을 이용해서 이 게시글에 등록된 댓글의 정보들의 list 도 만들어 줌.

	<div class="card-body"
		style="margin-top: 100px; margin-bottom: 10px; height: 50px">
		<p><%=boardVO.getName()%>
			<%=boardVO.getWriter()%></p>
	</div>

	<!-- 장소, 및 지도 -->
	<div id="map" style="width: 100%; height: 350px;"></div>

	<!-- 댓글작성 -->
	<div class="card mb-2">
		<div class="card-body">
			<p>
				한줄 평 :
				<%=boardVO.getContent()%></p>
			<ul class="list-group list-group-flush">
				<li class="list-group-item"><textarea class="form-control"
						id="exampleFormControlTextarea1" rows="3"></textarea>
					<button type="button" class="btn btn-dark mt-3"
						onclick="addReply()">post reply</button></li>
			</ul>
			<ul class="list-group list-group-flush" id="reply">
				<%for(int i = 0; i < replyList.size(); i++) { %>
					<li class="list-group-item"><span><%=replyList.get(i).getContent() %><br>작성자 : <%=replyList.get(i).getWriter() %></span></li>
				<%} %>
			</ul>
		</div>
	</div>

그래서 그냥 뭐 여기서는 그냥 VO 객체의 gette 메소드를 써서 페이지에 정보들을 출력해주는거임... 흠... 끝.

그리고 그 밑에는 댓글을 등록하는 자바스크립트 코드가있는데 한번 살펴보장.

		let ta = document.querySelector('textarea');
		let replyDiv = document.querySelector("#reply");
		function addReply() {
			// 바닐라 JS 통신
			// 1. JSON 형식 데이터 만들기
			const data = {"boardnum" : <%=num %>, "reply" : ta.value};
			console.log(data);
			
			// 2. 데이터를 보내줄 서버 페이지랑 통신
			let xhr = new XMLHttpRequest();
			
			// 요청방식, 요청경로 지정
			xhr.open("post", "replyService");
			// 전송 데이터의 형식 지정
			xhr.setRequestHeader('Content-Type', 'application/json;charset=UTF-8');
			// 요청 & 전송 데이터 보내기
			xhr.send(JSON.stringify(data));
			
			xhr.onreadystatechange = function() {
				if(xhr.readyState === XMLHttpRequest.DONE){ // 요청 성공
					if(xhr.status === 200) {
						console.log("응답 성공"); 
						console.log(xhr.responseText) // 응답 데이터 확인 (responseXML)
						if(xhr.responseText === "success") {
							
						} else {
							
						}
					} else {
						console.log("응답 실패");
						// fail 응답
					}
				} else {
					console.log("요청 실패");
				}
			}
		}

이거 아까 본 형태이다. 아까 로그인할 때 봤었던거임.
프론트엔드의 데이터를 백엔드쪽으로 가져갈 떄 쓰는 코드임.
근데 이번엔 그냥 보내줄 data 객체의 모양이 살짝 달라졌을뿐.. 아까와 구조는 똑같다.
게시글 번호와 작성한 댓글을 data로 보내주는 코드임. 어디로? replyService 파일로~

replyService

package com.controller;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.PrintWriter;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.PreparedStatement;
import java.sql.ResultSet;

import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;

import com.google.gson.JsonElement;
import com.google.gson.JsonParser;
import com.model.MemberVO;

/**
 * Servlet implementation class replyService
 */
@WebServlet("/replyService")
public class replyService extends HttpServlet {

	final String DB_URL = "jdbc:oracle:thin:@127.0.0.1:1521:xe";
	final String DB_ID = "hr";
	final String DB_PW = "hr";
	
	protected void service(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
		
		// GSON 라이브러리 사용
		
		StringBuffer sb = new StringBuffer(); // 읽어온 데이터 저장
		String line = null; // 버퍼안의 데이터를 읽을 때 (임시 저장)
		
		BufferedReader reader = request.getReader(); // 요청 데이터를 읽어 올 때 사용
		
		while((line = reader.readLine()) != null) { // 읽을 데이터가 있을 때 반복 수행
			sb.append(line); // 읽어온 데이터를 stringbuffer에 추가
		}
		
		JsonParser parser = new JsonParser(); // 파싱 (문자열 -> JSON)
		JsonElement element = parser.parse(sb.toString()); // 버퍼 데이터 문자열로 변경 후  JSON으로 변환
		
		int boardnum = element.getAsJsonObject().get("boardnum").getAsInt(); 
		String reply = element.getAsJsonObject().get("reply").getAsString();
		
		HttpSession session = request.getSession();
		MemberVO memberVO = (MemberVO)session.getAttribute("member");
		
		Connection conn = null;
		PreparedStatement psmt = null;
		
		try {
			Class.forName("oracle.jdbc.driver.OracleDriver");

			conn = DriverManager.getConnection(DB_URL, DB_ID, DB_PW);

			request.setCharacterEncoding("euc-kr");


			String query = "insert into reply values(reply_seq.nextval, ?, ?, ?)";
			psmt = conn.prepareStatement(query);
			psmt.setInt(1, boardnum);
			psmt.setString(2, reply);
			psmt.setString(3, memberVO.getId());
			
			int cnt = psmt.executeUpdate();

			PrintWriter out = response.getWriter(); // 응답 스트림 만드는거임
			
			if(cnt > 0) {
				out.print("success");
			} else {
				out.print("fail");
			}

		} catch (Exception e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		} finally {
			try {
				psmt.close();
				conn.close();
			} catch (Exception e) {
				e.printStackTrace();
			}
		}
	}
}

replyService도 loginService랑 진짜 아예 똑같다.
Gson 라이브러리로 Json 형식인 data를 자바에서 쓸 수 있게 파싱 해주고, 그 파싱된 데이터를 이용하여 reply 테이블에 insert 해줌.

그리고 중간에 세션코드가있는데, 지금 로그인 중인 아이디 값을 가져오려고 써준거임
member 라는 이름의 세션에 현재 로그인 된 아이디 정보가 들어있음.

그리고 insert 할 때 넘겨주는 값은 게시글번호, 댓글내용, 작성자(지금로그인 한 아이디) 다.

매우 간단한 구조이다.


이렇게 기본적인 게시판 로직에 대한 설명은 끝이 난것같다. 물론 게시글 작성은 우리가 하지 않았지만 그냥 비슷한 맥락으로 이해하면 된다.

카카오 맵 API 는 다음 사이트에 들어가서 읽어보면 Real 쉽게 사용 할 수 있음.

아 그 전에 아래 싸이트 들어가서 카카오로그인하고, 앱 등록하고 api 키 발급받아야함.

카카오 개발자 사이트

API키를 발급 받았으면, 아래 링크 들어가서 가이드 보면서 천천히 익혀보길 바람.

카카오 맵 API

그러면 다음과 같은 예쁜 지도를 얻을 수 있음..

이거 근데 맵 API 가지고 노는것도 지루하지않으니까 해볼만 할 거임 ㅎㅎㅎ

오늘은 너무 개념설명의 느낌보다는 하나의 프로젝트를 해부하는 느낌으로 글을 작성했다.

좋은 복습이 된것같다. 나 또한.

그럼 20000

#내일배움카드, #국비지원, #취업연계, #취업성공패키지

스마트 인재 개발원

profile
🧑‍💻

0개의 댓글