[JSP] 게시판 구성하기 (feat.시퀀스쿼리)
[JSP] 회원 정보 수정을 위한 파일 수정
conf > log4j 자동으로 log 파일이 만들어진다. **로그 관련 파일 ** web.xml (복붙) log4j가 어디있다 알려주면서 한글 깨지는 문제 처리 해주는 부분 **HttpE.." data-og-host="practice365.xyz" d..
practice365.xyz
오늘도 이어서 작성이 됩니다.
이제는 정보가 이동하는 과정이나 버튼을 눌렀을 때 처리하는 방법에 대한 흐름을 이해하는 정도가 됐습니다. 안 보고 다 작성 하라고 하면 못 하고.. 과제가 주어지면 그것도 쩔쩔 매면서 오류 투성이가 되는데 참 걱정이 많습니다. 흐름만 이해하면 뭘해! 혼자서 다 할 줄 알아야지! 하면서 자책을 하는 중인데 그래도 하다보면 실력이 느는 게 느껴져서 앞으로도 열심히 해보려고 해요. 이 전까지는 크게 어려운 게 없던 대신에 뿌듯함을 느끼지 못 했는데 이제는 코드를 읽으면서 이해하고 혼자 작성해보고 나면 뿌듯함을 느끼곤 합니다! 자 이제 각설하고 해보렵니다~~
게시판 생성 전 테이블 만들기
클라이언트 측으로부터 정보가 들어오면 받아서 읽어주려는 용도의 테이블이 필요
-- 게시판 생성 TABLE_BOARD
CREATE TABLE TBL_BOARD
(
BBS_SEQ NUMBER(12) NOT NULL,
USER_ID VARCHAR2(20),
BBS_NAME VARCHAR2(50),
BBS_EMAIL VARCHAR2(50),
BBS_PWD VARCHAR2(10),
BBS_TITLE VARCHAR2(150),
BBS_CONTENT CLOB,
BBS_READ_CNT NUMBER(10),
REG_DATE DATE
);
--BBS_READ_CNT NUMBER(10)
--나 또는 다른 사용자 누가 보든 한 번씩 카운트 증가 하도록 함
COMMENT ON COLUMN TBL_BOARD.BBS_SEQ IS '게시물번호(시퀀스 SEQ_BBS_SEQ)';
COMMENT ON COLUMN TBL_BOARD.USER_ID IS '사용자아이디';
COMMENT ON COLUMN TBL_BOARD.BBS_NAME IS '게시자 이름';
COMMENT ON COLUMN TBL_BOARD.BBS_EMAIL IS '게시자 이메일';
COMMENT ON COLUMN TBL_BOARD.BBS_PWD IS '게시물 비밀번호';
COMMENT ON COLUMN TBL_BOARD.BBS_TITLE IS '게시물 제목';
COMMENT ON COLUMN TBL_BOARD.BBS_CONTENT IS '게시물 내용';
COMMENT ON COLUMN TBL_BOARD.BBS_READ_CNT IS '게시물 조회수';
COMMENT ON COLUMN TBL_BOARD.REG_DATE IS '게시물 등록일';
CREATE UNIQUE INDEX XPK_BOARD ON TBL_BOARD(BBS_SEQ ASC);
--인덱스 : UNIQUE 중복값 X, 테이블의 데이터를 빨리 찾을 수 있음
CREATE SEQUENCE SEQ_BBS_SEQ
INCREMENT BY 1
MAXVALUE 999999999999
MINVALUE 1
NOCACHE;
--시퀀스 : 설정해두면 자동으로 숫자가 올라가는 것
게시물 번호에 인덱스를 지정
소팅 저장을 해서 읽어 오는 속도가 빨라지는 기능
[Oracle] 인덱스(Index) 문법과 리빌드(Rebuild) 방법
[Oracle] 인덱스(Index)의 정의 인덱스(INDEX) 데이터베이스 테이블에 있는 데이터를 좀 더 빨리 찾기 위한 색인 기술이다. 인덱스를 생성하게 되면 INDEX TABLE을 생성해 관리하게 된다. 테이블에 있는
practice365.xyz
그리고 바로 게시물 번호는 시퀀스를 지정
자동으로 지정된 숫자에서 지정된 만큼의 숫자가 순차적으로 올라갈 수 있도록 도와주는 기능
[Oracle] 시퀀스(Sequence) -- 수정중 --
시퀀스(Sequence) - 시퀀스를 사용하면 기본키 같이 순차적으로 증가하는 값을 자동으로 생성할 수 있음 --시퀀스 등록 CREATE SEQUENCE EX_SEQ INCREMENT BY 1 START WITH 50 MINVALUE 1 MAXVALUE 9999 NOCYCLE N..
practice365.xyz
여기서 잠깐!!
테이블이 만들어 졌다면 꼭 그 테이블에 대한 java 파일을 만들어야 한다!!
(하나의 게시글 정보를 담는 인스턴스를 만들기 위한 틀)
package com.icia.web.model;
< Board.java >
package com.icia.web.model;
import java.io.Serializable;
public class Board implements Serializable
{
private static final long serialVersionUID = 1L; //JVM에서 읽기 위해서 설정(싱크)
private long bbsSeq; //게시물번호 (테이블과 비교해서 작성), 시퀀스+1
private String userId; //게시자 아이디
private String bbsName; //게시자명
private String bbsEmail; //게시자 이메일
private String bbsPwd; //게시물 비밀번호
private String bbsTitle; //게시물 제목
private String bbsContent; //게시물 내용
private long bbsReadCnt; //조회수
private String regDate; //등록일
public Board()
{ //빈 생성자
bbsSeq = 0;
userId = "";
bbsName = "";
bbsEmail = "";
bbsPwd = "";
bbsTitle = "";
bbsContent = "";
bbsReadCnt = 0;
regDate = "";
}
public long getBbsSeq() {
return bbsSeq;
}
public void setBbsSeq(long bbsSeq) {
this.bbsSeq = bbsSeq;
}
public String getUserId() {
return userId;
}
public void setUserId(String userId) {
this.userId = userId;
}
public String getBbsName() {
return bbsName;
}
public void setBbsName(String bbsName) {
this.bbsName = bbsName;
}
public String getBbsEmail() {
return bbsEmail;
}
public void setBbsEmail(String bbsEmail) {
this.bbsEmail = bbsEmail;
}
public String getBbsPwd() {
return bbsPwd;
}
public void setBbsPwd(String bbsPwd) {
this.bbsPwd = bbsPwd;
}
public String getBbsTitle() {
return bbsTitle;
}
public void setBbsTitle(String bbsTitle) {
this.bbsTitle = bbsTitle;
}
public String getBbsContent() {
return bbsContent;
}
public void setBbsContent(String bbsContent) {
this.bbsContent = bbsContent;
}
public long getBbsReadCnt() {
return bbsReadCnt;
}
public void setBbsReadCnt(long bbsReadCnt) {
this.bbsReadCnt = bbsReadCnt;
}
public String getRegDate() {
return regDate;
}
public void setRegDate(String regDate) {
this.regDate = regDate;
}
}
게시판을 만들기 위해서 필요한 파일들을 만들긔...
< webapp / board / list.jsp >
화면을 구성하는 페이지로 게시글은 하드 코딩으로 만들어 둔 상태
<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%>
<!DOCTYPE html>
<html>
<head>
<%@ include file="/include/head.jsp" %>
<script>
$(document).ready(function(){
$("#btnWrite").on("click", function(){
document.bbsForm.bbsSeq.value = "";
document.bbsForm.action = "/board/write.jsp";
document.bbsForm.submit(); //아래의 값을 가지고 넘어간다.
});
});
</script>
</head>
<body>
<%@ include file="/include/navigation.jsp" %>
<div class="container">
<div class="d-flex">
<div style="width:50%;">
<h2>게시판</h2>
</div>
<div class="ml-auto input-group" style="width:50%;">
<select name="_searchType" id="_searchType" class="custom-select" style="width:auto;">
<option value="">조회 항목</option>
<option value="1">작성자</option>
<option value="2">제목</option>
<option value="3">내용</option>
</select>
<input type="text" name="_searchValue" id="_searchValue" value="" class="form-control mx-1" maxlength="20" style="width:auto;ime-mode:active;" placeholder="조회값을 입력하세요." />
<button type="button" id="btnSearch" class="btn btn-secondary mb-3 mx-1">조회</button>
</div>
</div>
<table class="table table-hover">
<thead>
<tr style="background-color: #dee2e6;">
<th scope="col" class="text-center" style="width:10%">번호</th>
<th scope="col" class="text-center" style="width:55%">제목</th>
<th scope="col" class="text-center" style="width:10%">작성자</th>
<th scope="col" class="text-center" style="width:15%">날짜</th>
<th scope="col" class="text-center" style="width:10%">조회수</th>
</tr>
</thead>
<tbody>
<tr>
<td class="text-center">2</td>
<td><a href="javascript:void(0)" onclick="fn_view(4)">제목12</a></td>
<td class="text-center">테스트</td>
<td class="text-center">2021.07.12 05:52:44</td>
<td class="text-center">0</td>
</tr>
<tr>
<td class="text-center">1</td>
<td><a href="javascript:void(0)" onclick="fn_view(3)">제목1</a></td>
<td class="text-center">테스트</td>
<td class="text-center">2021.07.11 21:15:33</td>
<td class="text-center">4</td>
</tr>
</tbody>
<tfoot>
<tr>
<td colspan="5"></td>
</tr>
</tfoot>
</table>
<nav>
<ul class="pagination justify-content-center">
<li class="page-item active"><a class="page-link" href="javascript:void(0)" style="cursor:default;">1</a></li>
</ul>
</nav>
<button type="button" id="btnWrite" class="btn btn-secondary mb-3">글쓰기</button>
<form name="bbsForm" id="bbsForm" method="post">
<!-- 딱 필요한 것만 서버에 보내자! -->
<input type="hidden" name="bbsSeq" value="" /> <!-- 항상 물고 다님 -->
<input type="hidden" name="searchType" value="" />
<input type="hidden" name="searchValue" value="" /> <!-- 페이지번호 -->
<input type="hidden" name="curPage" value="1" /> <!-- 하드코딩 -->
</form>
</div>
</body>
</html>
오늘은 일단 글쓰기를 눌렀을 때 글쓰기 페이지로 이동하는 기능을 넣어줌
document.bbsForm.bbsSeq.value = "";
document.bbsForm.action = "/board/write.jsp";
document.bbsForm.submit(); //아래의 값을 가지고 넘어간다.
bbsSwq는 시퀀스를 잡아둔 게시물 번호이기 때문에 커넥션 후 조회를 해야하니까 갖고 갈 필요가 없고, 글쓰기 페이지로 바로 이동을 합니다. 그리고 submit을 이용해 히든으로 숨겨 둔 값들을 가지고 가는데 아래의 값들을 가지고 가는 이유는? (아래에 계속 . . . )
<form name="bbsForm" id="bbsForm" method="post">
<!-- 딱 필요한 것만 서버에 보내자! -->
<input type="hidden" name="bbsSeq" value="" /> <!-- 항상 물고 다님 -->
<input type="hidden" name="searchType" value="" />
<input type="hidden" name="searchValue" value="" /> <!-- 페이지번호 -->
<input type="hidden" name="curPage" value="1" /> <!-- 하드코딩 -->
</form>
여기서 hidden으로 searchType, searchValue, curPage를 갖고 가는 이유는 나중에 되돌아 왔을 때 내가 있던 상황 그대로 돌아가기 위해서 가지고 가기 위한 것! write.jsp에서 다시 설명할 거임 (성공적으로 작성을 하게 되면 가지고 오지 않음)
기존의 코드를 보면 로그인 했을 때에도 로그인 창이 보인다. 때문에 로그인을 하면 바로 게시판으로 이동을 하도록 index.jsp 로그인 성공 했을 때 location.href 부분을 수정해주어야 함
<webapp / index.jsp>
< / board / write.jsp >
- 게시물을 쓰기 위한 페이지
- 로그인이 돼 있으면 자동으로 계정 정보(이름/이메일)을 화면에 뿌려줌
<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8" %>
<%@ page import="com.icia.common.util.StringUtil" %>
<%@ page import="com.icia.web.util.CookieUtil" %>
<%@ page import="com.icia.web.util.HttpUtil" %>
<%@ page import="com.icia.web.dao.UserDao" %>
<%@ page import="com.icia.web.model.User" %>
<%@ page import="org.apache.logging.log4j.LogManager" %>
<%@ page import="org.apache.logging.log4j.Logger" %>
<%
//로그
Logger logger = LogManager.getLogger("/board/write.jsp");
HttpUtil.requestLogString(request, logger);
String cookieUserId = CookieUtil.getValue(request, "USER_ID"); //쿠키 정보 읽어오기
int chkVal = 0;
//로그인이 안 돼 있다면 index.jsp로 이동하기
if(StringUtil.isEmpty(cookieUserId))
{
chkVal = 1; //userRegForm 에서는 boolean타입의 true/false였음
response.sendRedirect("/");
}
//이 세개는 리스트 버튼을 눌렀을 때 필요한 값들
String searchType = HttpUtil.get(request, "searchType", "");
String searchValue = HttpUtil.get(request, "searchValue", "");
long curPage = HttpUtil.get(request, "curPage", (long)1);
//유저 정보 관련 객체들
UserDao userDao = new UserDao();
User user = userDao.userSelect(cookieUserId);
//화면에 이름/이메일 디폴트 값으로 뿌려 줄 예정
//로그인이 돼 있다면 실행
if(chkVal == 0)
{
%>
<!DOCTYPE html>
<html>
<head>
<%@ include file="/include/head.jsp" %>
<script>
$(document).ready(function(){
$("#bbsTitle").focus(); //페이지 이동시 바로 포커스
$("#btnWrite").on("click", function(){
//getter
if($.trim($("#bbsTitle").val()).length <= 0)
{
alert("제목을 입력하세요");
$("#bbsTitle").val(""); //setter
$("#bbsTitle").focus();
return;
}
if($.trim($("#bbsContent").val()).length <= 0)
{
alert("내용을 입력하세요");
$("#bbsContent").val("");
$("#bbsContent").focus();
return;
}
document.writeForm.submit();
});
$("#btnList").on("click", function(){
document.bbsForm.action = "/board/list.jsp";
document.bbsForm.submit();
//리스트 버튼을 누르면 리스트로 이동하면서 bbsForm값을 다시 가져간다.
});
});
</script>
</head>
<body>
<%@ include file="/include/navigation.jsp" %>
<div class="container">
<h2>게시물 쓰기</h2>
<form name="writeForm" id="writeForm" action="/board/writeProc.jsp" method="post">
<!-- <form name="writeForm" id="writeForm" action="/board/writeFileProc.jsp" method="post" enctype="multipart/form-data"> -->
<!-- form name="writeForm" id="writeForm" action="/board/writeProc.jsp" method="post" -->
<!-- name, email 값을 불러와서 화면에 뿌려줌 -->
<input type="text" name="bbsName" id="bbsName" maxlength="20" value="<%=user.getUserName()%>" style="ime-mode:active;" class="form-control mt-4 mb-2" placeholder="이름을 입력해주세요." readonly />
<input type="text" name="bbsEmail" id="bbsEmail" maxlength="30" value="<%=user.getUserEmail()%>" style="ime-mode:inactive;" class="form-control mb-2" placeholder="이메일을 입력해주세요." readonly />
<input type="text" name="bbsTitle" id="bbsTitle" maxlength="100" style="ime-mode:active;" class="form-control mb-2" placeholder="제목을 입력해주세요." required />
<div class="form-group">
<textarea class="form-control" rows="10" name="bbsContent" id="bbsContent" style="ime-mode:active;" placeholder="내용을 입력해주세요" required></textarea>
</div>
<div class="form-group row">
<div class="col-sm-12">
<button type="button" id="btnWrite" class="btn btn-primary" title="저장">저장</button>
<button type="button" id="btnList" class="btn btn-secondary" title="리스트">리스트</button>
</div>
</div>
</form>
<form name="bbsForm" id="bbsForm" method="post">
<input type="hidden" name="searchType" value="<%=searchType%>" />
<input type="hidden" name="searchValue" value="<%=searchValue%>" />
<input type="hidden" name="curPage" value="<%=curPage%>" />
</form>
</div>
</body>
</html>
<%
}
%>
저장, 리스트 각 버튼에 대한 처리를 만들어줘야 함
String searchType = HttpUtil.get(request, "searchType", "");
String searchValue = HttpUtil.get(request, "searchValue", "");
long curPage = HttpUtil.get(request, "curPage", (long)1);
위에서 말한 세 가지이다. 이 세가지가 필요한 이유는 리스트 버튼을 눌렀을 때 들어오기 전 상태 그대로를 유지하기 위해서이다. 기본 전제가 write페이지는 list페이지를 거쳐 오기 때문에 기본적으로 값을 가지고 가야하는 것이다. 여기서도 히든으로 폼을 품고 있다. 대신 bbsSeq를 가지고 있을 필요가 없음
로그아웃을 한 상태로 test.icia.co.kr:8088/board/write.jsp 이렇게 들어오게 되면 500번대 에러(서버 잘못) 표시가 뜬다. 그걸 잡아주기 위해서 아래 처럼 index.jsp로 넘어가는 코드를 넣어주었다. 체크 하는 변수는 int, boolean 어떤 것도 상관 없다. 로그인 체크가 되었을 때는 html 부분을 모두 포함하고 있으면 됨~
다시 오라클로 넘어 와서 쿼리 작성
INSERT에서 한 번에 시퀀스까지 처리하지 않고, 별도로 존재하기 때문에 단독으로 SELECT로 갖고 와야 함 DUAL에서 갖고 온다.
INSERT INTO TBL_BOARD
(BBS_SEQ, USER_ID, BBS_NAME, BBS_EMAIL, BBS_PWD, BBS_TITLE, BBS_CONTENT, BBS_READ_CNT, REG_DATE)
VALUES (?, ?, ?, ?, ?, ?, ?, 0, SYSDATE);
--VALUES (1, 'TEST', 'test', 'test', 'test', 'test', 'test', 0, SYSDATE);
-- 여기서 바로 시퀀스 부르지 않고, 별도로 가지고 옴
ROLLBACK;
SELECT SEQ_BBS_SEQ.NEXTVAL
FROM DUAL;
--커런트발은 한 번도 안 해서(초기값이 없어서) 안 됨
--별도로 존재하기 때문에 단독으로 갖고 와야됨
CURRVAL이 아니라 NEXTVAL을 사용해야 하는 이유는 한 번도 사용한 적이 없으니 초기값이 없어서 안 된다.
package com.icia.web.dao;
< BoardDao.java >
* 메소드 생성
boardInsert() : 게시판 등록
newBbsSeq() : 시퀀스 조회
package com.icia.web.dao;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import com.icia.web.db.DBManager;
import com.icia.web.model.Board;
public class BoardDao
{
private static Logger Logger = LogManager.getLogger(BoardDao.class);
public BoardDao()
{
}
//게시판 등록
public int boardInsert(Board board)
{
int count = 0;
Connection conn = null;
PreparedStatement pstmt = null;
StringBuilder sql = new StringBuilder();
sql.append("INSERT INTO TBL_BOARD ");
sql.append(" (BBS_SEQ, USER_ID, BBS_NAME, BBS_EMAIL, BBS_PWD, BBS_TITLE, ");
sql.append(" BBS_CONTENT, BBS_READ_CNT, REG_DATE) ");
sql.append("VALUES (?, ?, ?, ?, ?, ?, ?, 0, SYSDATE) ");
try
{
int idx = 0;
long bbsSeq = 0;
conn = DBManager.getConnection();
//시퀀스 조회 추가
bbsSeq = newBbsSeq(conn);
//newBbsSeq 메소드가 conn을 매개변수로 받는다.
//리턴 값으로 시퀀스 값을 넘긴다.
board.setBbsSeq(bbsSeq);
//프라이머리 지역변수
pstmt = conn.prepareStatement(sql.toString());
pstmt.setLong(++idx, bbsSeq); //board.getBbsSeq() 이걸 써도 됨
pstmt.setString(++idx, board.getUserId());
pstmt.setString(++idx, board.getBbsName());
pstmt.setString(++idx, board.getBbsEmail());
pstmt.setString(++idx, board.getBbsPwd());
pstmt.setString(++idx, board.getBbsTitle());
pstmt.setString(++idx, board.getBbsContent());
count = pstmt.executeUpdate(); //날려주긔~~!
}
catch(SQLException e)
{
Logger.error("[BoardDao] boardInsert SQLException", e);
}
finally
{
DBManager.close(pstmt, conn);
}
return count;
}
//시퀀스를 가져오기
private long newBbsSeq(Connection conn)
{
long bbsSeq = 0;
PreparedStatement pstmt = null;
ResultSet rs = null;
StringBuilder sql = new StringBuilder();
sql.append("SELECT SEQ_BBS_SEQ.NEXTVAL ");
sql.append(" FROM DUAL ");
try
{
pstmt = conn.prepareStatement(sql.toString());
//인수값이 없으니까 바로 날림
rs = pstmt.executeQuery();
if(rs.next())
{
bbsSeq = rs.getLong(1);
//결과가 하나니까 AS 안줘도 됨
}
}
catch(SQLException e)
{
Logger.error("[BoardDao] newBbsSeq SQLException", e);
}
finally
{
DBManager.close(rs); //무조건 닫아줌
}
return bbsSeq;
//시퀀스 값을 얻어서 그 값을 리턴함
}
}
newBbsSeq() 메소드에서 왜 커넥션을 매개변수로 받을까?
시퀀스를 가져오려면 DB연결이 필요한데 이미 사용한 메소드에서 DB연결이 돼 있다면 그걸 갖고 와서 사용할 수 있다. 연결 돼 있는 세션으로 쿼리를 다시 날린다는 뜻임 또 다시 커넥션을 만들어서 연결해도 되지만 자원을 남기게 되는 거니까 코드의 재사용성을 위해 매개변수로 커넥션을 아예 넘기는 것임. 인스턴스 변수라 바로 호출 가능!!
<writeProc.jsp>
등록을 실행하고, 처리(등록/오류)에 따라서 alert창을 띄움
<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8" %>
<%@ page import="com.icia.common.util.StringUtil" %>
<%@ page import="com.icia.web.util.CookieUtil" %>
<%@ page import="com.icia.web.util.HttpUtil" %>
<%@ page import="com.icia.web.dao.BoardDao" %>
<%@ page import="com.icia.web.model.Board" %>
<%@ page import="org.apache.logging.log4j.LogManager" %>
<%@ page import="org.apache.logging.log4j.Logger" %>
<%
//로그
Logger logger = LogManager.getLogger("/user/writeProc.jsp");
HttpUtil.requestLogString(request, logger);
String cookieUserId = CookieUtil.getValue(request, "USER_ID");
boolean bSuccess = false;
String errorMsg = "";
String bbsTitle = HttpUtil.get(request, "bbsTitle", "");
String bbsContent = HttpUtil.get(request, "bbsContent", "");
//둘 다 비워지지 않았다면
if(!StringUtil.isEmpty(bbsTitle) && !StringUtil.isEmpty(bbsContent))
{
//객체생성
BoardDao boardDao = new BoardDao();
Board board = new Board();
board.setUserId(cookieUserId);
board.setBbsTitle(bbsTitle);
board.setBbsContent(bbsContent);
//리턴타입 인트 처리건수 //처리가 됐다면
if(boardDao.boardInsert(board) > 0)
{
bSuccess = true;
}
else
{
errorMsg = "게시물 등록 중 오류가 발생했습니다.";
}
}
else
{
errorMsg = "게시물 입력 값이 올바르지 않습니다.";
}
%>
<!DOCTYPE html>
<html>
<head>
<%@ include file="/include/head.jsp" %>
<script>
//오류가 나든 정상적이든 리스트페이지로 이동할 거임
$(document).ready(function(){
<%
if(bSuccess == true)
{
%>
alert("게시물이 등록 되었습니다.");
<%
}
else
{
%>
alert("<%=errorMsg%>");
<%
}
%>
location.href = "/board/list.jsp";
});
</script>
</head>
<body>
</body>
</html>
후... 멀고 험한 이 길..... 계속 가다보면 익숙해지겠쥬~