💫 페이징이 된 모습을 보기 위해 Borad를 복사해 PagingBorad 폴더를 만들었다.

 

🌀 페이징

[첫번째 페이지] [이전 블럭] [1] [2] [3] [4] [5] [다음 블럭] [마지막 페이지]

최종적으로 구현하고 싶은 모양이다.

각 페이지 번호를 누르면 페이지 넘버를 파라매터로 받아야한다.

그리고 파라매터로 받은 페이지 넘버가 주소에 표시되어야 한다. → url 정보를 넘겨줘야 함.

 

List.jsp

<%@page import="utils.BoardPage"%>
<%@page import="model1.board.BoardDAO"%>
<%@page import="model1.board.BoardDTO"%>
<%@page import="java.util.HashMap"%>
<%@page import="java.util.Map"%>
<%@page import="java.util.List"%>
<%@ page language="java" contentType="text/html; charset=UTF-8"
	pageEncoding="UTF-8"%>
<%
// DB에 연결
BoardDAO dao = new BoardDAO(application);

// form에서 액션 속성 생략 -> 자기 자신 페이지를 파라매터로 받는다. 
String searchField = request.getParameter("searchField");
String searchWord = request.getParameter("searchWord");

Map<String, Object> param = new HashMap<>();

// 검색조건으로 검색하려 할때
if (searchWord != null) {
	param.put("searchField", searchField);
	param.put("searchWord", searchWord);
}

// 전체 개수 구하기 selectCount()
// 검색 했을 때 개수 dao.selectCount(param)
// boardLists 에 DTO의 정보가 들어있다.
int totalCount = dao.selectCount(param);

int pageSize = Integer.parseInt(application.getInitParameter("POSTS_PER_PAGE"));
int blockPage = Integer.parseInt(application.getInitParameter("PAGES_PER_BLOCK"));
int totalPage = (int) Math.ceil( (double)totalCount / pageSize );

// 페이지 번호를 관리하는 변수
int pageNum = 1;

// 페이지 번호를 다른 걸로 선택하면 그 다른 페이지 번호를 받아온다
String pageTemp = request.getParameter("pageNum");

if (pageTemp != null && !pageTemp.equals("")){
	pageNum = Integer.parseInt(pageTemp);
}

// 게시물의 시작번호와 끝 번호 구하기
int start = (pageNum - 1) * pageSize + 1;
int end = pageNum * pageSize;

param.put("start", start);
param.put("end", end);

/* List<BoardDTO> boardLists = dao.selectList(param); */
List<BoardDTO> boardLists = dao.selectListPage(param);
dao.close();

%>

<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Insert title here</title>
</head>
<body>
	<jsp:include page="../Common/Link.jsp" />
	<h2>목록 보기(List)</h2>

	<!-- 폼태그에 action 속성이 생략되면 자신의 현재 페이지를 요청한다. (List.jsp)-->
	<form method="get">
		<table border="1" width="90%">
			<tr>
				<td align="center">
				<select name="searchField">
						<option value="title">제목</option>
						<option value="content">내용</option>
				</select> 
				<input type="text" name="searchWord" /> 
				<input type="submit" value="검색하기" />
				</td>
			</tr>
		</table>
	</form>

	<table border="1" width="90%">
		<tr>
			<th width="10%">번호</th>
			<th width="50%">제목</th>
			<th width="15%">작성자</th>
			<th width="10%">조회수</th>
			<th width="15%">작성일</th>
		</tr>


<!-- 게시물이 없을 떄 -->
		<% if (boardLists.isEmpty()) {%>

		<tr>
			<td colspan="5" align="center">등록된 게시물이 없습니다^^*</td>
		</tr>

		<% }else{

		int virtualNum = 0;
		for(BoardDTO dto : boardLists){ 
		virtualNum = totalCount--;
		%>

		<!-- 게시물이 있을 때 -->
		<tr align="center">
		<!-- DTO에서 쓴 이름과 같게 -->
			<%-- <td><%= dto.getNum() %></td> --%>
			<td><%= virtualNum %></td>
			<td>
			<a href="View.jsp?num=<%= dto.getNum() %>"> <%= dto.getTitle() %> </a>

			</td>
			
			
			<td><%= dto.getId() %></td>
			<td><%= dto.getVisitcount() %></td>
			<td><%= dto.getPostDate() %></td>
		</tr>

		<%
		 } // for문 
		} // if else문
		%>

	</table>

	<table border="1" width="90%">
		<tr align="center">
		<td>
		<!-- [첫번째 페이지] [이전 블럭]  [1] [2] [3] [4] [5] [다음 블럭] [마지막 페이지] -->
		
		<!-- request.getRequestURI()로 주소값을 가지고 올 수 있다 -->
		<%= BoardPage.pagingStr(totalCount, pageSize, blockPage, pageNum, request.getRequestURI()) %>
		</td>
			<td align="right">
				<button type="button" onclick="location.href='write.jsp'">글쓰기</button>
			</td>
		</tr>
	</table>
</body>
</html>

 

BoardPage.jsp

package utils;

public class BoardPage {
	public static String pagingStr(int totalCount, int pageSize, int blockPage, int pageNum, String reqUrl) {
		
		String pagingStr = "";
		
		// 전체 페이지수 계산 , totalPages = 마지막 페이지 수
		int totalPages = (int) Math.ceil( (double)totalCount / pageSize );
		
		// 이전 페이지 블록 바로가기 출력
		// ( (현재 페이지 - 1 ) /  PAGES_PER_BLOCK) ) *  PAGES_PER_BLOCK + 1
		int pageTemp = ((pageNum - 1 ) / blockPage) *  blockPage + 1;
		
		// 현재페이지의 시작 값이 1이면 [첫 페이지]와 [이전 블록]은 안보인다. 
		if(pageTemp != 1) {
			pagingStr += "<a href='" + reqUrl + "?pageNum=1'>[첫 페이지]</a>";
			pagingStr += "&nbsp;";
			pagingStr += "<a href='" + reqUrl + "?pageNum=" + (pageTemp - 1) + "'>[이전 블록]</a>";
		}
		
		// 각 블록에 페이지 번호 출력
		int blockCount = 1;
		while ( blockPage >= blockCount && pageTemp <= totalPages ) {
										   // 마지막 블록에도 번호 비활성화
			// 현재 선택한 번호 비활성화
			if (pageTemp == pageNum) {
				pagingStr += "&nbsp;[" + pageTemp + "]&nbsp;";
			} else {
				pagingStr += "&nbsp;<a href='" + reqUrl + "?pageNum=" + pageTemp + "'>[" + pageTemp + "]</a>&nbsp;";
			}
			
			pageTemp++; // 6 7 8 9 10 다음 블록으로 넘아가 11 
			blockCount++;  // 1 2 3 4 5 다음 블록 6
		}
		
//		System.out.println(pageTemp + " : " + totalCount);
		
		// 다음 페이지 블록 바로가기 출력
		if(pageTemp <= totalPages) {
			pagingStr += "<a href='" + reqUrl + "?pageNum=" + (pageTemp) + "'>[다음 블록]</a>";
			pagingStr += "&nbsp;";
			pagingStr += "<a href='" + reqUrl +  "?pageNum=" + totalPages + "'>[마지막 페이지]</a>";
		}
		
		return pagingStr;
	}
}

 

BoardDAO.java

package model1.board;

import java.sql.ResultSet;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;

import common.JDBConnect;
import jakarta.servlet.ServletContext;

public class BoardDAO extends JDBConnect {

	public BoardDAO(ServletContext application) {
		super(application);
	}

	// 1. 검색 조건에 맞는 게시물의 개수를 반환합니다.
	public int selectCount(Map<String, Object> map) {

		int totalcount = 0;

		// 복잡한 쿼리문은 오류날 확률이 높으므로 디벨로퍼에서 미리 실행해보고 옮기는 것이 좋다.
		String query = " select count(*) from board ";

		// searchWord를 선택하면 이름, 내용으로 분류해서 count 해줘야 한다.
		if (map.get("searchWord") != null) {
			query += " where " + map.get("searchField") + "" + " like '%" + map.get("searchWord") + "%'";

			// where title like '%제목%';
		}

		try {

			psmt = con.prepareStatement(query);
			rs = psmt.executeQuery();

			if (rs.next()) {
				totalcount = rs.getInt(1);
			}

		} catch (Exception e) {
			e.printStackTrace();
		}

		return totalcount;
	}

	// 2. 검색 조건에 맞는 게시물 목록을 반환합니다.
	public List<BoardDTO> selectList(Map<String, Object> map) {

		List<BoardDTO> bbs = new ArrayList<>();

		String query = " select * from board ";

		if (map.get("searchWord") != null) {
			query += " where " + map.get("searchField") + " like '%" + map.get("searchWord") + "%'";
		}

		query += " order by num desc ";

		try {

			psmt = con.prepareStatement(query);
			rs = psmt.executeQuery();

			while (rs.next()) {
				// 이 정보들을 DTO에 넣어준다
				BoardDTO dto = new BoardDTO();
				// DB 컬럼명
				dto.setNum(rs.getString("num"));
				dto.setTitle(rs.getString("title"));
				dto.setContent(rs.getString("content"));
				dto.setPostDate(rs.getDate("postdate"));
				dto.setId(rs.getString("id"));
				dto.setVisitcount(rs.getString("visitcount"));

				// DTO는 가장 최근의 정보만 읽어온다.
				// 읽어와서 DTO에 담긴 정보를 다른 곳에 저장해야 목록을 볼 수 있다.
				// collection ArayList()에 저장
				bbs.add(dto);

			}

		} catch (Exception e) {
			e.printStackTrace();
		}

		// 목록 데이터가 담긴 bbs를 리턴한다.
		return bbs;
	}

	// 3. 검색 조건에 맞는 게시물 목록을 반환합니다(페이징 기능 지원).
	public List<BoardDTO> selectListPage(Map<String, Object> map) {
		
		List<BoardDTO> bbs = new ArrayList<>();
		
		String query = " select * "
				+ " from ( "
				+ " select rownum rm, p.* "
				+ " from ( "
				+ " select * "
				+ " from board ";
				
		if (map.get("searchWord") != null) {
			query += " where " + map.get("searchField") + " like '%" + map.get("searchWord") + "%'";
		}
				
				query += " order by num desc "
						+ " ) p "
						+ " ) "
						+ " where rm between ? and ? ";

		try {

			psmt = con.prepareStatement(query);
			psmt.setString(1, map.get("start").toString());
			psmt.setString(2, map.get("end").toString());
			
			rs = psmt.executeQuery();
			
			while (rs.next()) {
				
				BoardDTO dto = new BoardDTO();
				// DB 컬럼명
				dto.setNum(rs.getString("num"));
				dto.setTitle(rs.getString("title"));
				dto.setContent(rs.getString("content"));
				dto.setPostDate(rs.getDate("postdate"));
				dto.setId(rs.getString("id"));
				dto.setVisitcount(rs.getString("visitcount"));

				bbs.add(dto);

			}

		} catch (Exception e) {
			e.printStackTrace();
		}
		
		return bbs;
	}

	// 4. 게시글 데이터를 받아 DB에 추가합니다.
	// 실제 데이터를 insert할 수 있는 기능
	public int insertWrite(BoardDTO dto) {

		// insert 구문이 들어갔는지 확인
		int result = 0;

		String query = " insert into board " + " (num, title, content, id, visitcount) "
				+ " values (seq_board_num.nextval,?,?,?,0) ";

		try {

			psmt = con.prepareStatement(query);
			psmt.setString(1, dto.getTitle());
			psmt.setString(2, dto.getContent());
			// 로그인한 ID 받아오기 -> session 영역의 정보를 받아온다
			psmt.setString(3, dto.getId());

			result = psmt.executeUpdate();

		} catch (Exception e) {
			e.printStackTrace();
		}

		return result;
	}

	// 5. 지정한 게시물을 찾아 내용을 반환합니다.
	public BoardDTO selectView(String num) {

		BoardDTO dto = new BoardDTO();

		String query = " select B.*, M.name " + " from member M inner join board B " + " on M.id = B.id"
				+ " where num = ? ";

		try {

			psmt = con.prepareStatement(query);
			psmt.setString(1, num);
			rs = psmt.executeQuery();

			if (rs.next()) {

				dto.setNum(rs.getString(1));
				dto.setTitle(rs.getString(2));
				dto.setContent(rs.getString("content"));
				dto.setPostDate(rs.getDate("postdate"));
				dto.setId(rs.getString("id"));
				dto.setVisitcount(rs.getString(6));
				dto.setName(rs.getString("name"));

				// System.out.println("kkkkkk : " + dto.getId());

			}

		} catch (Exception e) {
			e.printStackTrace();
		}

		return dto;

	}

	// 6. 지정한 게시물의 조회수를 1 증가시킵니다.
	public void updateVisitCount(String num) {

		String query = " update board " + " set visitcount = visitcount + 1 " + " where num = ? ";

		try {

			psmt = con.prepareStatement(query);
			psmt.setString(1, num);
			psmt.executeUpdate();

		} catch (Exception e) {
			e.printStackTrace();
		}
	}

	// 7. 지정한 게시물을 수정합니다.
	public int updateEdit(BoardDTO dto) {

		int result = 0;

		String query = " update board " + " set title = ? , content = ? " + " where num = ? ";

		try {

			psmt = con.prepareStatement(query);
			psmt.setString(1, dto.getTitle());
			psmt.setString(2, dto.getContent());
			psmt.setString(3, dto.getNum());

			result = psmt.executeUpdate();

		} catch (Exception e) {
			e.printStackTrace();
		}

		return result;
	}

	// 8. 지정한 게시물을 삭제합니다.
	public int deletePost(BoardDTO dto) {

		int result = 0;

		String query = " delete from board where num = ? ";

		try {

			psmt = con.prepareStatement(query);
			psmt.setString(1, dto.getNum());
			
			result = psmt.executeUpdate();

		} catch (Exception e) {
			e.printStackTrace();
		}

		return result;
	}

}

 

주소 : http://localhost:9999/firstjsp/PagingBoard/List.jsp?**pageNum=3**

이동하는 페이지에 따라 주소값이 달라진다!

[ 각 페이지 번호를 누르면 페이지 넘버를 파라매터로 받아야한다.

그리고 파라매터로 받은 페이지 넘버가 주소에 표시되어야 한다. → url 정보를 넘겨줘야 함. ]

이 부분에 대한 처리가 됐다.

 

 

😞 그런데 여기서 문제.. 발생

그런데 마지막 10 페이지 이후로 글이 안나온다..

그리고 마지막 페이지로 이동하는 태그도 보이지 않는다!!!!!

 

원인)

		<% }else{

		int virtualNum = 0;
		for(BoardDTO dto : boardLists){ 
		virtualNum = totalCount--;
		%>

List.jsp 의 이 부분에서 계속 totalCount을 감소시키고 있었다.

 

해결) 스스로 해보기

		<% }else{

		int virtualNum = totalCount - (pageNum - 1) * pageSize;
		for(BoardDTO dto : boardLists){ 
			// virtualNum = totalCount--;
		%>

		<!-- 게시물이 있을 때 -->
		<tr align="center">
		<!-- DTO에서 쓴 이름과 같게 -->
			<%-- <td><%= dto.getNum() %></td> --%>
			<td><%= virtualNum-- %></td>

totalCount를 사용하지 않고 virtualNum 만 따로 계산을 해줬다.

 

강사님의 식)

		<% }else{

		int virtualNum = 0;
		for(BoardDTO dto : boardLists){ 
			virtualNum =  totalCount - ((pageNum - 1) * pageSize + CountNum++);
		%>

		<!-- 게시물이 있을 때 -->
		<tr align="center">
		<!-- DTO에서 쓴 이름과 같게 -->
			<%-- <td><%= dto.getNum() %></td> --%>
			<td><%= virtualNum %></td>

 

 

 

이렇게 하면 마지막페이지까지 잘 나오고, virtualNum도 잘 나온다.

 

 

처음 페이징 할 때 중요 )

총 게시판에 몇 개의 글이 들어있는지 알아야 함.

그리고 개발자는 한 페이지에 몇 개의 글을 보여줄지 정하기.

한 페이지에 보여줄 글의 개수와 한 페이지에서 보여줄 페이지의 개수를 정하는 것이 가장 먼저. (중요)

한 페이지에 보여줄 글의 개수 , 한 페이지에서 보여줄 페이지의 개수 각 변수 만들기

 


이 코드를 안보고 짤 수 있을 정도로 흐름을 이해해야 프로젝트든 취업이든 할 수 있음.


 

 

 🎉 댓글을 달 수 있는 게시판 만들기

 

PagingBoard 폴더 복사해서 ReplyBoard 폴더 만들기

reply라고 하는 댓글을 저장하는 테이블을 만들어야 한다.

board 테이블과 reply 테이블은 식별관계.

글이 있어야 댓글을 쓸 수 있기 때문이다.

테이블 설계와 임의의 데이터(댓글) 하나 넣어보기

 

 

오라클 DB

create sequence seq_reply_no
start with 1;

drop table reply;

create table reply(
replyno number primary key,
replycomment varchar2(1000),
id varchar2(20),
regidate date default sysdate,
num number REFERENCES board(num)
);

-- 댓글 데이터 넣기
insert into reply
values (seq_reply_no.nextval, '페이징 처리-100 댓글1', 'musthave', sysdate, 109);

commit;

select * from reply;

-- 댓글 보기 
select replyno, replycomment, r.id, r.regidate, r.num
from reply r inner join board b
on r.num = b.num
where r.num = 109;

 

ReplyDAO.java

package model1.replay;

import java.util.ArrayList;
import java.util.List;

import common.JDBConnect;
import jakarta.servlet.ServletContext;

public class ReplyDAO extends JDBConnect {
	
	public ReplyDAO (ServletContext application) {
		super(application);
	}
	
	public List<ReplyDTO> selectReply(String num){
		List<ReplyDTO> replybbs = new ArrayList();
		
		String query  = " select replyno, replycomment, r.id, r.regidate, r.num "
				+ " from reply r inner join board b "
				+ " on r.num = b.num "
				+ " where r.num = ? "
				+ " order by replyno asc ";
		
		try {
			psmt = con.prepareStatement(query);
			psmt.setString(1, num);
			
			rs = psmt.executeQuery();
			
			while(rs.next()) {
	            
	            ReplyDTO dto = new ReplyDTO();
	            
	            dto.setReplyno(rs.getString(1));
	            dto.setReplycomment(rs.getString(2));
	            dto.setId(rs.getString(3));
	            dto.setRegidate(rs.getString(4));
	            dto.setNum(rs.getString(5));
	            
	            replybbs.add(dto);
	         }
	         
	         
	      }catch (Exception e) {
	         e.printStackTrace();
	      }
	      
	      return replybbs;
	      
	   }
	
	public int insertReply(ReplyDTO dto) {
		
		int result = 0;
		
		String query  = " insert into reply "
				+ " (replyno, replycomment, id, num) "
				+ " values (seq_board_num.nextval,?, ?, ?)";
		
		try {
			psmt = con.prepareStatement(query);
			psmt.setString(1, dto.getReplycomment());
			psmt.setString(2, dto.getId());
			psmt.setString(3, dto.getNum());

			result = psmt.executeUpdate();
			
		} catch (Exception e) {
			e.printStackTrace();
		}
		
		return result;
	}
}

 

ReplyDTO.java

package model1.replay;

import lombok.Getter;
import lombok.Setter;

@Getter
@Setter
public class ReplyDTO {
	// 컬럼명
private String replyno;
private String replycomment;
private String id;
private String regidate;
private String num;

}

 

View.jsp

<%@ page language="java" contentType="text/html; charset=UTF-8"
	pageEncoding="UTF-8"%>
<%@ include file="./IsLoggedIn.jsp"%>
<%@page import="model1.board.BoardDTO"%>
<%@page import="model1.board.BoardDAO"%>
<%@page import="model1.replay.ReplyDAO"%>
<%@page import="model1.replay.ReplyDTO"%>
<%@page import="java.util.List"%>

<%
String num = request.getParameter("num");
BoardDAO dao = new BoardDAO(application);
ReplyDAO replyDAO = new ReplyDAO(application);

// 글에 대한 상세보기 -> 조회수 증가 처리
dao.updateVisitCount(num);

// 상세정보 보기 (원글 정보)
BoardDTO dto = dao.selectView(num);

List<ReplyDTO> replyLists = replyDAO.selectReply(num);
%>


<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Insert title here</title>

<script>
	function deletePost() {
		let confirmed = confirm("정말로 삭제하시겠습니까?");

		if (confirmed) {
			let form = document.writeFrm; // 이름이 writeFrm인 폼 선택
			// 입력 폼에 값이 없지만 post 방식으로 넘긴다
			form.method = "post"; // 전송 방식
			form.action = "DeleteProcess.jsp"; // 전송 경로
			// 객체 = 기능 + 속성
			form.submit(); // 폼값전송

		}
	}
	
	function vaildateForm(form){
		if (form.replycontent.value == ""){
			alert("댓글 내용을 입력하세요.");
			form.replycontent.focus();
			return false;
		}
	}
	
</script>
</head>

<body>
	<%@ include file="./IsLoggedIn.jsp"%>
	<h2>회원제 게시판 - 상세보기(View)</h2>
	<form name="writeFrm">
		<input type="hidden" name="num" value="<%=num%>">

		<table border="1" width="90%">

			<tr>
				<td>번호</td>
				<td><%=dto.getNum()%></td>
				<td>작성자</td>
				<td><%=dto.getName()%></td>
			</tr>

			<tr>
				<td>작성일</td>
				<td><%=dto.getPostDate()%></td>
				<td>조회수</td>
				<td><%=dto.getVisitcount()%></td>
			</tr>

			<tr>
				<td>제목</td>
				<td colspan="3"><%=dto.getTitle()%></td>
			</tr>

			<tr>
				<td>내용</td>
				<!-- 줄바꿈이 안되어 저장되기 때문에 강제 줄바꿈을 해줘야 한다 -->
				<td colspan="3" height="100px"><%=dto.getContent().replace("\r\n", "<br/>")%></td>
			</tr>

			<tr>
				<td colspan="4" align="center">
					<!-- 글을 작성한 사람이 아니라면 수정하기, 삭제하기 버튼 안보이게 --> <%
					if (session.getAttribute("UserId") != null 
					&& session.getAttribute("UserId").toString().equals(dto.getId())) {
 %>
					<button type="button"
						onclick="location.href = 'Edit.jsp?num=<%=dto.getNum()%>'">수정하기</button>
					<button type="button" onclick="deletePost()">삭제하기</button> <%
 }
 %>
					<button type="button" onclick="location.href = 'List.jsp'">목록보기</button>
				</td>
			</tr>

		</table>


	</form>

	<p>댓글</p>

	<table border="1" width="90%">
		<%if (replyLists.isEmpty()) {%>
		<tr>
			<td colspan="5" align="center">등록된 댓글이 없습니다^^*</td>
		</tr>
		<%}else{ %>
		<tr>
			<th width="10%">번호</th>
			<th width="50%">댓글내용</th>
			<th width="15%">작성자</th>
			<th width="20%">날짜</th>
			<th width="5%">원글번호</th>
		</tr>

		<%
			for (ReplyDTO rDto : replyLists){
				 %>
		<tr align="center">
			<td><%= rDto.getReplyno() %></td>
			<td><%= rDto.getReplycomment() %></td>
			<td><%= rDto.getId() %></td>
			<td><%= rDto.getRegidate() %></td>
			<td><%= rDto.getNum() %></td>
		</tr>
		<%
				 }
				 }
				 %>
	</table>

	<h3>회원제 게시판 - 댓글(Reply)</h3>
	<form name="WriteFrm" method="post" action="ReplyProcess.jsp"
		onsubmit="return validateForm(this)">

		<input type="hidden" name="num" value="<%=dto.getNum() %>">

		<table border="1" width="90%">
			<tr>
				<td>댓글 내용</td>
				<td><textarea name="replycontent" style="width: 90%;"></textarea>
				</td>
			</tr>
			<tr>
				<td colspan="2" align="center">
					<button type="submit">작성완료</button>
					<button type="reset">다시입력</button>
				</td>
			</tr>
		</table>
	</form>
</body>

</html>

 

파라메터를 잘 못읽어서 null 을 받아올 때도 있었지만 수정 끝에 댓글 처리가 잘 되는 것을 확인 할 수 있었다. 

 

 

+ 주말동안은 댓글 수정, 삭제 버튼을 만들고 기능을 추가해보았다.

 

 👉 톰캣은 다쓰고 나면 꼭 서버 종료를 잘하자

인터넷도 다 종료하고 톰캣을 다시 켜야한다.

프로그래밍에서 1 = 참 0 = 거짓

 

🎉 게시판 만들기

이 코드는 통으로 그냥 외워라.

 

프로그램 만드는 두가지 방식 : 모델 1방식, 모델 2방식

모델 1방식 → 코드 다 한 곳에

모델 2방식 → 코드를 여러 곳에 분산, 로직 분리 (유지보수에 유리)

 

게시판 만들기는 모델 1방식으로 코딩 후 모델 2방식으로 바꿔본다.

 


DB 는 DAO 와, DTO는 List.jsp와 연결되어 동작한다.

페이징 기능까지 추가하면 모델 1 방식으로 게시판 만들기는 끝이 난다.

하면서 오류가 많이 나서 오류코드를 찾느라 메모를 많이 못했다..

하루하루 그날 배운 것을 정리하곤 하지만 이번에는 한번에 정리하는 것이 더 효율적으로 기억할 수 있을 것 같아 한 곳에 정리해본다.

파일이 많아지고 기능이 분산되며 효율적인 정리의 중요성을 깨닫는다..

 

그동안 했던 것을 생각나는대로 나열해보면

  1. SQL 디벨로퍼에서 테이블 만들고 데이터 넣기, 커밋
  2. DB에서 정보를 가지고 오는 DAO, 각 파일과 DAO(DB) 연결을 도와줄 DTO 만들기
  3. 목록보기(List.jsp) 만들고 게시글 목록을 DB에서 가지고 와 보여주기
  4. 목록에서 내용, 제목으로 글찾기 기능 구현
  5. 로그인 폼 연결, DB에서 로그인 아이디가 일치하는지 확인, 불일치 시 알려주기
  6. 로그인 성공 시 글쓰기 가능, 로그인 실패 시 글쓰기 기능 이용 불가
  7. 글쓰기 화면 만들기, 저장해서 게시목록에 올리기까지
  8. 글쓰기 화면에서 다시쓰기, 목록으로 돌아가기 버튼 구현
  9. 로그인 정보가 일치하면 게시글 상세보기도 가능
  10. 게시글 상세보기 화면 만들기
  11. 게시글 상세보기에서 해당 글 작성자의 경우 글 수정 가능하게 하기
  12. 해당 글 작성자인지 알아보기 위해 기능 만들기
  13. 해당 글 작성자 글 수정, 삭제하기(삭제하기 아직 미완. 0627 완료)
  14. 페이징기능 (한 페이지에 10개, 아래 쪽에 번호 나열, 해당번호에 맞는 10개의 글 노출)

정도 된다.

 

정리하며 순서는 바뀔 수도 있다.

생각해보면 이렇게 했어도 ‘회원 삭제, 회원 정보 수정, 글 임시저장’ 등등 추가적으로 구현할 수 있는게 정말 많은 것 같다. 그래서 사실상 정말 이 코드는 기본 중의 기본인듯.

 

모델 1 방식임에도 만든 파일이 꽤 많다.

  1. BoardDAO.java
  2. BoardDTO.java
  3. List.jsp
  4. Edit.jsp
  5. EditProcess.jsp
  6. IsLoggedIn.jsp
  7. DeleteProcess.jsp
  8. View.jsp
  9. Write.jsp
  10. WriteProcess.jsp

나열된 순서는 만든 순서와 무관하다.

이젠 구현화면과 코드로 하나씩 살펴보겠다.


 

🌀 데이터 베이스 연결

 

SQL 디벨로퍼에서 테이블 만들고 데이터 넣기, 커밋

-- 회원정보를 가지고 있는 member테이블, 게시판 글 정보를 가지고 있는 board테이블 생성

create table member (
id varchar2(10) not null,
pass varchar2(10) not null,
name varchar2(30) not null,
regidate date default sysdate not null,
primary key(id)
);

create table board (
num number primary key,
title varchar2(200) not null,
content varchar2(2000) not null,
id varchar2(10) not null,
postdate date DEFAULT sysdate not null,
visitcount number(6)
);

alter table board
add constraint board_mem_fk foreign key (id)
REFERENCES member(id);

-- 시퀀스 객체
create sequence seq_board_num
increment by 1
start with 1
minvalue 1;

-- member에 회원정보 데이터 넣기
insert into member (id, pass, name) values ('musthave', '1234', '머스트해브');

-- board에 글 데이터 넣기
insert into board (num, title, content, id, postdate, visitcount) 
   values (seq_board_num.nextval, '제목1입니다', '내용1입니다', 'musthave', sysdate, 0);

commit;


-- board에 글 데이터 넣기
insert into board
   values (seq_board_num.nextval, '지금은 봄입니다', '봄의왈츠', 'musthave', sysdate, 0);
insert into board
   values (seq_board_num.nextval, '지금은 여름입니다', '여름향기', 'musthave', sysdate, 0);
insert into board
   values (seq_board_num.nextval, '지금은 가을입니다', '가을동화', 'musthave', sysdate, 0);
insert into board
   values (seq_board_num.nextval, '지금은 겨울입니다', '겨울연가', 'musthave', sysdate, 0);      

commit;

회원정보를 가지고 있는 member 테이블과 게시판 글의 정보를 가지고 있는 board 테이블을 만들고 그 안에 데이터를 넣어두었다.

 

DAO, DTO 만들기

💡 DAO (Date Access Object) : 직접 DB에 접근하며 data를 삽입, 삭제, 조회 등 조작
💡 DTO (Data Transfer Object) : 로직을 가지지 않는 데이터 객체, 계층 간 데이터 교환
을 위한 Java Bean을 의미한다.

→ Java Bean 이란 특정한 정보를 가지고 있는 클래스를 표현하는 하나의 규칙. 데이터를 표현하기 위해 사용한다.

 

BoardDTO.java

package model1.board;

import lombok.Getter;
import lombok.Setter;

@Getter
@Setter
public class BoardDTO {
private String num;
private String title;
private String content;
private String id;
private java.sql.Date postDate;
private String visitcount;
private String name; // 로그인했을 떄 필요한 이름 member 테이블에도 접근할 수 있어야 한다
}

 

BoardDAO.java

package model1.board;

import java.sql.ResultSet;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;

import common.JDBConnect;
import jakarta.servlet.ServletContext;

public class BoardDAO extends JDBConnect {

	public BoardDAO(ServletContext application) {
		super(application);
	}

	// 1. 검색 조건에 맞는 게시물의 개수를 반환합니다.
	public int selectCount(Map<String, Object> map) {

		int totalcount = 0;

		// 복잡한 쿼리문은 오류날 확률이 높으므로 디벨로퍼에서 미리 실행해보고 옮기는 것이 좋다.
		String query = " select count(*) from board ";

		// searchWord를 선택하면 이름, 내용으로 분류해서 count 해줘야 한다.
		if (map.get("searchWord") != null) {
			query += " where " + map.get("searchField") + "" + " like '%" + map.get("searchWord") + "%'";

			// where title like '%제목%';
		}

		try {

			psmt = con.prepareStatement(query);
			rs = psmt.executeQuery();

			if (rs.next()) {
				totalcount = rs.getInt(1);
			}

		} catch (Exception e) {
			e.printStackTrace();
		}

		return totalcount;
	}

	// 2. 검색 조건에 맞는 게시물 목록을 반환합니다.
	public List<BoardDTO> selectList(Map<String, Object> map) {

		List<BoardDTO> bbs = new ArrayList<>();

		String query = " select * from board ";

		if (map.get("searchWord") != null) {
			query += " where " + map.get("searchField") + " like '%" + map.get("searchWord") + "%'";
		}

		query += " order by num desc ";

		try {

			psmt = con.prepareStatement(query);
			rs = psmt.executeQuery();

			while (rs.next()) {
				// 이 정보들을 DTO에 넣어준다
				BoardDTO dto = new BoardDTO();
				// DB 컬럼명
				dto.setNum(rs.getString("num"));
				dto.setTitle(rs.getString("title"));
				dto.setContent(rs.getString("content"));
				dto.setPostDate(rs.getDate("postdate"));
				dto.setId(rs.getString("id"));
				dto.setVisitcount(rs.getString("visitcount"));

				// DTO는 가장 최근의 정보만 읽어온다.
				// 읽어와서 DTO에 담긴 정보를 다른 곳에 저장해야 목록을 볼 수 있다.
				// collection ArayList()에 저장
				bbs.add(dto);

			}

		} catch (Exception e) {
			e.printStackTrace();
		}

		// 목록 데이터가 담긴 bbs를 리턴한다.
		return bbs;
	}

	// 3. 검색 조건에 맞는 게시물 목록을 반환합니다(페이징 기능 지원).

	// 4. 게시글 데이터를 받아 DB에 추가합니다.
	// 실제 데이터를 insert할 수 있는 기능
	public int insertWrite(BoardDTO dto) {

		// insert 구문이 들어갔는지 확인
		int result = 0;

		String query = " insert into board " + " (num, title, content, id, visitcount) "
				+ " values (seq_board_num.nextval,?,?,?,0) ";

		try {

			psmt = con.prepareStatement(query);
			psmt.setString(1, dto.getTitle());
			psmt.setString(2, dto.getContent());
			// 로그인한 ID 받아오기 -> session 영역의 정보를 받아온다
			psmt.setString(3, dto.getId());

			result = psmt.executeUpdate();

		} catch (Exception e) {
			e.printStackTrace();
		}

		return result;
	}

	// 5. 지정한 게시물을 찾아 내용을 반환합니다.
	public BoardDTO selectView(String num) {

		BoardDTO dto = new BoardDTO();

		String query = " select B.*, M.name " + " from member M inner join board B " + " on M.id = B.id"
				+ " where num = ? ";

		try {

			psmt = con.prepareStatement(query);
			psmt.setString(1, num);
			rs = psmt.executeQuery();

			if (rs.next()) {

				dto.setNum(rs.getString(1));
				dto.setTitle(rs.getString(2));
				dto.setContent(rs.getString("content"));
				dto.setPostDate(rs.getDate("postdate"));
				dto.setId(rs.getString("id"));
				dto.setVisitcount(rs.getString(6));
				dto.setName(rs.getString("name"));

				// System.out.println("kkkkkk : " + dto.getId());

			}

		} catch (Exception e) {
			e.printStackTrace();
		}

		return dto;

	}

	// 6. 지정한 게시물의 조회수를 1 증가시킵니다.
	public void updateVisitCount(String num) {

		String query = " update board " + " set visitcount = visitcount + 1 " + " where num = ? ";

		try {

			psmt = con.prepareStatement(query);
			psmt.setString(1, num);
			psmt.executeUpdate();

		} catch (Exception e) {
			e.printStackTrace();
		}
	}

	// 7. 지정한 게시물을 수정합니다.
	public int updateEdit(BoardDTO dto) {

		int result = 0;

		String query = " update board " + " set title = ? , content = ? " + " where num = ? ";

		try {

			psmt = con.prepareStatement(query);
			psmt.setString(1, dto.getTitle());
			psmt.setString(2, dto.getContent());
			psmt.setString(3, dto.getNum());

			result = psmt.executeUpdate();

		} catch (Exception e) {
			e.printStackTrace();
		}

		return result;
	}

	// 8. 지정한 게시물을 삭제합니다.
	public int deletePost(BoardDTO dto) {

		int result = 0;

		String query = " delete from board where num = ? ";

		try {

			psmt = con.prepareStatement(query);
			psmt.setString(1, dto.getNum());
			
			result = psmt.executeUpdate();

		} catch (Exception e) {
			e.printStackTrace();
		}

		return result;
	}

}

이곳에 게시판에 쓸 기능을 다 모아두었다.

 


🌀 게시판 목록

 

목록보기(List.jsp) 만들고 게시글 목록 보여주기

  1. 목록보기(List.jsp) 만들고 게시글 목록을 DB에서 가지고 와 보여주기
  2. 목록에서 내용, 제목으로 글찾기 기능 구현

 

List.jsp

<%@page import="model1.board.BoardDAO"%>
<%@page import="model1.board.BoardDTO"%>
<%@page import="java.util.HashMap"%>
<%@page import="java.util.Map"%>
<%@page import="java.util.List"%>
<%@ page language="java" contentType="text/html; charset=UTF-8"
	pageEncoding="UTF-8"%>
<%
// DB에 연결
BoardDAO dao = new BoardDAO(application);

// form에서 액션 속성 생략 -> 자기 자신 페이지를 파라매터로 받는다. 
String searchField = request.getParameter("searchField");
String searchWord = request.getParameter("searchWord");

Map<String, Object> param = new HashMap<>();

// 검색조건으로 검색하려 할때
if (searchWord != null) {
	param.put("searchField", searchField);
	param.put("searchWord", searchWord);
}

// 전체 개수 구하기 selectCount()
// 검색 했을 때 개수 dao.selectCount(param)
// boardLists 에 DTO의 정보가 들어있다.
int totalCount = dao.selectCount(param);
List<BoardDTO> boardLists = dao.selectList(param);
dao.close();

%>

<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Insert title here</title>
</head>
<body>
	<jsp:include page="../Common/Link.jsp" />
	<h2>목록 보기(List)</h2>

	<!-- 폼태그에 action 속성이 생략되면 자신의 현재 페이지를 요청한다. (List.jsp)-->
	<form method="get">
		<table border="1" width="90%">
			<tr>
				<td align="center">
				<select name="searchField">
						<option value="title">제목</option>
						<option value="content">내용</option>
				</select> 
				<input type="text" name="searchWord" /> 
				<input type="submit" value="검색하기" />
				</td>
			</tr>
		</table>
	</form>

	<table border="1" width="90%">
		<tr>
			<th width="10%">번호</th>
			<th width="50%">제목</th>
			<th width="15%">작성자</th>
			<th width="10%">조회수</th>
			<th width="15%">작성일</th>
		</tr>


		<!-- 게시물이 없을 떄 -->
		<% if (boardLists.isEmpty()) {%>

		<tr>
			<td colspan="5" align="center">등록된 게시물이 없습니다^^*</td>
		</tr>

		<% }else{

		int virtualNum = 0;
		for(BoardDTO dto : boardLists){ 
			virtualNum = totalCount--;
		%>

		<!-- 게시물이 있을 때 -->
		<tr align="center">
		<!-- DTO에서 쓴 이름과 같게 -->
			<%-- <td><%= dto.getNum() %></td> --%>
			<td><%= virtualNum %></td>
			<td>
			<a href="View.jsp?num=<%= dto.getNum() %>"> <%= dto.getTitle() %> </a>

			</td>
			
			
			<td><%= dto.getId() %></td>
			<td><%= dto.getVisitcount() %></td>
			<td><%= dto.getPostDate() %></td>
		</tr>

		<%
		 } // for문 
		} // if else문
		%>

	</table>

	<table border="1" width="90%">
		<tr>
			<td align="right">
				<button type="button" onclick="location.href='write.jsp'">글쓰기</button>
			</td>
		</tr>
	</table>
</body>
</html>

DB에서 게시글 데이터를 읽어온다.

form으로 제목,내용으로 게시글을 검색할 수 있는 검색바를 만들고

table에 게시글 목록이 보여지게 화면을 구성했다.

 

폼태그에 action 속성이 생략되었으므로 페이지 이동 없이 현재 페이지에서 검색된 결과를 보여준다.


 

🌀 로그인 인증과 글 게시

  1. 로그인 폼 연결, DB에서 로그인 아이디가 일치하는지 확인, 불일치 시 알려주기
  2. 로그인 성공 시 글쓰기 가능, 로그인 실패 시 글쓰기 기능 이용 불가
  3. 글쓰기 화면 만들기, 저장해서 게시목록에 올리기까지
  4. 글쓰기 화면에서 다시쓰기, 목록으로 돌아가기 버튼 구현

 

IsLoggedIn.jsp

<%@page import="utils.JSFunction"%>
<%@ page language="java" contentType="text/html; charset=UTF-8"
	pageEncoding="UTF-8"%>
<!--  로그인 유무를 판단 -->
<%
if (session.getAttribute("UserId") == null){
	JSFunction.alertLocation("로그인 후 이용해주세요.", "../Session/LoginForm.jsp", out);
	return;
}
%>

 

JSFunction

package utils;

import jakarta.servlet.jsp.JspWriter;

public class JSFunction {
	// location.href = "페이지 경로" -> 특정페이지 지정
	// history.back(), history.forward() -> 뒤로가기, 앞으로 가기

	public static void alertLocation(String msg, String url, JspWriter out) {
		try {
			String script = ""
					+ "<script>"
					+ "alert('" + msg + "');" // alert('hello')
					+ "location.href = '" + url + "'"
					+ "</script>";
			
			out.print(script);
					
		}catch(Exception e) {
			
		}
	}

	public static void alertBack(String msg, JspWriter out) {
		try {
			String script = ""
					+ "<script>"
					+ "alert('" + msg + "');"
					+ "history.back();"
					+ "</script>";
					
			out.print(script);
			
		}catch(Exception e) {
			
		}

	}
}

 

게시판 설계도를 보면 어디로 이동을 하든 로그인 유무를 체크한다.

<%@ include file ="./IsLoggedIn.jsp"%>

때문에 위와 같이 함수를 만들어두고 로그인 유무 체크를 하는 사이트마다 페이지 속성 지정을 해준다.

 

 

로그인 성공 시 글 쓰고, 올리기 기능

 

유효성 검사)

더보기
<!-- 유효성 검사 -->
<script>
function validateForm(form){
	if (form.title.value == ""){
		alert("제목을 입력하세요");
		form.title.focus();
		return false;
	}
	
	if (form.content.value == ""){
		alert("내용을 입력하세요");
		form.content.focus();
		return false;
	}
}
</script>

 (이미지)

 

로그인 성공 후 글쓰고 작성완료하면 게시글 목록에 글이 올라가고, DB에도 잘 전달이 되었는지 확인해본다.

 

write.jsp

<%@ page language="java" contentType="text/html; charset=UTF-8"
	pageEncoding="UTF-8"%>
	<!-- 공통관심사항 -->
	<%@ include file ="./IsLoggedIn.jsp"%>
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Insert title here</title>
<!-- 유효성 검사 -->
<script>
function validateForm(form){
	if (form.title.value == ""){
		alert("제목을 입력하세요");
		form.title.focus();
		return false;
	}
	
	if (form.content.value == ""){
		alert("내용을 입력하세요");
		form.content.focus();
		return false;
	}
}
</script>
</head>
<body>
	<jsp:include page="../Common/Link.jsp" />
	<h2>회원제 게시판 - 글쓰기(Write)</h2>


	<form name="writeFrm" method="post" action="WriteProcess.jsp"
		onsubmit="return validateForm(this);">
		<table bordar="1" width="90%">
			<tr>
				<td>제목</td>
				<td><input type="text" name="title" style="width: 90%;" /></td>
			</tr>

			<tr>
				<td>내용</td>
				<td><textarea name="content" style="width: 90%; height: 100px;"></textarea>
				</td>
			</tr>

			<tr>
				<td colspan="2" align="center">
					<button type="submit">작성 완료</button>
					<button type="reset">다시 입력</button>
					<button type="button" onclick="location.href='List.jsp';">
						목록 보기</button>
						</td>
			</tr>

		</table>
	</form>
</body>
</html>

 

WriteProcess.jsp

작성한 글을 DB와 연결해 DB에 전달해준다.

<%@page import="model1.board.BoardDTO"%>
<%@page import="model1.board.BoardDAO"%>
<%@ include file ="./IsLoggedIn.jsp"%>
<%@page import="utils.JSFunction"%>
<%@ page language="java" contentType="text/html; charset=UTF-8"
	pageEncoding="UTF-8"%>

<%
// 타이틀과 컨텐츠를 파라메터로 받는다.
String title = request.getParameter("title");
String content = request.getParameter("content");

// DTO의 데이터를 묶어서 DAO로 보낸다.
// 서버 -> DTO -> DAO -> DB
// 서버 <- DTO <- DAO

BoardDTO dto = new BoardDTO();

dto.setTitle(title);
dto.setContent(content);
dto.setId((String)session.getAttribute("UserId"));

// DAO에 데이터를 삽입하는 기능 추가
// DB에 연결
BoardDAO dao = new BoardDAO(application);
int result = dao.insertWrite(dto);
dao.close(); 

if(result > 0){
	// 성공했을 때 페이지 이동
	response.sendRedirect("List.jsp");
}else{
	// 실패 시 이전 페이지로 
	JSFunction.alertBack("글쓰기에 실패하였습니다.", out);
}

%>

 

 

글쓰기 폼을 만들고 작성완료, 다시입력, 목록보기 버튼을 만들었다.

 

글을 쓰고 나면 DB에도 잘 반영이 되는 것을 볼 수 있다.

 


 

🌀 게시글 상세보기, 수정 및 삭제

  1. 로그인 정보가 일치하면 게시글 상세보기도 가능
  2. 게시글 상세보기 화면 만들기
  3. 게시글 상세보기에서 해당 글 작성자의 경우 글 수정 가능하게 하기
  4. 해당 글 작성자인지 알아보기 위해 기능 만들기
  5. 해당 글 작성자 글 수정, 삭제하기(삭제하기 아직 미완. 0627 완료)

 

게시글 상세보기

 

View.jsp

어떤 글을 상세화면으로 보고, 수정하고 삭제할지 알기 위해 글의 번호(num)를 받는다.

<%@ page language="java" contentType="text/html; charset=UTF-8"
	pageEncoding="UTF-8"%>
<%@ include file="./IsLoggedIn.jsp"%>
<%@page import="model1.board.BoardDTO"%>
<%@page import="model1.board.BoardDAO"%>

<%
String num = request.getParameter("num");
BoardDAO dao = new BoardDAO(application);

// 글에 대한 상세보기 -> 조회수 증가 처리
dao.updateVisitCount(num);

// 상세정보 보기
BoardDTO dto = dao.selectView(num);
%>


<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Insert title here</title>

<script>

function deletePost(){
	let confirmed = confirm("정말로 삭제하시겠습니까?");
	
	if(confirmed){
		let form = document.writeFrm; // 이름이 writeFrm인 폼 선택
		// 입력 폼에 값이 없지만 post 방식으로 넘긴다
		form.method = "post"; // 전송 방식
		form.action = "DeleteProcess.jsp"; // 전송 경로
		// 객체 = 기능 + 속성
		form.submit(); // 폼값전송
		
	}
}

</script>
</head>

<body>
<%@ include file ="./IsLoggedIn.jsp"%>
<h2>회원제 게시판 - 상세보기(View)</h2>
	<form name="writeFrm">
	<input type="hidden" name="num" value="<%=num %>">

		<table border="1" width="90%">
		
			<tr>
				<td>번호</td>
				<td><%=dto.getNum()%></td>
				<td>작성자</td>
				<td><%=dto.getName()%></td>
			</tr>
			
			<tr>
				<td>작성일</td>
				<td><%=dto.getPostDate()%></td>
				<td>조회수</td>
				<td><%=dto.getVisitcount()%></td>
			</tr>
			
			<tr>
			<td>제목</td>
			<td colspan="3"><%= dto.getTitle() %></td>
			</tr>
			
			<tr>
			<td>내용</td>
			<!-- 줄바꿈이 안되어 저장되기 때문에 강제 줄바꿈을 해줘야 한다 -->
			<td colspan="3" height="100px"><%= dto.getContent().replace("\r\n", "<br/>") %></td>
			</tr>
			
			<tr>
			<td colspan="4" align ="center">
			<!-- 글을 작성한 사람이 아니라면 수정하기, 삭제하기 버튼 안보이게 -->
			<%
			if (session.getAttribute("UserId") != null
			&& session.getAttribute("UserId").toString().equals(dto.getId())) {
				
			%>
			<button type = "button" onclick="location.href = 'Edit.jsp?num=<%= dto.getNum() %>'">수정하기</button>
			<button type = "button" onclick="deletePost()">삭제하기</button>
			
			<% } %>
			<button type = "button" onclick="location.href = 'List.jsp'">목록보기</button>
			</td>
			</tr>
		</table>
		
	</form>
</body>

</html>

 

게시글 상세화면으로 가기 위한 링크가 생긴다.

 

😞 질문 input type= “hidden” 인 이유

<input type="hidden" name="num" value="<%=num %>">

<input type="hidden" > 은 사용자에게는 보이지 않는 숨겨진 입력 필드를 정의한다. 

숨겨진 입력 필드는 렌더링이 끝난 웹페이지에서는 전혀 보이지 않으며 페이지 콘텐츠 내에서도 보게 만들 방법이 없다. 

폼 제출 시 사용자가 변경해서는 안되는 데이터를 전달할 때 유용하게 쓰인다. -> num 과 같은 PK 값, 업데이트 되야 하는 데이터 베이스 레코드 값, 고유한 보안 토큰 등을 서버로 보낼 때 주로 사용 

 

글 수정, 삭제하기

 

Edit.jsp

해당 글 작성자인지 확인하고 해당 글 작성자라면 글을 수정하고 삭제할 수 있게 해준다.

<%@ page language="java" contentType="text/html; charset=UTF-8"
    pageEncoding="UTF-8"%>
<%@ page import="model1.board.BoardDAO"%>
<%@ page import="model1.board.BoardDTO"%>
<%@ include file="./IsLoggedIn.jsp"%> 
<%
   String num = request.getParameter("num");
   
   BoardDAO dao = new BoardDAO(application);
   BoardDTO dto = dao.selectView(num);
   
   String sessionId = session.getAttribute("UserId").toString();
   
   System.out.println(sessionId);
   System.out.println(dto.getId());
   System.out.println(dto.getNum());
   
   if(!sessionId.equals(dto.getId())){
      JSFunction.alertBack("작성자 본인만 수정할 수 있습니다.", out);
      return;
   }
   
   dao.close();
%>
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Insert title here</title>
<script>

   //validateForm()
   function validateForm(form) {
      if (form.title.value == "") {
           alert("제목을 입력하세요.");
           form.title.focus();
           return false;
       }
       if (form.content.value == "") {
           alert("내용을 입력하세요.");
           form.content.focus();
           return false;
       }
   }
</script>
</head>
<body>
<%@ include file ="./IsLoggedIn.jsp"%>
   <h2>회원제 게시판 - 수정하기(Edit)</h2>
   <form name="writeFrm" method="post" action="EditProcess.jsp" onsubmit="return validateForm(this);">
   <!-- 값을 추가적으로 지정해서 넘길 때 hidden form -->
   <!-- num, title, content 가 넘어간다. -->
   <input type="hidden" name="num" value="<%=dto.getNum() %>">
   
      <table border="1" width="90%">
         <tr>
            <td>제목</td>
            <td>
               <input type="text" name="title" value="<%= dto.getTitle() %>" />
            </td>
         </tr>
         <tr>
            <td>내용</td>
            <td>
               <textarea name="content" style="width: 90%; height: 100px;"><%=dto.getContent() %></textarea>
            </td>
         </tr>
         <tr>
            <td colspan="2" align="center">
                <button type="submit">작성완료</button>
                <button type="reset">다시입력</button>
                <button type="button" onclick="location.href='List.jsp'">목록보기</button>
            </td>
         </tr>
      </table>
   </form>
</body>
</html>

 

EditProcess.jsp

수정한 글을 DB에 올린다

<%@ page language="java" contentType="text/html; charset=UTF-8"
	pageEncoding="UTF-8"%>
<%@ page import="model1.board.BoardDAO"%>
<%@ page import="model1.board.BoardDTO"%>
<%@ page import="utils.JSFunction"%>
<%@ include file="./IsLoggedIn.jsp"%>

<%
String num = request.getParameter("num");
String title = request.getParameter("title");
String content = request.getParameter("content");

BoardDTO dto = new BoardDTO();
dto.setNum(num);
dto.setTitle(title);
dto.setContent(content);

BoardDAO dao = new BoardDAO(application);
int result = dao.updateEdit(dto);
dao.close();

/* 글 올리기 성공했을 때는 상세보기 화면, 실패하면 수정하기 */
if (result > 0){
	/* View 페이지에서 어떤 글을 수정해야 하는지 알기 위해  */
	response.sendRedirect("View.jsp?num=" + dto.getNum());
}else{
	JSFunction.alertBack("수정하기에 실패했습니다", out);
}

%>

 

DeleteProcess.jsp

글을 삭제하고 DB에 연결해 해당 데이터를 삭제한다.

<%@ page language="java" contentType="text/html; charset=UTF-8"
    pageEncoding="UTF-8"%>
<%@ page import="model1.board.BoardDAO"%>
<%@ page import="model1.board.BoardDTO"%>
<%@ page import="utils.JSFunction" %>
<%@ include file="./IsLoggedIn.jsp"%> 

<%
String num = request.getParameter("num");

BoardDTO dto = new BoardDTO();
BoardDAO dao = new BoardDAO(application);
dto = dao.selectView(num);

String sessionId = session.getAttribute("UserId").toString();

// 글을 작성한 사람인지 아닌지. 글을 작성한 사람만 게시글을 삭제할 수 있다.
if (sessionId.equals(dto.getId())){
	// 본인 인증 성공, 삭제 처리
	dto.setNum(num);
	int result = dao.deletePost(dto);
	dao.close();
	
	if (result > 0) {
		// 삭제 성공
		JSFunction.alertLocation("삭제되었습니다", "List.jsp", out);
	}else{
		// 삭제 실패 -> 상세 페이지 화면
		JSFunction.alertBack("삭제에 실패했습니다.", out);
	}
	
}else{
	// 본인 인증 실패
	JSFunction.alertBack("본인만 삭제할 수 있습니다.", out);
	return;
}

%>

 


💫 페이징이 된 모습을 보기 위해 Borad를 복사해 PagingBorad 폴더를 만들었다.

 

🌀 페이징

 

페이징 기능 (0627)

더보기

예를 들면 최신순으로 게시글 10개 화면에 노출 후 그 이상으로 글이 있다면

페이지 하단에 [1], [2], [3], [4], [5] 이런식으로 블록을 눌러 나머지 글을 더 볼 수 있게 해준다.

첫번째 페이지 < [1], [2], [3], [4], [5], > 마지막 페이지
첫번째 페이지 < [6], [7], [8], [9], [10], > 마지막 페이지
첫번째 페이지 < [11], [12], [13] > 마지막 페이지로

이런 식으로 총 123개의 글이 있고, 10개 단위로 페이징 기능을 구현한다고 하면 블록 간에 이동 ( < , > )을 할수도 있게 만든다. 추가로 한번에 첫번째 페이지로 이동할 수도 있고 마지막 페이지로 이동할 수도 있다.

글도 10개 단위로 글을 노출할 수도 있고, 5개 단위로 노출할 수도 있다. 또, 사용자의 선택에 따라 10개, 5개 등 선택할 수도 있다.

 

페이징 기능을 위한 변수

  • 한 페이지에 출력할 게시물의 개수

변수 이름 ) POSTS_PER_PAGE = 10

  • 한 화면(블록)에 출력할 페이지 번호 개수

변수 이름 ) PAGES_PER_BLOCK = 5

 

페이징 구현 절차

 

1. 테이블에 저장된 전체 레코드의 개수

ex) 105개

 

2. 각 페이지 별 출력될 게시물의 범위 계산

범위의 시작값 구하는 두가지 방식 : 

1. 범위의 끝 값 - 9
2. (현재 페이지 번호 - 1) * 게시물의 개수 + 1
범위의 끝 값 : 현재 페이지 번호 * POST_PER_PAGE (한 페이지에 출력할 게시물의 개수)

 

3. 전체 페이지 수 구하기

게시물 수 105개

페이지 수 : Math.ceil(105 / POST_PER_PAGE) ⇒ 11

 

4. 이전 페이지 블록 가기

- 현재 1 페이지 일 때

pageTemp = ( (현재 페이지 - 1 ) / PAGES_PER_BLOCK) ) * PAGES_PER_BLOCK + 1

어떤 페이지를 받든 처음 시작되는 번호를 계산한다.

-현재 5 페이지 일 때

pageTemp = ( (5-1) / 5 ) * 5 + 1 ⇒ 1

// 몫을 구하는 것 (예를 들어 4/5 ⇒ 0)

-현재 10 페이지 일 때

pageTemp = ( (10-1) / 5 ) * 5 + 1 ⇒ 6

// 몫을 구하는 것 (예를 들어 9/5 ⇒ 1)

pageTemp - 1 하면 이전 블록의 끝 값이 된다.

 

5. 블록에 페이지 번호 5개 출력하기

pageTemp에 1씩 더하기 한다 ⇒ 1, 2, 3, 4, 5

 

6. 다음 페이지 블럭가기

마지막 pageTemp값에 1을 더하면 다음 블록의 시작값이 구해진다.

 

게시글 데이터 100개를 추가했다.

 

데이터베이스에서

-- 뷰 테이블을 활용한 TOP-N 구하기
-- rownum(의사컬럼) : 조회 시 레코드 순서대로 1번 부터 번호 부여
-- 오라클 시스템에서 제공해주는 컬럼, 테이블에는 없지만 제공 -> 의사컬럼
-- 아무 테이블이나 사용 가능
-- rownum은 order by절보다 먼저 실행된다, 조건절에 사용 시 반드시 1을 포함한 조건식을 만들어야 한다.

-- 인라인 뷰
-- 일회성 가상 테이블
-- 메모리의 부화를 주지 않음 -> 효율성이 높아진다

-- board에 rm 주기
select * from board; 

-- 별칭을 부여해서 고정된 값으로 사용할 수 있게 해준다.
select rownum rm, p.*
from (
select *
from board
order by postdate desc 
) p;


-- 인라인 컬럼으로 쓰면서 실제 테이블에 고정되게 만든다.
-- 최신순으로 1번부터 10번까지 조회하기
select *
from (
select rownum rm, p.*
from (
select *
from board
order by postdate desc
) p
)
where rm between 1 and 10; 


-- 검색 조건에 맞는 게시물 목록 반환
select *
from (
select rownum rm, p.*
from (
select *
from board
where title like '%2%' -- 예를 들어 제목에 2가 포함된 게시글을 검색 
order by postdate desc
) p
)
where rm between 1 and 10;

rownum을 붙이게 되면 1번부터 데이터의 순서가 고정된다.

 

😞 질문 → 번호 부여하는 것에 왜 시퀸스를 안쓰나? 비슷한 거 아닌가??

더보기

시퀸스도 똑같이 번호를 부여하지만 primary key 값을 생성하기 위해 사용된다.

또 시퀸스로 번호를 매길 경우, 중간에 데이터를 지우게 되면 해당 번호 게시글은 삭제됨에도 번호는 재정렬이 되지 않기 때문에 이때는 rownum을 쓰는 것이 적절하다.

 

시퀀스 객체가 사용하는 값은 계속 캐시에 저장되어 있어서 삭제를 해도 되돌리는 것이 불가능하다. (!)

 

그러고 보니 쿠키는 알겠는데 캐시는 뭐지!? 비슷한거같은데 (아님)

→ 캐시(Cache)는 컴퓨터 시스템에서 빠른 데이터 액세스를 가능하게 하는 저장 공간의 한 형태입니다. 이는 자주 사용되는 데이터나 값을 복사해 임시로 저장해 놓는 공간으로, 한 번 저장된 데이터에 대한 요청이 있을 경우 원래의 저장 위치보다 더 빠르게 데이터를 제공하게 됩니다. 이로 인해 시스템의 전체 성능이 향상되고, 데이터에 접근하는 시간이 줄어듭니다.

완전 다른거였다..

쿠키와 캐시의 가장 큰 차이점은 저장 위치와 데이터의 사용 목적입니다. 쿠키는 클라이언트에 저장되어 서버와의 통신에 사용되는 반면, 캐시는 브라우저 내부에 저장되고 페이지 로딩 속도를 향상시키는 데 사용됩니다.

쿠키와 캐시는 모두 웹 애플리케이션의 성능 및 사용자 경험 개선을 위해 사용되는 도구이지만, 쿠키는 사용자 상태 정보를 추적하고 유지하는 데 사용되고, 캐시는 리소스 로딩 속도를 향상시키는 데 사용됩니다.

출처) https://www.inflearn.com/questions/969727/쿠키와-캐시의-차이점이-뭔가요

 

BoardDAO.java 에 추가한 코드)

	// 3. 검색 조건에 맞는 게시물 목록을 반환합니다(페이징 기능 지원).
	public List<BoardDTO> selectListPage(Map<String, Object> map) {
		
		List<BoardDTO> bbs = new ArrayList<>();
		
		String query = " select * "
				+ " from ( "
				+ " select rownum rm, p.* "
				+ " from ( "
				+ " select * "
				+ " from board ";
				
				if (map.get("searchWord") != null) {
					query += " where " + map.get("searchField") + " like '%" + map.get("searchWord") + "%'";
				}
				
				query += " order by num desc "
						+ " ) p "
						+ " ) "
						+ " where rm between ? and ? ";

		try {

			psmt = con.prepareStatement(query);
			psmt.setString(1, map.get("start").toString());
			psmt.setString(2, map.get("end").toString());

		} catch (Exception e) {
			e.printStackTrace();
		}
		
		return null;
	}

 


0625)

오늘 시험에서 잘 몰랐던 부분

더보기

오라클 null 값 제외하고 값 읽어오기

 

SELECT A컬럼 FROM B테이블 WHERE NOT A컬럼 is NULL;

그냥 머리 속에 아예 not 구문이 삭제되어 있었다..;

 

요청한 작업을 수행하는 중 오류 발생: IO 오류: The Network Adapter could not establish the connection (CONNECTION_ID=zKdIfC+zSJKe0zs0HLXHFg==) 업체 코드 17002

 

원인 : 오라클 오토런이 걸려있지 않아 발생한 오류.

 

잘 되다가도 가끔 이런 오류가 뜨는데 그럴 땐 이렇게 오라클 컨테이너를 확인해보고,

docker ps --all

구동시킬 컨테이너 이름을 적어 다시 시작해주면 된다.

docker restart (구동시킬 컨테이너 이름)

 

오라클 디벨로퍼도 껐다 다시 켜서 실행해보면 잘 된다.

🍀 세션의 역할

 

http - 새로 접속하는 개념. 이전의 클라이언트가 만들어 놓은 정보를 사용할 수 없다.

서버가 클라이언트에게 cookie 정보에 sessionid 를 심어 보낸다. (response)

예) 비회원 주문

sessionid 는 유효시간이 있다.

 

 

SessonMain.jsp

<%@page import="java.text.SimpleDateFormat"%>
<%@page import="java.util.Date"%>
<%@ page language="java" contentType="text/html; charset=UTF-8"
	pageEncoding="UTF-8"%>
	
<%
SimpleDateFormat dateFormat = new SimpleDateFormat("HH:mm:ss");

long creationTime = session.getCreationTime();
String creationTimeStr = dateFormat.format(new Date(creationTime));

long lastTime = session.getLastAccessedTime();
String lastTimeStr = dateFormat.format(new Date(lastTime));
%>

<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Insert title here</title>
</head>
<body>

	<h2>Session 설정확인</h2>
	
	<ul>
		<li>세션 유지시간 : <%=session.getMaxInactiveInterval()%></li>
		<li>세션 아이디 : <%=session.getId()%></li>
		<li>최초 요청 시각 : <%= creationTimeStr %></li>
		<li>마지막 요청 시간 : <%=lastTimeStr%>
		</li>
	</ul>
	
</body>
</html>

 

클라이언트 → 서버 → DTO → DAO → DB

클라이언트 ← 서버 ← DTO ← DAO ← DB

 

DTO, DAO 만들어서 아이디와 패스워드 비교

 

DTO를 만드는 기준 : DTO = 빈

빈을 만드는 기준과 같음.

 

* 패키지는 무조건 소문자로 만들기

* 프로젝트 만들때는 테이블 우선 만들기

* 테이블단위로 DAO, DTO가 만들어진다.

 

 

LoginForm.jsp

<%@ page language="java" contentType="text/html; charset=UTF-8"
	pageEncoding="UTF-8"%>
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Insert title here</title>
<script>
	function validateForm(form) {
		if (!form.user_id.value) {
			alert("아이디를 입력하세요.");
			return false;
		}
		if (form.user_pw.value == "") {
			alert("패스워드를 입력하세요.");
			return false;
		}
	}
</script>
</head>
<body>
	<h2>로그인 페이지</h2>
	<%=request.getAttribute("LoginErrMsg") == null ? "" : request.getAttribute("LoginErrMsg")%>

	<%
	if (session.getAttribute("UserId") == null) {
	%>
	<form action="LoginProcess.jsp" method="post" name="LoginFrm"
		onsubmit="return validateForm(this)">
		아이디 : <input type="text" name="user_id" /><br /> 
		패스워드 : <input type="password" name="user_pw" /><br /> 
		<input type="submit" value="로그인하기" />
	</form>

	<%
	} else {
	%>
	<%=session.getAttribute("UserName")%>
	회원님, 로그인하셨습니다.
	<br>
	<a href="Logout.jsp"> [로그아웃] </a>
	<%
	}
	%>
</body>
</html>

 

MemberDTO.java

package membership;

import lombok.Getter;
import lombok.Setter;

@Getter
@Setter

//SQL의 테이블명과 일치하게 클래스를 만든다.
public class MemberDTO {
	private String id;
	private String pass;
	private String name;
	private String regidate;
	
}

 

MemberDAO.java

package membership;

import common.JDBConnect;

//DB에 접속해서 쿼리문 처리하는 역할
public class MemberDAO extends JDBConnect {

	public MemberDAO(String driver, String url, String id, String pwd) {
		super(driver, url, id, pwd);
	}
	// 여기까지 DB 접속

	// 명시한 아이디/패스워드와 일치하는 회원 정보 반환
	public MemberDTO getMemberDTO(String uid, String upass) {

		MemberDTO dto = new MemberDTO();
		String query = " select * from member where id = ? and pass = ? ";

		// 쿼리문은 예외처리가 항상 필요하다
		try {
			psmt = con.prepareStatement(query);
			psmt.setString(1, uid);
			psmt.setString(2, upass);
			rs = psmt.executeQuery();

			if (rs.next()) {
				dto.setId(rs.getString("id")); // 컬럼명 또는 숫자(1부터 시작)
				dto.setPass(rs.getString("pass")); // 가능하면 컬럼 이름으로 쓰는 것이 좋음, 그룹은 숫자로
				dto.setName(rs.getString(3));
				dto.setRegidate(rs.getString(4));
			}

		} catch (Exception e) {
			e.printStackTrace();
		}

		return dto;
	}

}

 

LoginProcess.jsp

<%@page import="membership.MemberDTO"%>
<%@page import="membership.MemberDAO"%>
<%@ page language="java" contentType="text/html; charset=UTF-8"
	pageEncoding="UTF-8"%>
<%@page import="membership.MemberDAO"%>
<!-- 로그인 아이디 패스워드 판단 -->
<%
String userId = request.getParameter("user_id");
String userPwd = request.getParameter("user_pw");

String oracleDriver = application.getInitParameter("OracleDriver");
String oracleURL = application.getInitParameter("OracleURL");
String oracleId = application.getInitParameter("OracleId");
String oraclePwd = application.getInitParameter("OraclePwd");

MemberDAO dao = new MemberDAO(oracleDriver,oracleURL,oracleId,oraclePwd);
MemberDTO memberDTO = dao.getMemberDTO(userId, userPwd);

dao.close();

if (memberDTO.getId() != null){
	// 아이디 체크 성공
	session.setAttribute("UserId", memberDTO.getId());
	session.setAttribute("UserName", memberDTO.getName());
	response.sendRedirect("LoginForm.jsp");
}else{
	// 실패
	request.setAttribute("LoginErrMsg", "로그인 실패");
	request.getRequestDispatcher("LoginForm.jsp").forward(request, response);
}

%>

 

DB에 있는 정보를 읽어와 아이디와 일치하는지 확인한 후 이름(별칭) 정보로 회원 로그인 성공 유무를 알려준다.

 

 

Logout.jsp

<%@ page language="java" contentType="text/html; charset=UTF-8"
	pageEncoding="UTF-8"%>
<%
session.removeAttribute("UserId");
session.removeAttribute("UserName");

// session.invalidate(); 모든 값을 삭제

response.sendRedirect("LoginForm.jsp");
%>

 

Link.jsp

<%@ page language="java" contentType="text/html; charset=UTF-8"
	pageEncoding="UTF-8"%>
<table border="1" width="90%">
	<tr>
		<td align = "center">
		<%if (session.getAttribute("UserId") == null) { %>
		<a href = "../Session/LoginForm.jsp">로그인</a>
		<% }else{ %>
		<a href = "../Session/Logout.jsp">로그아웃</a>
		<% } %>
		
		&nbsp; &nbsp; &nbsp;
		<a href = "">게시판(페이징 x)</a>
		&nbsp; &nbsp; &nbsp;
		<a href = "">게시판(페이징 o)</a>
		</td>
	</tr>
</table>

 

헤더와 연결해 현재 로그인, 로그아웃 상태를 알 수 있게 해준다.

 

 

 

🍀 액션태그

 

3가지.

  1. 페이지 이동기능
  2. 페이지 읽고 쓰기
  3. 페이지 포함

 

include, forward, bean

 

✔︎ include → 페이지 포함

지시자 include 액션 include
페이지가 복사되어 하나의 파일로 포함된다 필요할 때 페이지를 포함해 처리
종속되어 움직인다 처리 후에는 연결이 끊긴다.

 

✔︎ forward → 페이지 이동

주소값이 머물러 있다.

 

 

✔︎ bean == 자바에서는 DTO , vo라고 부른다.

파라메터를 효율적으로 받기 위해서

// 빈(Bean) 객체 : DTO클래스
// 반드시 기본형 생성자가 정의되어 있어야 한다.
// 멤버는 private 해야한다.
// getter / setter를 정의한다.

// import 를 한번에 사용할 수 있다.



<jsp:useBean id="" class=""></jsp:useBean> 

<jsp:setProperty property="" name=""/> // 값 보내기
<jsp:getProperty property="" name=""/> // 값 읽기
name에는 id 값 입력 

위 세개는 한 세트처럼 쓰여진다.

 

UseBeanForm.jsp

<%@ page language="java" contentType="text/html; charset=UTF-8"
    pageEncoding="UTF-8"%>
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Insert title here</title>
</head>
<body>
    <h2>액션 태그로 폼값 한 번에 받기</h2> 
    <form method="post" action="UseBeanAction.jsp"> 
        이름 : <input type="text" name="name" /> <br /> 
        나이 : <input type="text" name="age" /> <br /> 
        <input type="submit" value="폼값 전송" />
    </form>
</body>

 

UseBeanAction.jsp

<%@ page language="java" contentType="text/html; charset=UTF-8"
    pageEncoding="UTF-8"%>
    <%-- <%
    String name = request.getParameter("name");
    String age = request.getParameter("age");
    %> --%>

<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Insert title here</title>
</head>
<body>

<%-- <jsp:useBean id="person" class="common.Person"></jsp:useBean>

<jsp:setProperty property="name" name="person" value="hong"/>

<!-- 파라매터를 직접 받을 때 -->
<jsp:setProperty property="name" name="person" param="hong"/>  
이름 : <jsp:getProperty property="name" name="person"/>

<jsp:setProperty property="age" name="person" value="10"/>

<!-- 파라매터를 직접 받을 때 -->
<jsp:setProperty property="age" name="person" param="10"/>
나이 : <jsp:getProperty property="age" name="person"/> --%>


<!-- 파라매터를 한번에 넣을 때 -->
<jsp:useBean id="person" class="common.Person"></jsp:useBean>
<!-- 파라매터 이름과 setter 메소드 이름이 같고, 개수가 같을 때 * 로 한번에 읽어올 수 있다. -->
<jsp:setProperty property= "*" name="person" />

<!-- 넘어온 데이터를 get메소드로 읽는다. -->
이름 : <jsp:getProperty property="name" name="person"/>
<br>
나이 : <jsp:getProperty property="age" name="person"/>

</body>
</html>

 

Person.java

package common;

import lombok.AllArgsConstructor;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;

@Getter
@Setter
@NoArgsConstructor
@AllArgsConstructor
public class Person {
	
	
	// 빈(Bean) 객체 : DTO클래스
	// 반드시 기본형 생성자가 정의되어 있어야 한다.
	// 멤버는 private 해야한다.
	// getter / setter를 정의한다.
	
	private String name;
	private int age;
	public String getName() {
		return name;
	}

}

 

+web.xml 인코딩 처리로 한글 깨짐현상을 방지할 수 있다.

<filter>
    <filter-name>SetCharEncoding</filter-name>
    <filter-class>org.apache.catalina.filters.SetCharacterEncodingFilter</filter-class>
      <init-param>
        <param-name>encoding</param-name>
        <param-value>utf-8</param-value>
      </init-param>
  </filter>
  <filter-mapping>
    <filter-name>SetCharEncoding</filter-name>
    <url-pattern>/*</url-pattern>
  </filter-mapping>

 

 

 

 

🎉 게시판 만들기

이 코드는 통으로 그냥 외워라.

 

프로그램 만드는 두가지 방식 : 모델 1방식, 모델 2방식

모델 1방식 → 코드 다 한 곳에

모델 2방식 → 코드를 여러 곳에 분산, 로직 분리 (유지보수에 유리)

 

게시판 만들기는 모델 1방식으로 코딩 후 모델 2방식으로 바꿔본다.

오늘까지 만들었던 순서)

  1. SQL 디벨로퍼에서 테이블 만들고 데이터넣기, 커밋
  2. 그 다음으로 자바파일로 DTO, DAO 만들기
  3. 목록보기 화면 List.jsp
  4. 로그인 기능을 구현하며 만들었던 폼에 링크걸기

 

SQL

create table board (
num number primary key,
title varchar2(200) not null,
content varchar2(2000) not null,
id varchar2(10) not null,
postdate date DEFAULT sysdate not null,
visitcount number(6)
);

alter table board
add constraint board_mem_fk foreign key (id)
REFERENCES member(id);

-- 시퀀스 객체
create sequence seq_board_num
increment by 1
start with 1
minvalue 1;

insert into board (num, title, content, id, postdate, visitcount) 
   values (seq_board_num.nextval, '제목1입니다', '내용1입니다', 'musthave', sysdate, 0);

commit;

insert into board
   values (seq_board_num.nextval, '지금은 봄입니다', '봄의왈츠', 'musthave', sysdate, 0);
insert into board
   values (seq_board_num.nextval, '지금은 여름입니다', '여름향기', 'musthave', sysdate, 0);
insert into board
   values (seq_board_num.nextval, '지금은 가을입니다', '가을동화', 'musthave', sysdate, 0);
insert into board
   values (seq_board_num.nextval, '지금은 겨울입니다', '겨울연가', 'musthave', sysdate, 0);      

commit;

 

BoardDTO.java

package model1.board;


import lombok.Getter;
import lombok.Setter;

@Getter
@Setter
public class BoardDTO {
private String num;
private String title;
private String content;
private String id;
private java.sql.Date postDate;
private String visitcount;
private String name; // 로그인했을 떄 필요한 이름
}

 

BoardDAO.java

package model1.board;

import common.JDBConnect;
import jakarta.servlet.ServletContext;

public class BoardDAO extends JDBConnect{

	public BoardDAO(ServletContext application) {
		super(application);
	}
	
	// 검색 조건에 맞는 게시물의 개수를 반환합니다.
	   
	// 검색 조건에 맞는 게시물 목록을 반환합니다.
	   
	// 검색 조건에 맞는 게시물 목록을 반환합니다(페이징 기능 지원).
	   
	// 게시글 데이터를 받아 DB에 추가합니다.
	   
	// 지정한 게시물을 찾아 내용을 반환합니다.
	   
	// 지정한 게시물의 조회수를 1 증가시킵니다.
	   
	// 지정한 게시물을 수정합니다.
	   
	// 지정한 게시물을 삭제합니다.

	

}

 

목록보기 화면 List.jsp

<%@ page language="java" contentType="text/html; charset=UTF-8"
	pageEncoding="UTF-8"%>
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Insert title here</title>
</head>
<body>
	<jsp:include page="../Common/Link.jsp" />
	<form method="get">
		<table border="1" width="90%">
			<tr>
				<td align="center"><select name="searchField">
						<option value="title">제목</option>
						<option value="content">내용</option>
				</select> 
				<input type="text" name="searchWord" /> 
				<input type="submit" name="검색하기" /></td>
			</tr>
		</table>
	</form>
</body>
</html>

 

로그인 기능을 구현하며 만들었던 폼에 링크걸기 Link.jsp

<%@ page language="java" contentType="text/html; charset=UTF-8"
	pageEncoding="UTF-8"%>
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Insert title here</title>
</head>
<body>
	<jsp:include page="../Common/Link.jsp" />
	<form method="get">
		<table border="1" width="90%">
			<tr>
				<td align="center"><select name="searchField">
						<option value="title">제목</option>
						<option value="content">내용</option>
				</select> 
				<input type="text" name="searchWord" /> 
				<input type="submit" name="검색하기" /></td>
			</tr>
		</table>
	</form>
</body>
</html>

 

오류 메세지 :

several ports (8005, 9999) required by tomcat v9.0 server at localhost are already in use. the server may already be running in another process, or a system process may be using the port. to start this server you will need to stop the other process or change the port number(s).

 

Mac ) 해결방법 :

터미널 창에서

//포트번호로 찾기
lsof -i :[포트번호]

//프로세스 종료하기
kill -9 [PID]

 

윈도우 ) 해결방법 :

명령 프롬포트에서

netstat -p tcp -ano 치고 8080, 9999 포트 사용 중인 pid 찾아서

taskkill /f /pid (번호) 치고 강제 종료

번호 칠 때는 괄호 없어야 한다.

 

 

맥은 윈도우와 명령어가 다르다! 

그리고,, 맥에서는 톰캣은 꼭 종료 시 서버를 잘 끄고, 실행했던 서버도 다 죽여야 다음에 저런 오류가 안뜬다.

이클립스 자체도 별로지만 맥이 이클립스를 좀 예민하게 받아들이는 것 같다..

대부분의 페이지가 DB와 연동 (이메일 등)

매번 매개변수를 초기화 해야하는 부담 → 생성자를 오버로딩 하는 방식으로 해결할 수 있다

가장 모듈화가 잘 된 방식이 이 방식.

 

코딩을 할 때는 모듈화가 중요하다. 잘 만들어진 모듈 하나는 열 개발자 안 부럽다.

→ 리팩토링이라고도 한다. 좀 더 코드를 최적화할 수 있는 형태를 생각, 모듈화 하듯

 

❗️앞으로 두고두고 중요한 개념이다

jsp의 내장객체인 application 을 읽을 수 있다면 매개변수는 한번에 읽어올 수 있다.

application 타입 → ServletContext application

 

JDBConnect.java

	public JDBConnect(ServletContext application) {
		String driver = application.getInitParameter("OracleDriver"); 
		
		try {
			Class.forName(driver);
			
			String url = application.getInitParameter("OracleURL");
			String id = application.getInitParameter("OracleId");
			String pwd = application.getInitParameter("OraclePwd");
			
			con = DriverManager.getConnection(url, id, pwd);
			
			System.out.println("DB 연결 성공(인수 생성자 2)");
			
		} catch (SQLException e) {
			e.printStackTrace();
		} catch (ClassNotFoundException e) {
			e.printStackTrace();
		}
	}

 

JDBCTest2.jsp

	<%
	JDBConnect jdbc3 = new JDBConnect(application);
	jdbc3.close();
	%>

테스트할 페이지에서는 매개변수인 application을 한번 더 호출해주면 된다.

 

 

 

전체코드)

JDBConnect.java

package common;

import java.sql.Statement;

import jakarta.servlet.ServletContext;

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

public class JDBConnect {
	// Connection,
	// statement (쿼리문 쓸 때 필요한 객체, 많이 안쓴다.),
	// PreparedStatement (쿼리문 사용할 떄 필요한 객체 많이쓴다. 쿼리문을 쓸 때 융통성이 좋다.),
	// ResultSet (실행된 쿼리문의 결과를 받아올 떄 필요한 객체)

	public Connection con;
	public Statement stmt;
	public PreparedStatement psmt;
	public ResultSet rs;

	public JDBConnect() {
		String driver = "oracle.jdbc.driver.OracleDriver";
		String url = "jdbc:oracle:thin:@localhost:1521:XE";

		try {
			Class.forName(driver);
			con = DriverManager.getConnection(url, "musthave", "1234");

			System.out.println("DB 연결 성공 (기본 생성자)");

		} catch (SQLException e) {
			e.printStackTrace();
		} catch (ClassNotFoundException e) {
			e.printStackTrace();
		}

	}

	// 두번째 생성자 방식 - 매개변수로 개인 정보를 숨김
	public JDBConnect(String driver, String url, String id, String pwd) {
		try {
			// JDBC 드라이버 로드
			Class.forName(driver);

			// DB에 연결
			con = DriverManager.getConnection(url, id, pwd);

			System.out.println("DB 연결 성공(인수 생성자 1)");

		} catch (SQLException e) {
			e.printStackTrace();

		} catch (ClassNotFoundException e) {
			e.printStackTrace();
		}

	}
	
	public JDBConnect(ServletContext application) {
		String driver = application.getInitParameter("OracleDriver"); 
		
		try {
			Class.forName(driver);
			
			String url = application.getInitParameter("OracleURL");
			String id = application.getInitParameter("OracleId");
			String pwd = application.getInitParameter("OraclePwd");
			
			con = DriverManager.getConnection(url, id, pwd);
			
			System.out.println("DB 연결 성공(인수 생성자 2)");
			
		} catch (SQLException e) {
			e.printStackTrace();
		} catch (ClassNotFoundException e) {
			e.printStackTrace();
		}
	}

	public void close() {

		try {
			if (con != null) {
				con.close();
			}
		} catch (Exception e) {

		}
	}
}

 

JDBCTest2.jsp

<%@ page language="java" contentType="text/html; charset=UTF-8"
	pageEncoding="UTF-8"%>
<%@ page import="common.JDBConnect"%>

<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Insert title here</title>
</head>
<body>

	<h2>JDBCTest1</h2>

	<%
	JDBConnect jdbc1 = new JDBConnect();
	jdbc1.close();
	%>

	<h2>JDBCTest2</h2>

	<%
	String driver = application.getInitParameter("OracleDriver");
	String url = application.getInitParameter("OracleURL");
	String id = application.getInitParameter("OracleId");
	String pwd = application.getInitParameter("OraclePwd");

	JDBConnect jdbc2 = new JDBConnect(driver,url,id,pwd);
	jdbc2.close();
	%>
	
	<h2>JDBCTest3</h2>
	<%
	JDBConnect jdbc3 = new JDBConnect(application);
	jdbc3.close();
	%>

</body>
</html>

 

기존 방식은 접속할때마다 접속할 수 있는 입구를 만들고, 접속을 끊으면 입구를 없애는 방식이었다.

요청이 있을 때마다 입구를 만드는 방식.

 

 

🍀 커넥션 풀 방식

미리 입구를 만들어 놓는다. 용량 낭비가 조금 있지만 바로 들어와서 이용할 수 있다.

커넥션 풀 방식에서는 web.xml 역할을 하는 것이 server.xml (서술배포자로의 역할을 한다)

server.xml 수정 - 구문 추가

 <Resource auth="Container"
              driverClassName="oracle.jdbc.OracleDriver"
              type="javax.sql.DataSource" 
              initialSize="0" // 초기 사이즈 (몇개 만들건지)
              minIdle="5" 
              maxTotal="20" // 최대 개수
              maxIdle="20"
              maxWaitMillis="5000" // 대기시간 5초 
              url="jdbc:oracle:thin:@localhost:1521:xe" 
              name="dbcp_myoracle" // 이름 속성에 들어간 값이 정해진 건 아니지만 프로그램에서 이름을 가져다 쓴다. 
              username="musthave"
              password="1234" />

이 정보가 Pull 방식을 쓸 수 있게 해주는 매개변수같이 동작한다.

 

❗️필수 구성 항목

  • driverClassName="oracle.jdbc.OracleDriver"
  • type="javax.sql.DataSource"
  • url="jdbc:oracle:thin:@localhost:1521:xe"
  • name="dbcp_myoracle" // 이름 속성에 들어간 값이 정해진 건 아니지만 프로그램에서 이름을 가져다 쓴다.
  • username="musthave"
  • password="1234"

 

name="dbcp_myoracle" // 이름 속성에 들어간 값이 정해진 건 아니지만 프로그램에서 이름을 가져다 쓰기 때문에 어떤 값이 있는지는 알아야한다. 떄문에 의미있는 이름을 짓는 것이 좋다.

 

context.xml 수정 - 구문 추가

<ResourceLink global="dbcp_myoracle" name="dbcp_myoracle" 
                  type="javax.sql.DataSource"/>

여기까지하고 톰켓서버 restart

 

 

DBConnPool.java

package common;

import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.Statement;

import javax.naming.Context;
import javax.naming.InitialContext;
import javax.sql.DataSource;

public class DBConnPool {
	public Connection con;
	public Statement stmt;
	public PreparedStatement psmt;
	public ResultSet rs;

	public DBConnPool() {

		// 커넥션 풀 방식

		try {
			// 기본 세팅
			Context initCtx = new InitialContext();
			Context ctx = (Context) initCtx.lookup("java:comp/env");
			DataSource source = (DataSource) ctx.lookup("dbcp_myoracle");

			con = source.getConnection(); // DB를 연결해주는 메서드

			System.out.println("DB 커넥션 풀 연결 성공");

		} catch (Exception e) {

		}
	}

	public void close() {

		try {
			if (con != null) {
				con.close();
			}
		} catch (Exception e) {

		}
	}
}

 

JDBCTest2.jsp

<%@ page language="java" contentType="text/html; charset=UTF-8"
	pageEncoding="UTF-8"%>
<%@ page import="common.JDBConnect"%>
<%@ page import= "common.DBConnPool" %>

<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Insert title here</title>
</head>
<body>

	<h2>JDBCTest1</h2>

	<%
	JDBConnect jdbc1 = new JDBConnect();
	jdbc1.close();
	%>

	<h2>JDBCTest2</h2>

	<%
	String driver = application.getInitParameter("OracleDriver");
	String url = application.getInitParameter("OracleURL");
	String id = application.getInitParameter("OracleId");
	String pwd = application.getInitParameter("OraclePwd");

	JDBConnect jdbc2 = new JDBConnect(driver,url,id,pwd);
	jdbc2.close();
	%>
	
	<h2>JDBCTest3</h2>
	<%
	JDBConnect jdbc3 = new JDBConnect(application);
	jdbc3.close();
	%>
	
	<h2>JDBCTest4</h2>
	<%
	DBConnPool pool = new DBConnPool();
	pool.close();
	%>

</body>
</html>

 

연결 테스트를 해보면 연결이 된 것을 확인할 수 있다.

 

 

🍀 DB 데이터를 서버에서 집어넣어보기

 

ExeUpdate.jsp

<%@page import="java.sql.PreparedStatement"%>
<%@ page language="java" contentType="text/html; charset=UTF-8"
	pageEncoding="UTF-8"%>
<%@ page import="common.JDBConnect"%>
<%@ page import="java.sql.PreparedStatement"%> 

<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Insert title here</title>
</head>
<body>
	<!-- 회원추가 -->
	<h2>회원 추가 테스트</h2>

	<%
	JDBConnect jdbc = new JDBConnect();

	String id = "test1";
	String pass = "1111";
	String name = "테스트1회원";

	// varchar2 타입 문자 처리 -> '"내용"' 코드가 까다로워 이렇게 쓰지 않는다
	// String sql = "insert into member (id, pass, name) values ('" + id + "', '" + pass + "', '" + name + "')";

	// PreparedStatement 방식 -> 컬럼의 개수에 맞게 ? 를 넣어준다. 
	// PreparedStatement 쿼리문을 실행하게 하는 메소드, 유연성이 있다. 
	String sql = "insert into member (id, pass, name) values (?, ?, ?)";

	PreparedStatement psmt = jdbc.con.prepareStatement(sql);
	// psmt.setString(순서, 정해주고 싶은 값);
	psmt.setString(1, id);  
	psmt.setString(2, pass);
	psmt.setString(3, name);
	
	// PreparedStatement 로 쿼리문 실행
	// executeQuery() -> select
	// executeUpdate() -> Insert, update, delete
	// 				   -> 반환값을 가진다. 0 or 0 보다 큰 값
	
	int result = psmt.executeUpdate();
	
	if (result > 0) {
		out.print(result + "행이 입력되었습니다.");
	} else {
		out.print("입력 실패");
	}
	
	jdbc.close(); // 사용중인 객체 close()

%>

</body>
</html>

 

JDBConnect.java

package common;

import java.sql.Statement;

import jakarta.servlet.ServletContext;

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

public class JDBConnect {
	// Connection,
	// statement (쿼리문 쓸 때 필요한 객체, 많이 안쓴다.),
	// PreparedStatement (쿼리문 사용할 떄 필요한 객체 많이쓴다. 쿼리문을 쓸 때 융통성이 좋다.),
	// ResultSet (실행된 쿼리문의 결과를 받아올 떄 필요한 객체)

	public Connection con;
	public Statement stmt;
	public PreparedStatement psmt;
	public ResultSet rs;

	public JDBConnect() {
		String driver = "oracle.jdbc.driver.OracleDriver";
		String url = "jdbc:oracle:thin:@localhost:1521:XE";

		try {
			Class.forName(driver);
			con = DriverManager.getConnection(url, "musthave", "1234");

			System.out.println("DB 연결 성공 (기본 생성자)");

		} catch (SQLException e) {
			e.printStackTrace();
		} catch (ClassNotFoundException e) {
			e.printStackTrace();
		}

	}

	// 두번째 생성자 방식 - 매개변수로 개인 정보를 숨김
	public JDBConnect(String driver, String url, String id, String pwd) {
		try {
			// JDBC 드라이버 로드
			Class.forName(driver);

			// DB에 연결
			con = DriverManager.getConnection(url, id, pwd);

			System.out.println("DB 연결 성공(인수 생성자 1)");

		} catch (SQLException e) {
			e.printStackTrace();

		} catch (ClassNotFoundException e) {
			e.printStackTrace();
		}

	}
	
	public JDBConnect(ServletContext application) {
		String driver = application.getInitParameter("OracleDriver"); 
		
		try {
			Class.forName(driver);
			
			String url = application.getInitParameter("OracleURL");
			String id = application.getInitParameter("OracleId");
			String pwd = application.getInitParameter("OraclePwd");
			
			con = DriverManager.getConnection(url, id, pwd);
			
			System.out.println("DB 연결 성공(인수 생성자 2)");
			
		} catch (SQLException e) {
			e.printStackTrace();
		} catch (ClassNotFoundException e) {
			e.printStackTrace();
		}
	}

	public void close() {

		try {
			if (stmt != null) {
				stmt.close();
			}
			if (rs != null) {
				rs.close();
			}
			if (psmt != null) {
				psmt.close();
			}
			if (con != null) {
				con.close();
			}
		} catch (Exception e) {

		}
	}
}

다른 객체들을 사용할때도 다 null 이 아니면 close() 될 수 있도록 수정했다.

 

DB에도 데이터가 잘 들어간 것을 확인할 수 있다.

 

 

Insert, update, delete 비슷한 방식 처리

PreparedStatement 사용

 

select는 다른 방식

PreparedStatement 사용이 일반적이지만,

여기 예시에서는 Statement를 사용해보겠다.

 

 

🌀 select 해보기

 

ExeQuery.jsp

<%@ page language="java" contentType="text/html; charset=UTF-8"
	pageEncoding="UTF-8"%>
<%@ page import="common.JDBConnect"%>
<%@ page import="java.sql.Statement"%>
<%@ page import="java.sql.ResultSet"%>

<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Insert title here</title>
</head>
<body>

	<h2>회원 목록 조회 테스트</h2>
	<%
	JDBConnect jdbc = new JDBConnect();

	String sql = "select * from member";

	Statement stmt = jdbc.con.createStatement();

	// Statement 로 쿼리문 실행
	// executeQuery() -> select
	// executeUpdate() -> Insert, update, delete
	// 				   -> 반환값을 가진다. 0 or 0 보다 큰 값

	// 실행할 때 쿼리실행문을 넣는다.
	// ResultSet 래코드의 개수만큼 참조 -> 그 안에는 컬럼 단위 
	// next() 메서드가 주소값을 참조하며 이동해 레코드를 읽어주는 역할 
	// 참조 가능한 레코드가 있을 땐 next() -> true 반환 
	// 없다면 flase 반환 
	// 그렇다면 false를 반환할때까지 while 문으로 돌리기
	ResultSet rs = stmt.executeQuery(sql);

	while (rs.next()) {
		String id = rs.getString("id");
		String pw = rs.getString("pass");
		String name = rs.getString("name");
		// java.sql.Date regidate = rs.getDate("regidate");
		String regidate = rs.getString("regidate");

		out.print(String.format("%s %s %s %s", id, pw, name, regidate) + "</br>");

	}

	jdbc.close();
	%>

</body>
</html>

 

  • executeQuery() -> select
  •                               → ResultSet : 조회된 결과

 

  • executeUpdate() -> Insert, update, delete
  •                                    -> 반환값을 가진다. 0 or 0 보다 큰 값

 

  • getInt(), getFloat(), getString(), getDate() 로 데이터를 받는다.

 

 

날짜 데이터 가져오는 방식)

날짜 연산이 필요할 땐 아래와 같이,

java.sql.Date regidate = rs.getDate("regidate");

그냥 날짜 데이터만 가지고 올 땐 아래와 같은 형식으로 사용한다.

String regidate = rs.getString("regidate");

 

 


 

🍀 회원가입 폼을 만들어서 실제 테이블에 저장해보기

 

 

🌀 회원가입 → 브라우저 화면에서 값을 입력해서 집어넣기

 

Member.jsp - 회원가입 페이지 만들기

<%@ page language="java" contentType="text/html; charset=UTF-8"
    pageEncoding="UTF-8"%>
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Insert title here</title>
</head>

<body>

<form action="MemberProcess.jsp" method = "post">

<label>아이디 : </label>
<input type = "text" name = "id">
<br>
<label>비밀번호 : </label>
<input type = "password" name = "pwd">
<br> 
<label>이름 : </label>
<input type = "text" name = "name">
<br>
<input type = "submit" value = "회원가입">

</form>

</body>
</html>

 

MemberProcess.jsp - 회원가입된 정보 DB에 집어 넣기

<%@page import="utils.JSFunction"%>
<%@page import="java.sql.PreparedStatement"%>
<%@page import="common.JDBConnect"%>
<%@ page language="java" contentType="text/html; charset=UTF-8"
	pageEncoding="UTF-8"%>

<%
String id = request.getParameter("id");
String pass = request.getParameter("pwd");
String name = request.getParameter("name");

JDBConnect jdbc = new JDBConnect();

String sql = "insert into member (id, pass, name) values (?, ?, ?)";

PreparedStatement psmt = jdbc.con.prepareStatement(sql);

psmt.setString(1, id);
psmt.setString(2, pass);
psmt.setString(3, name);

int result = psmt.executeUpdate();

if (result > 0){
	JSFunction.alertLocation("데이터 삽입 성공", "MemberList.jsp", out);
}

%>

회원가입이 완료되어 데이터가 DB에 들어가고, MemberList.jsp 페이지로 넘어간 모습이다.

 

 

🌀 회원 목록 보기

 

MemberList.jsp

<%@page import="java.sql.ResultSet"%>
<%@ page language="java" contentType="text/html; charset=UTF-8"
    pageEncoding="UTF-8"%>
<%@page import="java.sql.PreparedStatement"%>
<%@page import="common.JDBConnect"%>
    
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Insert title here</title>
</head>
<body>
<h2>회원 목록 보기</h2>
<%
JDBConnect jdbc = new JDBConnect();

String sql = "select * from member"; 

PreparedStatement psmt = jdbc.con.prepareStatement(sql);
ResultSet rs = psmt.executeQuery();

while (rs.next()){
	String id = rs.getString("id");
	String pass = rs.getString("pass");
	String name = rs.getString("name");
	String regidate = rs.getString("regidate");
	
	out.print(String.format("%s %s %s %s", id, pass, name, regidate) + "</br>");
}
%>
</body>
</html>

데이터가 들어간 것을 확인할 수 있다.

  • [ jsp ) .metadate 파일 보는 법, 숨겨진 파일 보는 법 : shift + command + . ]
  • jsp는 파일 저장 방식이 다르다고 하는데 .metadate 파일 안에서 내가 만든 파일은 어디서 볼 수 있나
  • jsp workspace 에 저장되어 있는 파일은 뭐지..!?

→ getRealPath 실제 저장된 파일 경로

 

톰켓에서 실제 데이터를 저장하고 있는 곳

/jsp-workspace/.metadata/.plugins/org.eclipse.wst.server.core/tmp0/wtpwebapps/firstjsp/

 

  • 쿠키는 톰캣서버에서 제공하는 객체라 톰캣서버를 통해서만 수집하는 건가???

→ 그게 아니다. 어디서든 쿠키를 설정할 수 있다! 회원가입, 결제창 등 고객과 상호작용을 위한 페이지마다 자바와 같은 언어로 쿠키 기능을 넣으면 된다. 톰캣으로만 쿠키 기능을 구현할 수 있는 것이 아님. 쿠키는 하나의 기능! 자바스크립트를 통해 쿠키를 읽을 수도 있다.

  • jsp가 일단 잘 설치된게 맞는지!! 맞는것 같다.. 근데 자바 버전이 다른 것 같은데 이상 없는 건지 모르겠음 나중에 문제가 될지..? (뭐 까는 것마다 자바버전,경로 설정하다보니)

 

  • wep.xml 파일의 역할

→ 웹 애플리케이션에서 중요한 역할을 하는 배포 서술자 <servlet>, <servlet-mapping>

→ 서블릿 설정, 요청과 응답을 처리하기 전에 특정 로직을 실행하는 필터를 정의 <filter>, <filter-mapping>

→ 어플리케이션의 생명주기 이벤트를 처리하는 리스너를 정의 <listener>

→ 웹 애플리케이션 초기화 매개변수 설정 <context-param>

→ 에러 페이지 설정 <error-page>

→ 보안설정 <security-constraint>, <login-config> …

→ 파일 확장자와 MIME 타입 매핑 <mime-mapping>

( MIME 타입은 인터넷을 통해 전송되는 파일의 형식을 지정하는 표준 - text/html , image/jpeg , video/mp4 같은 것 )

  • 팝업창 구현한 예제 왜 닫기 버튼 안 먹히는지 (gpt도 모름..)
  • html + css 할때 그리드 플랙스 다 같이 써도 되는지
  • 오라클 SQL 진짜.. 부모와 자식 구분이 안됨. <- 상대적인 거고 어떤 의도로 테이블을 만드느냐에 따라 어디선 이게 부모, 어디선 자식이 될 수 있는 거였음. 절대적인 건 없다. 

 

 

 

복습할 것)

제이쿼리 선택자 구성 방식

자바 컬렉션

 

 

🍪 Cookie

공지사항 팝업을 만들어보기

  • ‘하루동안 열지 않기’ 체크 박스, 닫기 버튼 구현
  • 제이쿼리 적용

 

Ajax → 자바스크립트를 통해 서버와 통신할 수 있게 하는 개념

$.ajax({
url : './PopupCookie.jsp',
type : 'get',
data : {inactiveToday : chkVal}, // 파라메터값 (키 : value) 형식
dataType : "text", // 어떤 형태로 데이터를 받을 건지 (쿠키는 대부분 text형식으로 받음)
success : function(resData) { // function안에 있는 매개변수의 이름은 어떤 것이든 상관없다

	if (resData != '') location.reload();

	}
});

 

url - 이동할 페이지이다. 이동한 페이지에서 데이터를 처리하고 다시 현페이지로 돌아온다. 필요한 경우 데이터를 전달할 수 있다.

type - 전달 방식을 의미한다. GET/POST가 있다.

data - 데이터 처리를 할 페이지에게 전달할 정도이다. 내가 전달하고 싶은 파라메터값. Array 형식일 때 위와 같이 적으면 된다.

dataType - 전달받는 인자의 형식.

success - 데이터 처리가 성공했을 경우 해당 함수(함수가 아니어도 되는듯 하다)를 실행한다. json으로 처리할 경우 데이터를 처리하는 페이지에서 전달해주는 값이 인자로 받아진다.

 

 

공지사항 팝업 창

 

PopupMain.jsp

<%@ page language="java" contentType="text/html; charset=UTF-8"
	pageEncoding="UTF-8"%>
	
	<%
	
	String popupMode = "on";
	
	Cookie[] cookies = request.getCookies();
	if (cookies != null){
		for (Cookie c : cookies){
			String cookieName = c.getName();
			String cookieValue = c.getValue();
			
			if (cookieName.equals("PopupClose")){
				popupMode = cookieValue;
			}
		}
	}
	%>
	
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Insert title here</title>

<style>
div#popup {
	position: absolute;
	top: 100px;
	left: 50px;
	color: yellow;
	width: 270px;
	height: 100px;
	background-color: gray;
    display: <% if (popupMode.equals("on")) { %>block<% } else { %>none<% } %>;
}

div#popup>div {
	position: relative;
	background-color: #ffffff;
	top: 0px;
	border: 1px solid gray;
	padding: 10px;
	color: black;
}
</style>

<script src="https://ajax.googleapis.com/ajax/libs/jquery/3.7.1/jquery.min.js"></script>
<script>
/* 닫기 버튼 */
$(function(){
	$("#closeBtn").click(function(){
		$("#popup").hide();
		
		/* 하루동안 열지 않음 버튼 체크 유무 알기 */
		/* 사용자 pc에서 그 정보를 보관하고 있어야 함 -> 그것이 쿠키 */
		/* 어떤 정보를 서버에서 관리할지, 쿠키로 관리할지 판단해야한다. */
		/* 체크가 되어있으면 1 안되어 있으면 null값 */
		let chkVal = $("input:checkbox[id=inactivetoday]:checked").val();
		
		
		// ajax
		// 서버에 요청
		// 페이지 이동이 일어나지 않는다. 새로고침이 일어남, main 화면에서 처리
		$.ajax({
			url : './PopupCookie.jsp', 
			type : 'get',
			data : {inactivetoday : chkVal},
			dataType : "text",
			success : function(resData) { // resData => "쿠키 : 하루동안 열지 않음"
				if (resData != ''){
					console.log(resData);  
					location.reload(); 
				}
			}
		});
	});
});
    
</script>

</head>
<body>

<%
// 태그 자체를 실행문으로 전체 처리
if(popupMode.equals("on")) {
%>

	<div id="popup">
		<h2 align="center">공지사항 팝업입니다.</h2>
		<div align="right">
			<form name="popFrm">
				<input type="checkbox" id="inactivetoday" value="1" /> 하루 동안 열지 않음
				<input type="button" value="닫기" id="closeBtn" />
			</form>
		</div>
	</div>
	<% } %>

</body>
</html>

 

PopupCookie.jsp

<%@ page language="java" contentType="text/html; charset=UTF-8"
	pageEncoding="UTF-8"%>

<%
    String chkval = request.getParameter("inactivetoday");
    
    if (chkval != null && chkval.equals("1")){
    	Cookie cookie = new Cookie("PopupClose", "off");
    	cookie.setPath("/");
    	cookie.setMaxAge(3600 * 24);
    	response.addCookie(cookie);
    	// ajax 통신을 통해 요청된 text 타입 데이터를 보내주는 역할 (여기서의 역할)
    	out.println("쿠키 : 하루 동안 열지 않음");
    } else {
    	out.println("쿠키 설정 안됨");
    }
    %>

 

 

 

자동로그인 기능 구현해보기

+쿠키를 적용할 만한 기능들 검색해보기

 

페이지 이동 방식 → jsp, js에서 제공 ( js - location 객체 )

utils 패키지에 페이지 이동 방식에 대한 내용 정의

history, location 객체가 제공해주기 때문에 이를 이용할 것이다.

페이지를 이동하는 과정에서 페이지 이동 이유, 정보, 문제점 등을 메세지 창을 통해 전달

js alert 창을 이용해 제공.

 

out.print("<h1 style = 'color : red'>로그인 실패</h1>");

기능을 그대로 수행해준다.

 

IdsaveProcess 페이지에서는 아이디 저장하기 체크박스에 값이 있으면 쿠키를 만들어주고

체크가 안된 상태로 넘어온다면 쿠키를 만들지 않는다

 

가능하면 기능은 분리해서 사용

java 파일로 만든 기능을 jsp 파일에서 사용한다.

 

response 객체의 타입 → HttpServletResponse response

request 객체의 타입 → HttpServletRequest request

아이디 저장 체크 후 로그인에 성공하면 저렇게 아이디가 저장되고 쿠키가 생기는 것을 확인할 수 있다.

아이디 저장을 체크하지 않고 로그인에 성공하면 쿠키가 생기지 않는다.

그런데 이렇게 하면 뒤로가기를 했을 때 사용자가 입력했던 정보들이 다 노출된다.

이걸 어떻게 해결하지

뒤로 갔을 때 새로고침이 되는 함수를 만들어서.. (?)

 

페이지 뒤로 가기, 앞으로 가기 경로를 결정해주는 JSFunction.java 파일

package utils;

import jakarta.servlet.jsp.JspWriter;

public class JSFunction {
	// location.href = "페이지 경로" -> 특정페이지 지정
	// history.back(), history.forward() -> 뒤로가기, 앞으로 가기

	public static void alertLocation(String msg, String url, JspWriter out) {
		try {
			String script = ""
					+ "<script>"
					+ "alert('" + msg + "');" // alert('hello')
					+ "location.href = '" + url + "'"
					+ "</script>";
			
			out.print(script);
					
		}catch(Exception e) {
			
		}
	}

	public static void alertBack(String msg, JspWriter out) {
		try {
			String script = ""
					+ "<script>"
					+ "alert('" + msg + "');"
					+ "history.back();"
					+ "</script>";
					
			out.print(script);
			
		}catch(Exception e) {
			
		}

	}
}

 

쿠키를 생성, 삭제, 정보를 확인하는 CookieManager.java 파일

package utils;

import jakarta.servlet.http.Cookie;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;

public class CookieManager {
	
	// 쿠키 생성
	public static void makeCookie(HttpServletResponse response, String cName, String cValue, int cTime) {
		Cookie cookie = new Cookie(cName ,cValue );
		cookie.setPath("/"); 
		cookie.setMaxAge(cTime); 
		response.addCookie(cookie);
	}
	
	// 쿠키 삭제
	public static void deleteCookie(HttpServletResponse response, String cName) {
		makeCookie(response, cName, "", 0);
	}
	
	// 쿠키 정보 확인
	public static String readCookie(HttpServletRequest request, String cName) {
		
		String cookieValue = "";
		
		Cookie[] cookies = request.getCookies(); 
		if (cookies != null) {
			for (Cookie c : cookies) {
				String cookieName = c.getName();
				if (cookieName.equals(cName)) {
					cookieValue = c.getValue();
				}
			}
		}
		
		return cookieValue;
	}
}

 

IdSaveMain.jsp

<%@page import="utils.CookieManager"%>
<%@ page language="java" contentType="text/html; charset=UTF-8"
	pageEncoding="UTF-8"%>
	
	<%
	String loginId = CookieManager.readCookie(request, "loginId"); // "must" 받아오기
	
	String cookieCheck = "";
	if (!loginId.equals("")){
		cookieCheck = "checked";
	}
	
	%>
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Insert title here</title>
</head>
<body>

	<form action = "IdsaveProcess.jsp" method = "post">
		아이디 : <input type="text" name="user_id" value="<%=loginId %>" /> 
		<input type="checkbox" name="save_check" value="Y" <%= cookieCheck %> /> 아이디 저장하기 
		<br /> 
		패스워드 : <input type="text" name="user_pw" /> 
		<br /> 
		<input type="submit" value="로그인하기" />
	</form>

</body>
</html>

 

IdsaveProcess.jsp

<%@ page language="java" contentType="text/html; charset=UTF-8"
    pageEncoding="UTF-8"%>
    
<%@ page import= "utils.JSFunction"%>
<%@ page import= "utils.CookieManager"%>

<%
String user_id = request.getParameter("user_id");
String user_pw = request.getParameter("user_pw");
String save_check = request.getParameter("save_check");

if (user_id.equals("must") && user_pw.equals("1234")){
	// 로그인 성공
	if (save_check != null && save_check.equals("Y")){
		// 쿠키 생성
		// 별도의 클래스를 만들어서 분리
		CookieManager.makeCookie(response, "loginId", user_id, 86400);
	}else{
		// 쿠키 삭제
		CookieManager.deleteCookie(response, "loginId");
	}
	
	JSFunction.alertLocation("로그인에 성공했습니다.", "IdSaveMain.jsp", out);
	
}else{
	// 로그인 실패 
	JSFunction.alertBack("로그인에 실패했습니다.", out);
}
%>

 

 

 

오라클데이터베이스를 활용해서 프로그램 만들기

 

(SQL)

system에서 musthave 1234 계정만들기,

권한 부여

create user musthave identified by 1234;

grant connect,resource,dba
to musthave;

 

테이블 만들기, 값 넣기 (SQL)

create table member (
id varchar2(10) not null,
pass varchar2(10) not null,
name varchar2(30) not null,
regidate date default sysdate not null,
primary key(id)
);

create table board (
num number primary key,
title varchar2(200) not null,
content varchar2(2000) not null,
id varchar2(10) not null,
postdate date DEFAULT sysdate not null,
visitcount number(6)
);

alter table board
add constraint board_mem_fk foreign key (id)
REFERENCES member(id);

-- 시퀀스 객체
create sequence seq_borad_num
increment by 1
start with 1
minvalue 1;

insert into member (id, pass, name) values ('musthave', '1234', '머스트해브');

insert into board (num, title, content, id, postdate, visitcount) 
   values (seq_borad_num.nextval, '제목1입니다', '내용1입니다', 'musthave', sysdate, 0);

commit;

 

ojdbc11 설치

모든 암세포는 맥북으로 오라클을 쓰는 순간 자라난다

 

jsp로 돌아와서

jdbcTest.jsp - 오라클와 연결이 되는지 확인하기

<%@page import="java.sql.DriverManager"%>
<%@page import="java.sql.Driver"%>
<%@page import="java.net.ConnectException"%>
<%@ page language="java" contentType="text/html; charset=UTF-8"
    pageEncoding="UTF-8"%>
    <%@page import = "java.sql.*" %>
    <%
    //1. Connection 객체 생성
    Connection conn = null;
    
    // 이 정보를 이용해서 db에 접속
    String driver ="oracle.jdbc.driver.OracleDriver";
    String url = "jdbc:oracle:thin:@localhost:1521:XE"; // orcl 로 바꾸라카는데 나는 xe로 해야함.
    
    Boolean connect = false;
    
    try {
    	// 2. DriverManager 객체 생성
    Class.forName(driver);
    	conn = DriverManager.getConnection(url, "musthave", "1234");
    	
    	connect = true;
    	
    	// db 연결 종료
    	conn.close();
    	
    }catch(Exception e) {
    	
    }
    
    %>
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Insert title here</title>
</head>
<body>
<% if (connect == true) { %>
<h1> 연결성공 </h1>
<%}else{ %>
      <h1>연결 실패</h1>
<% } %>
</body>
</html>

 

 

 

자바에서 DB와 연결하기 위해 사용하는 객체

  • Connection
  • statement (쿼리문 쓸 때 필요한 객체, 많이 안쓴다.)
  • PreparedStatement (쿼리문 사용할 떄 필요한 객체 많이쓴다. 쿼리문을 쓸 때 융통성이 좋다.)
  • ResultSet (실행된 쿼리문의 결과를 받아올 떄 필요한 객체)

 

자바스크립트에서 함수를 만들어 사용하듯 클래스를 만들어 다른 파일에서도 사용한다.

 

JDBConnect.java

package common;

import java.sql.Statement;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;

public class JDBConnect {
	// Connection,
	// statement (쿼리문 쓸 때 필요한 객체, 많이 안쓴다.),
	// PreparedStatement (쿼리문 사용할 떄 필요한 객체 많이쓴다. 쿼리문을 쓸 때 융통성이 좋다.),
	// ResultSet (실행된 쿼리문의 결과를 받아올 떄 필요한 객체)

	public Connection con;
	public Statement stmt;
	public PreparedStatement psmt;
	public ResultSet rs;

	public JDBConnect() {
		String driver = "oracle.jdbc.driver.OracleDriver";
		String url = "jdbc:oracle:thin:@localhost:1521:XE";

		try {
			Class.forName(driver);
			con = DriverManager.getConnection(url, "musthave", "1234");

			System.out.println("DB 연결 성공 (기본 생성자)");

		} catch (SQLException e) {
			e.printStackTrace();
		} catch (ClassNotFoundException e) {
			e.printStackTrace();
		}

	}

	// 두번째 생성자 방식 - 매개변수로 개인 정보를 숨김
	public JDBConnect(String driver, String url, String id, String pwd) {
		try {
			// JDBC 드라이버 로드
			Class.forName(driver);

			// DB에 연결
			con = DriverManager.getConnection(url, id, pwd);

			System.out.println("DB 연결 성공(인수 생성자 1)");

		} catch (SQLException e) {
			e.printStackTrace();

		} catch (ClassNotFoundException e) {
			e.printStackTrace();
		}

	}

	public void close() {

		try {
			if (con != null) {
				con.close();
			}
		} catch (Exception e) {

		}
	}
}

예외처리해서 꼭 종료까지 해야한다.

 

 

web.xml

<context-param> 라는 태그로 초기화 매개변수를 정의한다.

정의된 초기화 매개변수는 모든 서블릿과 JSP에서 ServletContext를 통해 접근가능하다.

이렇게 하면 어플리케이션의 설정정보를 중앙 집중적으로 관리할 수 있고 유지 보수가 용이하다.

<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="https://jakarta.ee/xml/ns/jakartaee" xsi:schemaLocation="https://jakarta.ee/xml/ns/jakartaee https://jakarta.ee/xml/ns/jakartaee/web-app_5_0.xsd" id="WebApp_ID" version="5.0">
  <display-name>firstjsp</display-name>
  <welcome-file-list>
    <welcome-file>index.html</welcome-file>
    <welcome-file>index.jsp</welcome-file>
    <welcome-file>index.htm</welcome-file>
    <welcome-file>default.html</welcome-file>
    <welcome-file>default.jsp</welcome-file>
    <welcome-file>default.htm</welcome-file>
  </welcome-file-list>
  
  <context-param>
  <param-name>INIT_PARAM</param-name>
  <param-value>web.xml에 저장한 초기화 매개변수</param-value>
  </context-param>
  
  <!-- 오라클 설정정보 -->
    
  <context-param>
  <param-name>OracleDriver</param-name>
  <param-value>oracle.jdbc.driver.OracleDriver</param-value>
  </context-param>
  
  <context-param>
     <param-name>OracleURL</param-name>
     <param-value>jdbc:oracle:thin:@localhost:1521:XE</param-value>
  </context-param>
  
  <context-param>
  <param-name>OracleId</param-name>
  <param-value>musthave</param-value>
  </context-param>
  
  <context-param>
  <param-name>OraclePwd</param-name>
  <param-value>1234</param-value>
  </context-param>
  
  
</web-app>

 

JDBCTest2.jsp 연결을 테스트하는 파일

<%@ page language="java" contentType="text/html; charset=UTF-8"
    pageEncoding="UTF-8"%>
    
    <%@ page import = "common.JDBConnect" %>
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Insert title here</title>
</head>
<body>
<h2>JDBCTest1</h2>

<%
JDBConnect jdbc1 = new JDBConnect();
jdbc1.close();
%>

<h2>JDBCTest2</h2>

<%
String driver = application.getInitParameter("OracleDriver");
String url = application.getInitParameter("OracleURL");
String id = application.getInitParameter("OracleId");
String pwd = application.getInitParameter("OraclePwd");

JDBConnect jdbc2 = new JDBConnect(driver,url,id,pwd);
jdbc2.close();
%>

</body>
</html>

연결성공 !

 

 

+ Recent posts