🍁게시판 구현
1. 주제
- 게시판
- 회원 관리(인증)
- 기타 등등
2. 요구분석
게시판 CRUD
권한 처리
- 목록 보기, 상세보기는 비회원과 회원 모두 할 수 있다.
- 글쓰기, 수정하기, 삭제하기는 비회원은 할 수 없다.
- 수정하기, 삭제하기는 회원의 자기 글만 가능하다.
- 수정하기, 삭제하기는 관리자는 모든 글에 가능하다.
- 즉, 로그인을 하지 않더라도 글은 볼 수 있지만, 글을 조작하는 건 해당 글을 작성한 회원과 관리자만 가능하다.
3. 전체 구성(페이지 관계도)
- drwo.io
4. 화면 설계 & 스토리 보드
5. 데이터베이스
- ERD
- ToyProject > ddl.sql, dml.sql
ERD에 대해서는 위 글을 참고한다.
6. 구현
패키지
1. com.test.toy: 메인 패키지
2. com.test.toy.user: 회원 패키지
3. com.test.toy.user.repository: 회원 DB
4. com.test.toy.user.model: 회원 DB
5. com.test.toy.board: 게시판 패키지
6. com.test.toy.board.repository: 게시판 DB
7. com.test.toy.board.model: 게시판 DB
8. com.test.toy.filter: 필터 패키지
파일
1. com.test.toy
- Index.java: 시작 페이지
- Template.java: 템플릿 페이지
- DBUtil.java
2. com.test.toy.user
- Register.java: 회원 가입
- Unregister.java: 회원 탈퇴
- Login.java: 로그인
- Logout.java: 로그아웃
- Info.java: 회원 정보
3. com.test.toy.user.repository
- UserDAO.java
4. com.test.toy.user.model
- UserDTO.java
DTO와 VO
DTO는 Data Transfer Object로, 하나의 계층에서 또 다른 계층으로 데이터를 보낼 때 단순하게 구현하기 위한 상자 역할을 한다.
이와 비슷한 UserVO는 Value Obejct이다. DTO와 VO 둘 다 모델이며, 특성에서 차이를 보인다.
UserVO는 DB에서 가져온 데이터만을 다루며, 수정을 하지 않는 읽기 전용이다. 그래서 setter를 만들지 않는다. 그리고 생성자를 오버라이딩해서 만든다. 반면에 UserDTO는 DB 데이터를 포함해 다른 데이터를 다룬다.
현실에서는 이 둘을 명확하게 구분해서 사용하기가 어렵다. 내부적으로 DB를 가공하여 사용하는 작업을 하기 때문이다. 그래서 이 둘을 똑같은 것으로 생각해도 된다.
5. com.test.toy.board
- List.java: 목록보기
- Add.java 글쓰기
- View.java: 글보기
- Edit.java: 수정
- Del.java: 삭제
- Auth.java: 권한 확인
- Dummy.java: 더미 데이터
- Comment.java: 댓글
- DelComment.java: 댓글 삭제
- EditComment.java: 댓글 수정
6. com.test.toy.board.repository
- BoardDAO.java
7. com.test.toy.board.model
- BoardDTO.java
- CommentDTO.java
8. com.test.toy.filter
- EncodingFilter.java
- AuthFilter.java
뷰
1. views
a. 루트
- index.jsp: 시작 페이지
- template.jsp: 템플릿 페이지
b. user
- register.jsp: 회원 가입
- unregister.jsp: 회원 탈퇴
- login.jsp: 로그인
- info.jsp: 회원 정보
c. inc (조각페이지)
- asset.jsp: CSS, JS
- header.jsp
d. board
- list.jsp
- add.jsp
- view.jsp
- edit.jsp
- del.jsp
라이브러리
lib
- ojdbc6.jar
- jstl-1.2.jar
- cos.jar
- lombok.jar
- json-simple-1.1.1.jar
클라이언트 라이브러리
webapp > asset > css > toy.css
Favicon (Favorite icon)
- *.ico
프로필 사진 경로
webapp > asset > pic
페이징
다량의 게시물을 한 화면으로 출력하기 불편하기 때문에 가상의 페이지를 분할하고 나눠서 출력하는 행위
페이징이 어려운 이유는 페이지 바 때문이다. 게시물 수가 모자르면 1번부터 5번까지 있을 쑤 있고, 10번을 넘어갈 경우 다음 버튼이 필요하기 때문이다.
📑작업 과정
1. 템플릿 작업
2. 시작 페이지 제작 (Index.java, index.jsp)
3. Favicon (Favorite icon 즐겨찾기 아이콘) 작업
4. 회원가입 페이지 제작 (Register.java, register.jsp)
5. DTO 작업(UserDTO는 접두어를 붙이지 않고, ERD 이름과 동일하게 쓴다.)하고 DAO로 데이터 전송 (이때 Getter, Setter를 Lombok으로 대체)
6. DB 작업 (UserDAO) (DBUtil은 따로 패키지를 만들어서 넣는 게 좋지만, 루프 폴더에 생성)
7. 회원탈퇴 페이지 제작 (Unregister.java, unregister.jsp)
8. 로그아웃했을 때 보이는 메뉴 변경 (register -> unregister)
9. 회원 정보 페이지 제작 (Info.java, info.jsp)
10. 게시판 작업 (당일 작성한 글의 regdate 수정, 30분 내에 작성한 글에는 new 출력)
11. 태그 비활성화 (게시판 글을 추가할 때 <script> 태그를 추가해도 실행되지 않도록 설정)
12. 권한 처리 (해당 글을 작성한 회원 또는 관리자만이 해당 글을 수정하거나 삭제할 수 있도록 설정)
13. 검색 기능 구현 (List.java, list.jsp, BoardDAO.java)
14. 더미 데이터 생성 및 페이징 기능, 페이지 바 제작 (Dummy.java, List.Java, list.jsp)
15. Ajax로 댓글 기능 구현 (view.jsp, Comment.java + json-simple, BoardDAO.java, CommentDTO.java)
16. 댓글 삭제 기능 구현 (DelComment.java)
17. 글을 삭제했을 때 해당 글의 댓글을 삭제하는 기능 구현 (a. 글에 댓글이 있어서 못 지운다, b. 글의 제목과 내용을 수정한다)
🍂데이터베이스
eXERD
ddl.sql
계정 생성
-- system
create user toy identified by java1234;
grant connect, resource, dba to toy;
새로운 연결
-- system
create user toy identified by java1234;
grant connect, resource, dba to toy;
-- ToyProject > ddl.sql
-- 게시판
-- 회원
drop table tblUser;
create table tblUser (
id varchar2(50) not null,
pw varchar2(50) not null,
name varchar2(50) not null,
email varchar2(100) not null,
lv char(1) not null,
pic varchar2(100) default 'pic.png' not null,
intro varchar2(500) not null,
ing char(1) default 'y' not null,
constraint tbluser_pk primary key(id)
);
-- 게시판
drop table tblBoard;
drop sequence seqBoard;
create table tblBoard (
seq number not null,
subject varchar2(300) not null,
content varchar2(4000) not null,
regdate date default sysdate not null,
readcount number default 0 not null,
id varchar2(50) not null,
constraint tblboard_pk primary key(seq),
constraint tblboard_fk foreign key(id) references tblUser(id)
);
create sequence seqBoard;
create or replace view vwBoard
as
select
seq, subject, id, readcount, content,
case
when to_char(sysdate, 'yyyy-mm-dd') = to_char(regdate, 'yyyy-mm-dd')
then to_char(regdate, 'hh24:mi:ss') -- 글쓰고 난 뒤 하루가 지나면 시간도 출력
else to_char(regdate, 'yyyy-mm-dd')
end as regdate,
(select name from tblUser where id = tblBoard.id) as name,
case
when (sysdate - regdate) < 30 / 24 / 60 then 1
else 0
end as isnew,
(select count(*) from tblComment where bseq = tblBoard.seq) as ccnt -- commentcount 댓글 개수
from tblBoard order by seq desc;
-- 댓글
drop table tblComment;
drop sequence seqComment;
create table tblComment (
seq number not null,
content varchar2(1000) not null,
regdate date default sysdate not null,
id varchar2(50) not null, --회원
bseq number not null, --부모글번호
constraint tblcomment_pk primary key(seq),
constraint tblcomment_fk_id foreign key(id) references tblUser(id),
constraint tblcomment_fk_bseq foreign key(bseq) references tblBoard(seq)
);
create sequence seqComment;
select * from tblComment;
dml.sql
-- Toyproject > dml.sql
-- 회원
insert into tblUser (id, pw, name, email, lv, pic, intro, ing)
values ('isaac', '1111', '아이작', 'isaac@gmail.com', '1', default, '자바를 공부하는 학생입니다.', default);
insert into tblUser (id, pw, name, email, lv, pic, intro, ing)
values ('sopia', '1111', '소피아', 'sopia@gmail.com', '1', default, '사회복지를 공부하는 직장인입니다.', default);
insert into tblUser (id, pw, name, email, lv, pic, intro, ing)
values ('admin', '1111', '관리자', 'admin@gmail.com', '2', default, '관리자입니다.', default);
select * from tblUser;
update tblUser set ing = 'y';
update tblUser set lv = 2 where id = 'admin';
commit;
-- 게시판
insert into tblBoard (seq, subject, content, regdate, readcount, id)
values (seqBoard.nextVal, '제목', '내용', default, default, 'isaac');
insert into tblBoard (seq, subject, content, regdate, readcount, id)
values (seqBoard.nextVal, '게시판테스트', '안녕하세요.', default, default, 'isaac');
select * from tblBoard;
-- seq, subject, id, readcount, regdate, name
select * from vwBoard;
select * from vwBoard where subject like '%JDBC%'; --검색
delete from tblBoard where seq = 13;
UPDATE tblBoard SET regdate = regdate + 1/24/60 WHERE seq = 5;
commit;
-- 페이징
-- rownum을 활용하여 페이징
select * from (select a.*, rownum as rnum from vwBoard a) where rnum between 1 and 10;
select * from (select a.*, rownum as rnum from vwBoard a) where rnum between 11 and 20;
select * from (select a.*, rownum as rnum from vwBoard a) where rnum between 21 and 30;
🍂클라이언트 라이브러리
toy.css
/* webapp > asset > css > toy.css */
html {
overflow-y: scroll;
}
#header {
display: flex;
justify-content: space-between;
border-bottom: 1px solid #CCC;
height: 60px;
}
#header > h1 {
margin: 1rem;
font-size: 1.2rem;
}
#header > h1 .material-symbols-outlined {
transform: translate(0px, 5px);
font-size: 1.8rem;
}
#header > nav {
margin: 1rem;
}
#header > nav > a {
font-size: 16px;
padding-right: 2px;
}
#header > nav > a::after {
content: ' ·';
}
#header > nav > a:last-child::after {
content: '';
}
#main {
margin: 1rem;
}
🍂Java
1. com.test.toy: 메인 패키지
Index.java: 시작 페이지
package com.test.toy;
import java.io.IOException;
import javax.servlet.RequestDispatcher;
import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
@WebServlet("/index.do")
public class Index extends HttpServlet {
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
//Index.java
RequestDispatcher dispatcher = req.getRequestDispatcher("/WEB-INF/views/index.jsp");
dispatcher.forward(req, resp);
}
}
Template.java: 템플릿 페이지
package com.test.toy;
import java.io.IOException;
import javax.servlet.RequestDispatcher;
import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
@WebServlet("/template.do")
public class Template extends HttpServlet {
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
RequestDispatcher dispatcher = req.getRequestDispatcher("/WEB-INF/views/template.jsp");
dispatcher.forward(req, resp);
}
}
DBUtil.java
package com.test.toy;
import java.sql.Connection;
import java.sql.DriverManager;
public class DBUtil {
private static Connection conn;
public static Connection open() {
String url = "jdbc:oracle:thin:@localhost:1521:xe";
String id = "toy";
String pw = "java1234";
try {
Class.forName("oracle.jdbc.driver.OracleDriver");
conn = DriverManager.getConnection(url, id, pw);
return conn;
} catch (Exception e) {
e.printStackTrace();
}
return null;
}
public static Connection open(String server, String id, String pw) {
String url = "jdbc:oracle:thin:@" + server + ":1521:xe";
try {
Class.forName("oracle.jdbc.driver.OracleDriver");
conn = DriverManager.getConnection(url, id, pw);
return conn;
} catch (Exception e) {
e.printStackTrace();
}
return null;
}
}
2. com.test.toy.user: 회원 패키지
Register.java: 회원 가입
package com.test.toy.user;
import java.io.IOException;
import java.io.PrintWriter;
import javax.servlet.RequestDispatcher;
import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import com.oreilly.servlet.MultipartRequest;
import com.oreilly.servlet.multipart.DefaultFileRenamePolicy;
import com.test.toy.user.model.UserDTO;
import com.test.toy.user.repository.UserDAO;
@WebServlet("/user/register.do")
public class Register extends HttpServlet {
//Get 방식과 Post 방식을 함께 만들어서 서블릿 파일 하나로도 두 개의 연결이 가능하다.
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
//Register.java
RequestDispatcher dispatcher = req.getRequestDispatcher("/WEB-INF/views/user/register.jsp");
dispatcher.forward(req, resp);
}
@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
//RegisterOk.java 역할
//1. 데이터 가져오기
//2. DB 작업 > insert
//3. 피드백
//req.getParameter() 동작 불가능 > MultipartRequest 대체
//multipart/form-data로 호출했기 떄문에 req.getParameter()가 동작하지 않는다.
//대신에 cos.jar 안에 들어있는 MultipartRequest로 대체한다.
try {
MultipartRequest multi = new MultipartRequest(
req, //Request
req.getRealPath("/asset/pic"), //업로드 폴더 경로
1024 * 1024 * 10, //파일의 크기
"UTF-8", //인코딩 방식을 바꾸기 때문에 따로 한글 처리를 하지 않아도 된다.
new DefaultFileRenamePolicy()
);
//System.out.println(req.getRealPath("/asset/pic"));
//사진 저장 경로 확인
String id = multi.getParameter("id");
String pw = multi.getParameter("pw");
String name = multi.getParameter("name");
String email = multi.getParameter("email");
String pic = multi.getFilesystemName("pic"); //사진은 geteParameter로 가져오지 않으며, 파일 이름으로 가져와야 한다.
String intro = multi.getParameter("intro");
//System.out.println(intro);
UserDTO dto = new UserDTO();
dto.setId(id);
dto.setPw(pw);
dto.setName(name);
dto.setEmail(email);
//사진
if (pic != null && !pic.equals("")) {
dto.setPic(pic);
} else {
dto.setPic("pic.png");
}
dto.setIntro(intro);
UserDAO dao = new UserDAO();
int result = dao.register(dto);
if (result == 1) {
resp.sendRedirect("/toy/index.do");
}
} catch (Exception e) {
System.out.println("Register.doPost()");
e.printStackTrace();
}
//0 또는 에러
PrintWriter writer = resp.getWriter();
writer.print("<script>alert('failed');history.back();</script>");
writer.close();
}
}
Unregister.java: 회원 탈퇴
package com.test.toy.user;
import java.io.IOException;
import java.io.PrintWriter;
import javax.servlet.RequestDispatcher;
import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import com.test.toy.user.repository.UserDAO;
@WebServlet("/user/unregister.do")
public class Unregister extends HttpServlet {
//Unregister.java
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
RequestDispatcher dispatcher = req.getRequestDispatcher("/WEB-INF/views/user/unregister.jsp");
dispatcher.forward(req, resp);
}
@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
//UnregisterOk.java
//회원 탈퇴
//회원 탈퇴는 delete가 아니라 update이다.
//어떤 사람이 구매한 구매 이력을 지우는 것과 같다. 어떤 사람이 생산해낸 모든 산출물은 그 사람이 나간다고 해서 반드시 없애야 하는 게 아니다.
//어떤 데이터고, 사이트의 정책이 어떻게 되어 있고, 사용자에게 어떤 동의, 약관을 받았는지에 따라서 달라진다.
//1.
String id = req.getSession().getAttribute("id").toString();
//인증 티켓은 세션 안에 있다. 이 사람을 탈퇴시키면 된다.
//이제 DB 작업을 한다.
//2.
UserDAO dao = new UserDAO();
int result = dao.unregister(id);
//3.
if (result == 1) {
//회원 탈퇴 + 로그아웃
//물리적인 회원 탈퇴는 끝난 상태이다.
//탈퇴를 하면 세션을 뒤져서 주었던 인증 티켓을 회수한다. (로그아웃 시킨다)
req.getSession().removeAttribute("id");
req.getSession().removeAttribute("name");
req.getSession().removeAttribute("lv");
resp.sendRedirect("/toy/index.do");
} else {
//회원 탈퇴 실패
PrintWriter writer = resp.getWriter();
writer.print("<script>alert('failed');history.back();</script>");
writer.close();
}
}
}
Login.java: 로그인
package com.test.toy.user;
import java.io.IOException;
import java.io.PrintWriter;
import javax.servlet.RequestDispatcher;
import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import com.test.toy.user.model.UserDTO;
import com.test.toy.user.repository.UserDAO;
@WebServlet("/user/login.do")
public class Login extends HttpServlet {
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
//Login.java
RequestDispatcher dispatcher = req.getRequestDispatcher("/WEB-INF/views/user/login.jsp");
dispatcher.forward(req, resp);
}
@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
//LoginOk.java 역할
//1. 데이터 가져오기(id, pw)
//2. DB 작업 > select
//3. 인증 티켓 발급?
//4. 피드백
//1.
String id = req.getParameter("id");
String pw = req.getParameter("pw");
//2.
UserDAO dao = new UserDAO();
UserDTO dto = new UserDTO(); //넘기는 dto
dto.setId(id);
dto.setPw(pw);
UserDTO result = dao.login(dto); //돌려받는 dto
if (result != null) {
//로그인 성공
//인증 티켓 발급
req.getSession().setAttribute("id", id); //인증 티켓
req.getSession().setAttribute("name", result.getName());
req.getSession().setAttribute("lv", result.getLv());
resp.sendRedirect("/toy/index.do");
} else {
//로그인 실패
//0 또는 에러
PrintWriter writer = resp.getWriter();
writer.print("<script>alert('failed');history.back();</script>");
writer.close();
}
}
}
Logout.java: 로그아웃
package com.test.toy.user;
import java.io.IOException;
import javax.servlet.RequestDispatcher;
import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
@WebServlet("/user/logout.do")
public class Logout extends HttpServlet {
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
//Logout.java
req.getSession().removeAttribute("id"); //인증 티켓 제거
req.getSession().removeAttribute("name");
req.getSession().removeAttribute("lv");
resp.sendRedirect("/toy/index.do");
}
}
Info.java: 회원 정보
package com.test.toy.user;
import java.io.IOException;
import javax.servlet.RequestDispatcher;
import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import com.test.toy.user.model.UserDTO;
import com.test.toy.user.repository.UserDAO;
@WebServlet("/user/info.do")
public class Info extends HttpServlet {
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
//Info.java
//회원 정보, 활동 내역 출력
//1.
String id = req.getSession().getAttribute("id").toString();
//2.
UserDAO dao = new UserDAO();
UserDTO dto = dao.get(id); //회원 정보
dto.setIntro(dto.getIntro().replace("\r\n", "<br>")); //여러 줄 처리
//3.
req.setAttribute("dto", dto);
RequestDispatcher dispatcher = req.getRequestDispatcher("/WEB-INF/views/user/info.jsp");
dispatcher.forward(req, resp);
}
}
3. com.test.toy.user.repository
UserDAO.java
package com.test.toy.user.repository;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.Statement;
import com.test.toy.DBUtil;
import com.test.toy.user.model.UserDTO;
public class UserDAO {
private Connection conn;
private Statement stat;
private PreparedStatement pstat;
private ResultSet rs;
public UserDAO() {
this.conn = DBUtil.open();
}
public int register(UserDTO dto) {
/*
//코드조각
매개변수 유/무
반환값 유/무 > 단일값, DTO, List<DTO>
queryNoParamNoReturn
queryParamNoReturn
queryNoParamTokenReturn
queryParamTokenReturn
queryNoParamDTOReturn
queryParamDTOReturn
queryNoParamListReturn
queryParamListReturn
*/
try {
String sql = "insert into tblUser (id, pw, name, email, pic, lv, intro, ing) values (?, ?, ?, ?, ?, 1, ?, default)";
pstat = conn.prepareStatement(sql);
pstat.setString(1, dto.getId());
pstat.setString(2, dto.getPw());
pstat.setString(3, dto.getName());
pstat.setString(4, dto.getEmail());
pstat.setString(5, dto.getPic());
pstat.setString(6, dto.getIntro());
return pstat.executeUpdate();
} catch (Exception e) {
e.printStackTrace();
}
return 0;
}
public UserDTO login(UserDTO dto) {
//매개변수(O), 반환값(DTO)
//queryParamDTOReturn
try {
String sql = "select * from tblUser where id = ? and pw = ? and ing = 'y'";
//현재 활동중인 멤버만 조회할 수 있도록 함
pstat = conn.prepareStatement(sql);
pstat.setString(1, dto.getId());
pstat.setString(2, dto.getPw());
rs = pstat.executeQuery();
if (rs.next()) {
UserDTO result = new UserDTO();
//위에서 dto가 있어서 result로 이름 변경
result.setId(rs.getString("id"));
result.setName(rs.getString("name"));
result.setLv(rs.getString("lv"));
return result;
}
} catch (Exception e) {
e.printStackTrace();
}
return null;
}
public int unregister(String id) {
//queryParamNoReturn
try {
String sql = "update tblUser set ing = 'n' where id = ?";
//y를 n으로 바꿔서 회원탈퇴 처리
//아이디를 제외한 나머지 정보는 null처리를 하거나 임의의 글자로 덮어쓰기 하는 경우가 많다.
//y를 n으로 바꿀 뿐만 아니라 나머지 정보도 수정해 주면 좋다.
pstat = conn.prepareStatement(sql);
pstat.setString(1, id);
return pstat.executeUpdate();
} catch (Exception e) {
e.printStackTrace();
}
return 0;
}
public UserDTO get(String id) {
//queryParamDTOReturn
try {
String sql = "select * from tblUser where id = ?";
pstat = conn.prepareStatement(sql);
pstat.setString(1, id);
rs = pstat.executeQuery();
if (rs.next()) {
UserDTO dto = new UserDTO();
dto.setName(rs.getString("name"));
dto.setEmail(rs.getString("email"));
dto.setPic(rs.getString("pic"));
dto.setIntro(rs.getString("intro"));
return dto;
}
} catch (Exception e) {
e.printStackTrace();
}
return null;
}
}
4. com.test.toy.user.model
UserDTO.java
package com.test.toy.user.model;
import lombok.Data;
//Lombok
@Data
public class UserDTO {
//컬럼 이름과 DTO의 이름을 같게 해줘야 한다.
private String id;
private String pw;
private String name;
private String email;
private String lv;
private String pic;
private String intro;
/*
public String getId() {
return id;
}
public void setId(String id) {
this.id = id;
}
public String getPw() {
return pw;
}
public void setPw(String pw) {
this.pw = pw;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getEmail() {
return email;
}
public void setEmail(String email) {
this.email = email;
}
public String getLv() {
return lv;
}
public void setLv(String lv) {
this.lv = lv;
}
public String getPic() {
return pic;
}
public void setPic(String pic) {
this.pic = pic;
}
public String getIntro() {
return intro;
}
public void setIntro(String intro) {
this.intro = intro;
}
*/
//멤버변수만 직접 만든다.
//getter, setter같은 생성자, toString은 반복적인 성질이 있다.
}
5. com.test.toy.board: 게시판 패키지
List.java: 목록보기
package com.test.toy.board;
import java.io.IOException;
import java.util.ArrayList;
import java.util.HashMap;
import javax.servlet.RequestDispatcher;
import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;
import com.test.toy.board.model.BoardDTO;
import com.test.toy.board.repository.BoardDAO;
@WebServlet("/board/list.do")
public class List extends HttpServlet {
//List.java
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
//A. list.do > 호출(목록보기)
//B. list.do?column=subject&word=검색어 > 호출(검색하기)
String column = req.getParameter("column");
String word = req.getParameter("word");
String search = "n"; //검색중("y"), 목록보기("n")
//아무것도 넘어오지 않으면 목록보기, 뒤에 넘어오는 게 있으면 검색하기로 인식한다.
if ((column == null && word == null) || (column.equals("") && word.equals(""))) {
search = "n";
} else {
search = "y";
}
//이제 DB에 select를 어떻게 넘길 것인가가 중요하다.
//column, word, search를 넘겨서 이 정보를 바탕으로 query를 수정하도록 한다.
//dto를 클래스로 만들면 클래스는 범용이기 때문에 다른 곳에서도 사용할 수 있게 된다.
//column, word, search는 이곳에서 한 번 쓰고 말 것이기 때문에 클래스를 만들면 부담이 된다. 이럴 때 HashMap을 사용한다. HashMap에는 아무거나 넣을 수 있다.
HashMap<String,String> map = new HashMap<String,String>();
map.put("column", column);
map.put("word", word);
map.put("search", search); //search 데이터를 넘기면 BoardDAO에서 검색중인지 if문으로 검사할 수 있다.
//페이징
//list.do만으로는 의사를 전혀 알 수 없다.
//의사표현이라는 건 데이터를 전달한다는 뜻이다.
//list.do는 1페이지로 간주하며, list.do?page=3과 같이 뒤에 page 파라미터를 붙여서 전달한다.
int nowPage = 0; //현재 페이지 번호
int totalCount = 0; //총 게시물 수
int pageSize = 10; //한 페이지에서 출력할 게시물 수
int totalPage = 0; //총 페이지 수
int begin = 0; //페이징 시작 위치
int end = 0; //페이징 끝위치
int n = 0; //출력 페이지 번호
int loop = 0; //루프 변수(10바퀴)
int blockSize = 10; //
String page = req.getParameter("page");
if (page == null || page.equals("")) {
nowPage = 1;
} else {
nowPage = Integer.parseInt(page); //계산을 위해 숫자형으로 변경
}
//list.do?page=1 > where rnum between 1 and 10
//list.do?page=2 > where rnum between 11 and 20
//list.do?page=3 > where rnum between 21 and 30
begin = ((nowPage - 1) * pageSize) + 1;
end = begin + pageSize - 1;
map.put("begin", begin + "");
map.put("end", end + "");
//사용자와 대화하면서 값을 만들 수 있게 수정 (빈문자열을 붙여 문자열로 변경)
//1. DB 작업 > select
//2. 반환 > JSP 호출하기
HttpSession session = req.getSession();
//조회수 티켓
session.setAttribute("read", "n");
//1.
BoardDAO dao = new BoardDAO();
ArrayList<BoardDTO> list = dao.list(map);
//ArrayList vs List
//java.util.List<BoardDTO> list = dao.list();
//게시판 목록을 달라고 호출을 하는데, 이를 ArrayList의 <BoardDTO>로 받아온다.
//계층과 계층간의 데이터를 주고받을 때 interface 타입을 쓰는 게 좋아서 List를 사용했었다.
//List를 사용해도 제네릭 클래스(인터페이스)를 의미하기 때문에 상관은 없지만, 클래스 이름이 List인 경우 혼동할 수 있다
//가장 나은 방법은 ArrayList를 사용하는 방법이다. 처음에 클래스 이름을 List로 하지 않는 것이 좋았다.
//1.5 데이터 가공
for (BoardDTO dto : list) { //데이터 가공을 위해서 dto를 하나씩 다시 꺼낸다.
//날짜 자르기 (년월일)
//String regdate = dto.getRegdate();
//regdate.substring(0, 10); //2023-10-31 10글자를 가져온다.
//dto.setRegdate(regdate.substring(0, 10)); //덮어쓰기
String subject = dto.getSubject();
//제목 길이 자르기
if (subject.length() > 23) {
subject = subject.substring(0, 23) + "..";
}
subject = subject.replace("<", "<");
subject = subject.replace(">", ">");
dto.setSubject(subject);
}
//페이징
//총 게시물 수
totalCount = dao.getTotalCount();
totalPage = (int)Math.ceil((double)totalCount / pageSize); //ceil 메소드로 소수점 이하가 발생하면 무조건 올림
//ceil이 실수형이므로 int로 다운캐스팅을 해 주어야 한다.
//페이지바
StringBuilder sb = new StringBuilder();
/*
//재사용이 안 되는 페이지바
for (int i=1; i<=totalPage; i++) {
if (i == nowPage) {
sb.append(String.format(" <a href='#!' style='color:tomato;'>%d</a> ", i));
//자기 자신을 또 눌렀을 때에는 이동할 필요가 없으므로 링크를 '#!'로 처리한다.
} else {
sb.append(String.format(" <a href='/toy/board/list.do?page=%d'>%d</a> ", i, i));
}
}
*/
//list.do?page=1
//[이전] 1 2 3 4 5 6 7 8 9 10 [다음]
//list.do?page=11
//[이전] 11 12 13 14 15 16 17 18 19 20[다음]
loop = 1; //루프 변수(10바퀴)
//n = 1; //출력 페이지 번호
n = ((nowPage - 1) / blockSize) * blockSize + 1;
//이전 10페이지
if (n == 1) {
//sb.append(String.format("<a href='#!'>[이전 %d페이지]</a>", blockSize));
} else {
sb.append(String.format("<a href='/toy/board/list.do?page=%d'>[이전 %d페이지]</a>", n - 1, blockSize));
}
//다음 10페이지 숫자
while (!(loop > blockSize || n > totalPage)) {
if (n == nowPage) {
sb.append(String.format(" <a href='#!' style='color:tomato;'>%d</a> ", n));
//자기 자신을 또 눌렀을 때에는 이동할 필요가 없으므로 링크를 '#!'로 처리한다.
} else {
sb.append(String.format(" <a href='/toy/board/list.do?page=%d'>%d</a> ", n, n));
}
loop++;
n++;
}
//다음 10페이지
if (n > totalPage) {
//sb.append(String.format("<a href='#!'>[다음 %d페이지]</a>", blockSize));
} else {
sb.append(String.format("<a href='/toy/board/list.do?page=%d'>[다음 %d페이지]</a>", n, blockSize));
}
//2.
req.setAttribute("list", list);
req.setAttribute("map", map); //list.jsp에서 검색을 하고 있는지 확인할 수 있도록 map도 함께 넘긴다.
req.setAttribute("totalCount", totalCount);
req.setAttribute("totalPage", totalPage); //페이징 작업 결과를 jsp에 넘긴다.
req.setAttribute("nowPage", nowPage);
req.setAttribute("pagebar", sb.toString());
RequestDispatcher dispatcher = req.getRequestDispatcher("/WEB-INF/views/board/list.jsp");
dispatcher.forward(req, resp);
}
}
String과 StringBuilder의 차이
- 불변성 (Immutability)
String은 불변(immutable)한 객체이므로, String 객체를 생성한 후 내용을 변경할 수 없다. 따라서 문자열을 수정할 때마다 새로운 String 객체가 생성됩니다.
StringBuilder는 가변(mutable)한 객체로, 문자열을 동적으로 수정할 수 있다. 따라서 문자열을 더하거나 수정할 때 새로운 객체를 생성하지 않고도 효율적으로 작업할 수 있다.
- 성능 (Performance)
String은 문자열을 수정할 때마다 새로운 문자열을 생성하므로, 연속적인 문자열 수정 작업에서 성능이 저하될 수 있다.
StringBuilder는 문자열을 직접 수정하므로 연산이 빠르며 메모리 사용량도 줄일 수 있다.
결론적으로, 문자열을 빈번하게 수정해야 하는 경우 StringBuilder를 사용하는 것이 효율적이며, 불변성이 중요한 경우에는 String을 사용하면 된다.
Add.java 글쓰기
package com.test.toy.board;
import java.io.IOException;
import java.io.PrintWriter;
import javax.servlet.RequestDispatcher;
import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;
import com.test.toy.board.model.BoardDTO;
import com.test.toy.board.repository.BoardDAO;
@WebServlet("/board/add.do")
public class Add extends HttpServlet {
//Add.java
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
RequestDispatcher dispatcher = req.getRequestDispatcher("/WEB-INF/views/board/add.jsp");
dispatcher.forward(req, resp);
}
@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
//AddOk.java 역할
//1. 데이터 가져오기
//2. DB 작업 > insert
//3. 피드백
HttpSession session = req.getSession(); //사용하기 편하게 세션을 미리 저장해둔다.
//1.
req.setCharacterEncoding("UTF-8");
String subject = req.getParameter("subject");
String content = req.getParameter("content");
//2.
BoardDAO dao = new BoardDAO();
BoardDTO dto = new BoardDTO();
dto.setSubject(subject);
dto.setContent(content);
dto.setId(session.getAttribute("id").toString());
//DAO는 request가 없는 일반 클래스이기 때문에 미리 꺼내서 상자 안에 담은 것이다.
int result = dao.add(dto);
//3.
if (result == 1) {
//글 작성 성공
resp.sendRedirect("/toy/board/list.do");
} else {
//글 작성 실패
PrintWriter writer = resp.getWriter();
writer.print("<script>alert('failed');history.back();</script>");
writer.close();
}
}
}
View.java: 글보기
package com.test.toy.board;
import java.io.IOException;
import javax.servlet.RequestDispatcher;
import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;
import com.test.toy.board.model.BoardDTO;
import com.test.toy.board.repository.BoardDAO;
@WebServlet("/board/view.do")
public class View extends HttpServlet {
//View.java
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
HttpSession session = req.getSession(); //세션 꺼내기
//1. 글번호 + 검색 정보 가져오기
String seq = req.getParameter("seq");
String search = req.getParameter("search");
String column = req.getParameter("column");
String word = req.getParameter("word");
//2. DB작업
BoardDAO dao = new BoardDAO();
BoardDTO dto = dao.get(seq);
if (session.getAttribute("read") != null
&& session.getAttribute("read").toString().equals("n")) {
//2.3 조회수 증가
dao.updateReadcount(seq);
//F5를 눌러도 session이 만료되지 않았기 때문에 조회수가 증가하지 않음
session.setAttribute("read", "y");
}
//2.5 데이터 조작
String content = dto.getContent();
//태그 비활성화
//<div> -> <div>
content = content.replace("<", "<");
content = content.replace(">", ">");
//개행 문자 처리
content = content.replace("\r\n", "<br>");
dto.setContent(content);
//view 처리
String subject = dto.getSubject();
subject = subject.replace("<", "<");
subject = subject.replace(">", ">");
dto.setSubject(subject);
//내용으로 검색할 경우 <span>태그를 사용하여 검색어 강조
if (search.equals("y") && column.equals("content")) {
dto.setContent(dto.getContent().replace(word, "<span style='background-color:gold;color:tomato;'>" + word + "</span>"));
}
//3. 데이터 넘겨주기
req.setAttribute("dto", dto);
RequestDispatcher dispatcher = req.getRequestDispatcher("/WEB-INF/views/board/view.jsp");
dispatcher.forward(req, resp);
}
}
Edit.java: 수정
package com.test.toy.board;
import java.io.IOException;
import java.io.PrintWriter;
import javax.servlet.RequestDispatcher;
import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;
import com.test.toy.board.model.BoardDTO;
import com.test.toy.board.repository.BoardDAO;
@WebServlet("/board/edit.do")
public class Edit extends HttpServlet {
//Edit.java
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
//view.do에서 수정하기 버튼을 클릭하면 location.href로 edit?do로 가되, seq를 넘긴다. edit?seq=?
//1. 데이터 가져오기 (seq)
//2. DB 작업 > select
//3. 결과 + JSP 호출하기
if (Auth.check(req, resp)) {
//접근할 수 없는 회원이 방문한 경우 return
return;
}
//1.
String seq = req.getParameter("seq");
//2.
BoardDAO dao = new BoardDAO();
BoardDTO dto = dao.get(seq);
String subject = dto.getSubject();
// " > \ "
//subject = subject.replace("\"", "\\\"");
subject = subject.replace("\"", """);
dto.setSubject(subject);
//3.
req.setAttribute("dto", dto);
RequestDispatcher dispatcher = req.getRequestDispatcher("/WEB-INF/views/board/edit.jsp");
dispatcher.forward(req, resp);
}
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
//EditOk.java 역할
//1. 데이터 가져오기
//2. DB 작업 > update
//3. 피드백
HttpSession session = req.getSession(); //사용하기 편하게 세션을 미리 저장해둔다.
//글 쓸 때 뿐만 아니라 수정할 때도 아이디가 필요하므로 session을 저장한다.
//1.
req.setCharacterEncoding("UTF-8");
String subject = req.getParameter("subject");
String content = req.getParameter("content");
String seq = req.getParameter("seq"); //수정할 글번호
//2.
BoardDAO dao = new BoardDAO();
BoardDTO dto = new BoardDTO();
dto.setSubject(subject);
dto.setContent(content);
dto.setId(session.getAttribute("id").toString()); //세션 사용
dto.setSeq(seq); //수정할 글번호
int result = dao.edit(dto);
//3.
if (result == 1) {
//글 작성 성공
resp.sendRedirect("/toy/board/view.do?seq=" + seq);
} else {
//글 작성 실패
PrintWriter writer = resp.getWriter();
writer.print("<script>alert('failed');history.back();</script>");
writer.close();
}
}
}
Del.java: 삭제
package com.test.toy.board;
import java.io.IOException;
import java.io.PrintWriter;
import javax.servlet.RequestDispatcher;
import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import com.test.toy.board.repository.BoardDAO;
@WebServlet("/board/del.do")
public class Del extends HttpServlet {
//Del.java
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
if (Auth.check(req, resp)) {
//접근할 수 없는 회원이 방문한 경우 return
return;
}
//1.
String seq = req.getParameter("seq");
//2.
req.setAttribute("seq", seq);
//seq를 받았지만 전달을 위해 받은 것이므로 아무것도 활용하지 않고 seq를 넘기기만 한다.
RequestDispatcher dispatcher = req.getRequestDispatcher("/WEB-INF/views/board/del.jsp");
dispatcher.forward(req, resp);
}
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
//DelOk.java 역할
//1. 데이터 가져오기(seq)
//2. DB 작업 > delete
//3. 피드백
//1.
String seq = req.getParameter("seq");
//2.
BoardDAO dao = new BoardDAO();
//글에 포함된 댓글 삭제
dao.delCommentAll(seq);
int result = dao.del(seq); //댓글 존재 > 문제 발생
//3.
if (result == 1) {
//글 삭제 성공
resp.sendRedirect("/toy/board/list.do");
} else {
//글 작성 실패
PrintWriter writer = resp.getWriter();
writer.print("<script>alert('failed');history.back();</script>");
writer.close();
}
}
}
Auth.java: 권한 확인
package com.test.toy.board;
import java.io.PrintWriter;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;
import com.test.toy.board.model.BoardDTO;
import com.test.toy.board.repository.BoardDAO;
public class Auth {
public static boolean check(HttpServletRequest req, HttpServletResponse resp) {
HttpSession session = req.getSession();
String seq = req.getParameter("seq");
BoardDAO dao = new BoardDAO();
BoardDTO dto = dao.get(seq);
//글번호를 주면 해당 글의 dto를 반환하는 get 메소드를 사용
//세션 안의 id와 dto 안의 id가 같을 경우 동일한 회원으로 본다.
//이 둘이 동일하지 않는 회원을 찾는다. (접근하면 안 되는 사람을 찾는다.)
if (!session.getAttribute("id").toString().equals(dto.getId()) && !session.getAttribute("lv").toString().equals("2")) {
try {
//페이지 주소를 바꿔서 http://localhost:8090/toy/board/edit.do?seq=10으로 들어가도 수정 페이지에 접근할 수 없다.
PrintWriter writer = resp.getWriter();
writer.print("<script>alert('No access rights');history.back();</script>");
writer.close();
} catch (Exception e) {
System.out.println("Auth.check()");
e.printStackTrace();
}
return true;
}
return false;
}
}
Dummy.java: 더미 데이터
package com.test.toy.board;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.util.Random;
import com.test.toy.DBUtil;
public class Dummy {
public static void main(String[] args) {
try {
Connection conn = null;
PreparedStatement stat = null;
conn = DBUtil.open();
//제목 랜덤, 내용 상수,
String sql = "insert into tblBoard (seq, subject, content, regdate, readcount, id) values (seqBoard.nextVal, ?, '내용', default, default, 'isaac')";
String temp = "연말정산 관련 용어 정리 근로소득 노동을 제공하여 얻은 임금입니다. 과세소득과 비과세소득을 모두 포함합니다. 과세표준 과세의 기준이 되는 항목입니다. 연말정산에서는 소득-소득공제의 값입니다. 인적공제 부양가족 수에 따라서 근로소득 금액을 공제하는 것입니다. 소득공제 세금의 근거가 되는 '소득'의 몸집을 줄이는 것입니다. (카드, 현금영수증 등) 세액공제 이미 도출된 세금 자체를 줄이는 것입니다. (연금저축, 의료비, 기부금 등) 결정세액 근로자가 납부해야 하는 최종 세금입니다. 마이너스로 나오면 환급되는 금액입니다. 경정청구 공제 받을 수 있는 항목을 누락했을 때 추가로 신고하는 것입니다. 주식 관련 용어 정리 EPS 당기 순이익을 주식수로 나눈 값입니다. 높은 수록 주가 상승 가능성이 높습니다. PER 시가총액을 예상 연간 순이익으로 나눈 값입니다. PBR 시가총액을 자기자본(순자산)으로 나눈 값입니다. ROE 당기 순이익을 순자산으로 나눈 값입니다. 높을수록 기업이 안정적입니다. 예수금 현재 주식 계좌에 들어있는 현금을 말합니다. 액면분할 기업의 주식을 일정 비율로 나누어 총 주식수를 늘리는 것을 말합니다. VI발동 단일 종목이 10% 이상 주가 변동 시 2분간 단일가 매매로 전환되는 것입니다. 세력 거대 자본과 정보력을 바탕으로 주가를 조종하는 사람들입니다. (큰손) 작전 세력들이 특정 종목을 대량으로 매입해 주가를 급등시키는 것을 말합니다. 설거지 작전 수행 뒤 개인 투자자들에게 고점에서 물량을 떠넘기는 것을 말합니다. 개미털기 주가를 급하락시켜 개인 투자자들이 팔게 만드는 것을 말합니다. 물리다 매수한 가격보다 주가가 떨어져 팔지 못하고 들고 있는 상태를 말합니다. 쩜상/하 주식시장이 개장하자마자 시초가가 바로 상한가/하한가를 찍는 것을 말합니다. 감자탕 주식 수를 줄이고 액면가를 높이는 것을 말합니다. (재무상태가 좋지 않다는 뜻입니다) 경제 관련 용어 정리 양적완화 중앙은행이 시장에 돈을 풀어 경제를 활성화 시키는 정책입니다. 테이퍼링 양적완화 규모를 천천히 줄이는 것입니다. 달러 가치가 상승하는 것을 말합니다. 달러인덱스 세계 주요 6개국의 화폐 대비 달러의 평균 가치를 표시하는 지표입니다. 모멘텀 주가의 상승과 하락에 가속도를 붙여주는 자극 또는 계기입니다. 코스피 증권시장에 상장된 기업들의 주식 가격을 종합적으로 표시한 수치입니다. 연준 기준금리를 정하는 곳인 미국연방준비제도의 줄임말입니다. 펀더멘탈 국가 거시경제지표를 말합니다. (성장률, 물가상승률, 실업률, 경상수지 등) 예금, 적금 관련 용어 정리 단리 원금에 대해서만 이율을 적용하는 것입니다. 복리 매월 발생된 이자를 원금에 합산한 금액으로 이율을 적용하는 것입니다. 보통예금 언제든지 자유롭게 입출금이 가능한 예금입니다. 일반적인 통장을 말합니다. 정기예금 기간을 미리 정해 일정 금액을 묶어놓고 만기 후 이자를 받는 것을 말합니다. 자유적금 가입기간 동안 매월 원하는 금액을 자유롭게 납입하는 적금을 말합니다. 정기적금 일정 기간동안 매월 정해진 금액을 납입하는 적금을 말합니다. 이연만기일 적금 납입 시 정해진 날짜에 내지 못해서 만기일이 밀리는 것입니다. 보험 관련 용어 정리 부담보 특정 질환이 있다면 보험 가입 시 일정 기간동안 그 질환은 보장이 안되는 것을 말합니다. 책임준비금 보험사가 계약자에게 보험금을 지급하기 위해 적립해 둔 금액을 말합니다. 예정이율 보험사의 보험료 운용 예상수익률로 보험료 책정의 기준이 됩니다. 공시이율 만기환급금, 중도해지환급금에 영향을 끼치는 이율을 말합니다. 전기납 가입 후 전 기간동안 보험료를 납입한 것을 말합니다. (납입기간=보장기간) 비례보상 여러 상품에 중복 가입하더라도 실제로 부담한 비용 이상을 보장하지 않는 다는 말입니다. 실효 보험료 미납으로 인해 계약이 정지된 상태입니다. (보험 효력이 없습니다) 부동산 관련 용어 정리 전용면적 방과 거실, 화장실, 주방 등 생활공간을 포함한 내부면적을 말합니다. 공시지가 정부에서 세금을 부과하기 위해서 산정, 고시한 토지의 가격을 말합니다. 실거래가 실제로 시장에서 거래되는 가격을 말합니다. (계약서에 적는 가격) 임장 부동산을 사려고 할 때 직접 해당 지역에 가서 탐방하는 것을 말합니다. DTI 소득에 비례해 대출한도를 결정하는 계산 비율입니다. (총부채상환비율) DSR 갚아야 할 모든 대출의 원금+이자가 소득에서 차지하는 비율을 말합니다. LTV 주택담보대출을 받을 때, 부동산 자산가치가 안정되는 비율을 말합니다.";
String[] templist = temp.split(" ");
Random rnd = new Random();
stat = conn.prepareStatement(sql);
for (int i=0; i<250; i++) {
String subject = "";
for (int j=0; j<5; j++) {
subject += templist[rnd.nextInt(templist.length)] + " ";
}
stat.setString(1, subject);
stat.executeUpdate();
//System.out.println(i);
}
stat.close();
conn.close();
} catch (Exception e) {
System.out.println("Dummy.main()");
e.printStackTrace();
}
}
}
Comment.java: 댓글
package com.test.toy.board;
import java.io.IOException;
import java.io.PrintWriter;
import java.util.ArrayList;
import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;
import org.json.simple.JSONArray;
import org.json.simple.JSONObject;
import com.test.toy.board.model.CommentDTO;
import com.test.toy.board.repository.BoardDAO;
@WebServlet("/board/comment.do")
public class Comment extends HttpServlet {
//Comment.java
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
//1. 데이터 가져오기(bseq)
//2. DB 작업 > select
//3. 목록 반환 > JSON 반환
//1.
String bseq = req.getParameter("bseq");
//2.
BoardDAO dao = new BoardDAO();
//리스트를 가져옴
ArrayList<CommentDTO> clist = dao.listComment(bseq);
//3.
JSONArray arr = new JSONArray(); //= ArrayList
for (CommentDTO dto : clist) {
//CommentDTO 1개 > JSONObject 1개
JSONObject obj = new JSONObject();
obj.put("seq", dto.getSeq());
obj.put("content", dto.getContent());
obj.put("id", dto.getId());
obj.put("regdate", dto.getRegdate());
obj.put("bseq", dto.getBseq());
obj.put("name", dto.getName());
arr.add(obj);
}//for
resp.setContentType("application/json");
resp.setCharacterEncoding("UTF-8");
PrintWriter writer = resp.getWriter();
writer.write(arr.toString()); //댓글 목록
writer.close();
}
@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
//1. 데이터 가져오기(content, bseq)
//2. 댓글 작성자 아이디 알아내기 (인증 티켓 > session)
//3. DB 작업 > insert
//4. 피드백 > ajax
HttpSession session = req.getSession();
//1.
String content = req.getParameter("content");
String bseq = req.getParameter("bseq");
//2.
String id = session.getAttribute("id").toString();
//3.
BoardDAO dao = new BoardDAO();
CommentDTO dto = new CommentDTO();
dto.setContent(content);
dto.setBseq(bseq);
dto.setId(id);
int result = dao.addComment(dto);
//4.
resp.setContentType("application/json");
JSONObject obj = new JSONObject();
obj.put("result", result);
PrintWriter writer = resp.getWriter();
writer.write(obj.toString());
writer.close();
//Ajax 입출력은 기본이므로 익숙해지는 게 좋다.
//이제 success 이벤트가 발생하므로 view.jsp에서 success 작업을 하도록 한다.
}
}
DelComment.java: 댓글 삭제
package com.test.toy.board;
import java.io.IOException;
import java.io.PrintWriter;
import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.json.simple.JSONObject;
import com.test.toy.board.repository.BoardDAO;
@WebServlet("/board/delcomment.do")
public class DelComment extends HttpServlet {
// DelComment.java
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
}
@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
String seq = req.getParameter("seq");
BoardDAO dao = new BoardDAO();
int result = dao.delComment(seq);
//피드백
resp.setContentType("application/json");
JSONObject obj = new JSONObject();
obj.put("result", result);
PrintWriter writer = resp.getWriter();
writer.write(obj.toString());
writer.close();
}
}
EditComment.java: 댓글 수정
package com.test.toy.board;
import java.io.IOException;
import java.io.PrintWriter;
import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.json.simple.JSONObject;
import com.test.toy.board.model.CommentDTO;
import com.test.toy.board.repository.BoardDAO;
@WebServlet("/board/editcomment.do")
public class EditComment extends HttpServlet {
@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
//EditComment.java
String content = req.getParameter("content");
String seq = req.getParameter("seq");
BoardDAO dao = new BoardDAO();
CommentDTO dto = new CommentDTO();
dto.setContent(content);
dto.setSeq(seq);
int result = dao.editComment(dto);
resp.setContentType("application/json");
JSONObject obj = new JSONObject();
obj.put("result", result);
PrintWriter writer = resp.getWriter();
writer.write(obj.toString());
writer.close();
}
}
6. com.test.toy.board.repository
BoardDAO.java
package com.test.toy.board.repository;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.Statement;
import java.util.ArrayList;
import java.util.HashMap;
import com.test.toy.DBUtil;
import com.test.toy.board.model.BoardDTO;
import com.test.toy.board.model.CommentDTO;
public class BoardDAO {
private Connection conn;
private Statement stat;
private PreparedStatement pstat;
private ResultSet rs;
public BoardDAO() {
this.conn = DBUtil.open();
}
public int add(BoardDTO dto) {
//queryParamNoReturn
try {
String sql = "insert into tblBoard (seq, subject, content, regdate, readcount, id) values (seqBoard.nextVal, ?, ?, default, default, ?)";
pstat = conn.prepareStatement(sql);
pstat.setString(1, dto.getSubject());
pstat.setString(2, dto.getContent());
pstat.setString(3, dto.getId());
return pstat.executeUpdate();
} catch (Exception e) {
e.printStackTrace();
}
return 0;
}
public ArrayList<BoardDTO> list(HashMap<String, String> map) {
//queryNoParamListReturn
try {
String where = "";
if (map.get("search").equals("y")) {
where = String.format("where %s like '%%%s%%'" //%로 이스케이프
, map.get("column")
, map.get("word"));
}
String sql = String.format("select * from (select a.*, rownum as rnum from vwBoard a %s) where rnum between %s and %s", where, map.get("begin"), map.get("end"));
//와일드 카드지만, view이기 때문에 컬럼은 seq, subject, id, readcount, regdate, name가 있다.
//안쪽 쿼리에 where절을 넣어서 검색 기능을 넣었다.
stat = conn.createStatement();
rs = stat.executeQuery(sql);
ArrayList<BoardDTO> list = new ArrayList<BoardDTO>();
while (rs.next()) {
BoardDTO dto = new BoardDTO();
dto.setSeq(rs.getString("seq"));
dto.setSubject(rs.getString("subject"));
dto.setId(rs.getString("id"));
dto.setRegdate(rs.getString("regdate"));
dto.setReadcount(rs.getInt("readcount"));
dto.setName(rs.getString("name"));
dto.setIsnew(rs.getInt("isnew"));
dto.setCcnt(rs.getInt("ccnt"));
//dto에 멤버 이름을 담을 변수가 없기 때문에 새로 만든다.
//상자 안에 넣을 공간이 없으면 확장하면 되는 것이다. BoardDTO에 name을 추가했다.
list.add(dto);
}
return list;
} catch (Exception e) {
e.printStackTrace();
}
return null;
}
public BoardDTO get(String seq) {
//글번호를 레코드로 해서 해당하는 데이터를 dto에 담아 dto를 돌려주는 작업
try {
String sql = "select tblBoard.*, (select name from tblUser where id = tblBoard.id) as name from tblBoard where seq = ?";
//상관서브쿼리로 이름 가져오기
pstat = conn.prepareStatement(sql);
pstat.setString(1, seq);
rs = pstat.executeQuery();
if (rs.next()) {
BoardDTO dto = new BoardDTO();
dto.setSeq(rs.getString("seq"));
dto.setSubject(rs.getString("subject"));
dto.setContent(rs.getString("content"));
dto.setRegdate(rs.getString("regdate"));
dto.setReadcount(rs.getInt("readcount"));
dto.setName(rs.getString("name"));
dto.setId(rs.getString("id"));
return dto;
}
} catch (Exception e) {
e.printStackTrace();
}
return null;
}
public void updateReadcount(String seq) {
//queryParamNoReturn
try {
String sql = "update tblBoard set readcount = readcount + 1 where seq = ?";
pstat = conn.prepareStatement(sql);
pstat.setString(1, seq);
//return pstat.executeUpdate();
pstat.executeUpdate();
//리턴하지 않으므로 return만 지운다.
} catch (Exception e) {
e.printStackTrace();
}
}
public int edit(BoardDTO dto) {
//queryParamNoReturn
//select가 아니므로 return 값이 없다.
try {
String sql = "update tblBoard set subject = ?, content = ? where seq = ?";
pstat = conn.prepareStatement(sql);
pstat.setString(1, dto.getSubject());
pstat.setString(2, dto.getContent());
pstat.setString(3, dto.getSeq());
return pstat.executeUpdate();
} catch (Exception e) {
e.printStackTrace();
}
return 0;
}
public int del(String seq) {
//queryParamNoReturn
try {
String sql = "delete from tblBoard where seq = ?";
pstat = conn.prepareStatement(sql);
pstat.setString(1, seq);
return pstat.executeUpdate();
} catch (Exception e) {
e.printStackTrace();
}
return 0;
}
public int getTotalCount() {
//queryNoParamTokenReturn
try {
String sql = "select count(*) as cnt from tblBoard";
stat = conn.createStatement();
rs = stat.executeQuery(sql);
//총 게시물 수
if (rs.next()) {
return rs.getInt("cnt");
}
} catch (Exception e) {
e.printStackTrace();
}
return 0;
}
public int addComment(CommentDTO dto) {
//queryParamNoReturn
try {
String sql = "insert into tblComment (seq, content, regdate, id, bseq) values (seqComment.nextVal, ?, default, ?, ?)";
pstat = conn.prepareStatement(sql);
pstat.setString(1, dto.getContent());
pstat.setString(2, dto.getId());
pstat.setString(3, dto.getBseq());
return pstat.executeUpdate();
} catch (Exception e) {
e.printStackTrace();
}
return 0;
}
public ArrayList<CommentDTO> listComment(String bseq) {
//queryParamListReturn
try {
String sql = "select c.*, (select name from tblUser where id = c.id) as name from tblComment c where bseq = ? order by seq desc";
//name을 alias로 추가하므로 와일드카드를 사용하려면 테이블을 alias로 c로 묶어야 한다.
pstat = conn.prepareStatement(sql);
pstat.setString(1, bseq);
rs = pstat.executeQuery();
ArrayList<CommentDTO> list = new ArrayList<CommentDTO>();
while (rs.next()) {
CommentDTO dto = new CommentDTO();
dto.setSeq(rs.getString("seq"));
dto.setContent(rs.getString("content"));
dto.setId(rs.getString("id"));
dto.setRegdate(rs.getString("regdate"));
dto.setBseq(rs.getString("bseq"));
dto.setName(rs.getString("name"));
list.add(dto);
}
return list;
} catch (Exception e) {
e.printStackTrace();
}
return null;
}
public int delComment(String seq) {
//queryParamNoReturn
try {
String sql = "delete from tblComment where seq = ?";
pstat = conn.prepareStatement(sql);
pstat.setString(1, seq);
return pstat.executeUpdate();
} catch (Exception e) {
e.printStackTrace();
}
return 0;
}
public void delCommentAll(String seq) {
//queryParamNoReturn
try {
String sql = "delete from tblComment where bseq = ?";
pstat = conn.prepareStatement(sql);
pstat.setString(1, seq);
pstat.executeUpdate();
} catch (Exception e) {
e.printStackTrace();
}
}
public int editComment(CommentDTO dto) {
//queryParamNoReturn
try {
String sql = "update tblComment set content = ? where seq = ?";
pstat = conn.prepareStatement(sql);
pstat.setString(1, dto.getContent());
pstat.setString(2, dto.getSeq());
return pstat.executeUpdate();
} catch (Exception e) {
e.printStackTrace();
}
return 0;
}
}
7. com.test.toy.board.model
BoardDTO.java
package com.test.toy.board.model;
import lombok.Data;
@Data
public class BoardDTO {
private String seq;
private String subject;
private String content;
private String regdate;
private int readcount; //산술 연산을 위해 integer로 선언
private String id;
private String name;
private int isnew;
private int ccnt;
//DTO = table이 아니다. table에 없는 데이터를 넣을 변수를 생성할 수 있다.
//단 board와 관계가 있는 것이어야 한다. (터무니 없는 데이터는 안 된다.)
}
CommentDTO.java
package com.test.toy.board.model;
import lombok.Data;
@Data
public class CommentDTO {
private String seq;
private String content;
private String regdate;
private String id;
private String bseq;
private String name; //멤버 변수로 이름을 새로 추가
}
8. com.test.toy.filter: 필터 패키지
EncodingFilter.java
package com.test.toy.filter;
import java.io.IOException;
import javax.servlet.Filter;
import javax.servlet.FilterChain;
import javax.servlet.FilterConfig;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
public class EncodingFilter implements Filter {
private String encoding;
@Override
public void init(FilterConfig filterConfig) throws ServletException {
//System.out.println("필터 생성");
this.encoding = filterConfig.getInitParameter("encoding");
}
@Override
public void doFilter(ServletRequest req, ServletResponse resp, FilterChain chain) throws IOException, ServletException {
//arg0을 req로 변경, arg1를 resp(response)로 변경 arg2를 chain으로 변경
//System.out.println("필터 동작");
//인코딩 처리
req.setCharacterEncoding("UTF-8");
//필터 호출 > 서블릿 호출
chain.doFilter(req, resp); //forward() 역할
}
@Override
public void destroy() {
//System.out.println("필터 소멸");
Filter.super.destroy();
}
}
AuthFilter.java
package com.test.toy.filter;
import java.io.IOException;
import java.io.PrintWriter;
import javax.servlet.Filter;
import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;
public class AuthFilter implements Filter {
@Override
public void doFilter(ServletRequest req, ServletResponse resp, FilterChain chain) throws IOException, ServletException {
//권한 체크
//System.out.println("권한 체크 필터");
//인증 받지 못한 사용자 내쫓기
HttpServletRequest httpReq = (HttpServletRequest)req;
HttpServletResponse httpResp = (HttpServletResponse)resp;
//형변환을 시켜서 HttpServletRequest를 ServletRequest로 가져옴
//이를 이용해서 세션을 얻어옴
HttpSession session = httpReq.getSession();
/*
if (session.getAttribute("id") == null) {
System.out.println("비회원");
} else {
System.out.println("회원: " + session.getAttribute("id"));
}
System.out.println();
*/
//익명 사용자 > 배제
//System.out.println(httpReq.getRequestURI()); //httpReq.getRequestURI(): 지금 호출한 페이지의 주소값
//필터는 모든 페이지를 부를 때마다 호출이 되기 때문에 자기가 누구를 부를 때 호출되는 상황인지를 알 수 없다.
//그래서 이런 식으로 불러내어야 한다.
//비회원이면서 add.do로 끝내는 페이지를 들어간 경우
if (session.getAttribute("id") == null) {
if (httpReq.getRequestURI().endsWith("add.do")
|| httpReq.getRequestURI().endsWith("edit.do")
|| httpReq.getRequestURI().endsWith("del.do")
|| httpReq.getRequestURI().endsWith("info.do")) {
//httpResp.sendRedirect("/toy/index.do");
PrintWriter writer = httpResp.getWriter();
writer.write("<script>alert('unauthorized');location.href='/toy/index.do';</script>");
writer.close();
return;
}
}
//필터 > 서블릿 호출
chain.doFilter(req, resp);
}
}
web.xml
<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://xmlns.jcp.org/xml/ns/javaee" xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_4_0.xsd" id="WebApp_ID" version="4.0">
<display-name>ToyProject</display-name>
<welcome-file-list>
<welcome-file>index.html</welcome-file>
<welcome-file>index.htm</welcome-file>
<welcome-file>index.jsp</welcome-file>
<welcome-file>default.html</welcome-file>
<welcome-file>default.htm</welcome-file>
<welcome-file>default.jsp</welcome-file>
</welcome-file-list>
<!-- 인코딩 필터 등록 -->
<!-- 어떤 요청이 들어오면 필터를 호출하도록 등록 -->
<filter>
<filter-name>encoding</filter-name>
<filter-class>com.test.toy.filter.EncodingFilter</filter-class>
<init-param> <!-- 넘길 때 -->
<param-name>encoding</param-name>
<param-value>UTF-8</param-value>
</init-param>
</filter>
<filter-mapping>
<filter-name>encoding</filter-name>
<url-pattern>*.do</url-pattern> <!-- 특정 add.do 뿐만 아니라 모든 do에서 적용 -->
</filter-mapping>
<!-- 권한 필터 등록 -->
<filter>
<filter-name>auth</filter-name>
<filter-class>com.test.toy.filter.AuthFilter</filter-class>
</filter>
<filter-mapping>
<filter-name>auth</filter-name>
<url-pattern>*.do</url-pattern> <!-- 특정 add.do 뿐만 아니라 모든 do에서 적용 -->
</filter-mapping>
</web-app>
필터
서블릿에 페이지를 달라고 하면 서블릿이 작업을 마치고 jsp에 결과물을 돌려준다.
필터는 서블릿 앞에 들어있다. 원래는 서블릿에 가야 할 게 일단 한번 필터를 거치고 필터에서 서블릿을 호출하고, 그 뒤에 jsp를 호출한다.
요청 필터 외에 응답 필터라고 해서 돌아갈 때 필터를 거치게끔 할 수도 있다.
여기에 인코딩 처리를 넣을 수 있다. 그럼 모든 서블릿이 미리 인코딩을 한 다음에 서블릿을 호출하기 때문에 서블릿에서는 인코딩과 관련한 처리를 따로 하지 않아도 된다.
필터 설정은 web.xml을 사용한다.
필터를 부르는 것을 등록한 다음에는 필터까지만 호출하지 않고, 그 다음에 서블릿을 호출하는 것도 설정해 주어야 한다.
🍂JSP
1. views
a. 루트
index.jsp: 시작 페이지
<%@ page language="java" contentType="text/html; charset=UTF-8"
pageEncoding="UTF-8"%>
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<%@ include file="/WEB-INF/views/inc/asset.jsp" %>
<style>
</style>
</head>
<body>
<!-- template.jsp > index.jsp -->
<%@ include file="/WEB-INF/views/inc/header.jsp" %>
<main id="main">
<h1 class="main">시작 페이지</h1>
</main>
<script>
</script>
</body>
</html>
template.jsp: 템플릿 페이지
<%@ page language="java" contentType="text/html; charset=UTF-8"
pageEncoding="UTF-8"%>
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<%@ include file="/WEB-INF/views/inc/asset.jsp" %>
<style>
</style>
</head>
<body>
<!-- template.jsp -->
<%@ include file="/WEB-INF/views/inc/header.jsp" %>
<main id="main">
<h1>콘텐츠 제목 <small>부제</small></h1>
콘텐츠 내용
</main>
<script>
</script>
</body>
</html>
b. user
register.jsp: 회원 가입
<%@ page language="java" contentType="text/html; charset=UTF-8"
pageEncoding="UTF-8"%>
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<%@ include file="/WEB-INF/views/inc/asset.jsp" %>
<style>
</style>
</head>
<body>
<!-- register.jsp -->
<%@ include file="/WEB-INF/views/inc/header.jsp" %>
<main id="main">
<h1 class="sub">회원 <small>가입하기</small></h1>
<form method="POST" action="/toy/user/register.do" enctype="multipart/form-data">
<table class="vertical">
<tr>
<th>아이디</th>
<td><input type="text" name="id" id="id" required class="short"></td>
</tr>
<tr>
<th>암호</th>
<td><input type="password" name="pw" id="pw" required class="short"></td>
</tr>
<tr>
<th>이름</th>
<td><input type="text" name="name" id="name" required class="short"></td>
</tr>
<tr>
<th>이메일</th>
<td><input type="text" name="email" id="email" required class="long"></td>
</tr>
<tr>
<th>사진</th>
<td><input type="file" name="pic" id="pic" class="long"></td>
</tr>
<tr>
<th>자기 소개</th>
<td><textarea name="intro" id="intro" class="long" required></textarea></td>
</tr>
</table>
<div>
<button type="button" class="back" onclick="location.href='/toy/index.do';">돌아가기</button>
<button type="submit" class="add primary">가입하기</button>
</div>
</form>
</main>
<script>
</script>
</body>
</html>
unregister.jsp: 회원 탈퇴
<%@ page language="java" contentType="text/html; charset=UTF-8"
pageEncoding="UTF-8"%>
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<%@ include file="/WEB-INF/views/inc/asset.jsp" %>
<style>
</style>
</head>
<body>
<%@ include file="/WEB-INF/views/inc/header.jsp" %>
<main id="main">
<h1 class="sub">회원 <small> 탈퇴하기</small></h1>
<form method="POST" action="/toy/user/unregister.do"> <!-- action에 자기자신 넣기 -> doPost가 호출됨. -->
<!-- POST를 부를 수 있는 방법이 폼 태그밖에 없어서 폼 태그로 부른다. -->
<div>
<button type="button" class="back" onclick="location.href='/toy/index.do';">돌아가기</button>
<button type="submit" class="out primary">탈퇴하기</button>
</div>
</form>
</main>
<script>
</script>
</body>
</html>
login.jsp: 로그인
<%@ page language="java" contentType="text/html; charset=UTF-8"
pageEncoding="UTF-8"%>
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<%@ include file="/WEB-INF/views/inc/asset.jsp" %>
<style>
#form-list {
display: flex;
}
#form-list form {
margin-right: 5px;
}
</style>
</head>
<body>
<!-- user/login.jsp -->
<%@ include file="/WEB-INF/views/inc/header.jsp" %>
<main id="main">
<h1 class="sub">회원 <small>로그인</small></h1>
<form method="POST" action="/toy/user/login.do">
<table class="vertical">
<tr>
<th>아이디</th>
<td><input type="text" name="id" id="id" required class="short"></td>
</tr>
<tr>
<th>암호</th>
<td><input type="password" name="pw" id="pw" required class="short"></td>
</tr>
</table>
<div>
<button type="button" class="back" onclick="location.href='/toy/index.do';">돌아가기</button>
<button type="submit" class="login primary">로그인</button>
</div>
</form>
<hr>
<div id="form-list">
<form method="POST" action="/toy/user/login.do">
<input type="hidden" name="id" value="isaac">
<input type="hidden" name="pw" value="1111">
<button type="submit" class="login primary">아이작</button>
</form>
<form method="POST" action="/toy/user/login.do">
<input type="hidden" name="id" value="sopia">
<input type="hidden" name="pw" value="1111">
<button type="submit" class="login primary">소피아</button>
</form>
<form method="POST" action="/toy/user/login.do">
<input type="hidden" name="id" value="admin">
<input type="hidden" name="pw" value="1111">
<button type="submit" class="login primary">관리자</button>
</form>
</div>
</main>
<script>
</script>
</body>
</html>
info.jsp: 회원 정보
<%@ page language="java" contentType="text/html; charset=UTF-8"
pageEncoding="UTF-8"%>
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<%@ include file="/WEB-INF/views/inc/asset.jsp" %>
<style>
#info img {
border: 1px solid #AA;
padding: 3px;
width: 150px;
height: 180px;
object-fit: cover;
}
#info tr:first-child td:first-child {
width: 170px;
text-align: center;
vertical-align: middle;
}
#info th:nth-child(2), #info th:nth-child(4) {
width: 90px;
}
#info td:nth-child(3), #info td:nth-child(5) {
width: 193px;
}
#info tr:last-child td {
padding-top: 20px;
padding-bottom: 20px;
}
</style>
</head>
<body>
<!-- info.jsp -->
<%@ include file="/WEB-INF/views/inc/header.jsp" %>
<main id="main">
<h1>회원 <small>정보 보기</small></h1>
<table id="info">
<tr>
<td rowspan="3"><img src="/toy/asset/pic/${dto.pic}"></td>
<th>이름</th>
<td>${name}</td> <!-- session안에 있는 name을 쓰려고 dto에 옮겨 담지 않았다. -->
<th>아이디</th>
<td>${id}</td>
</tr>
<tr>
<th>등급</th>
<td>${lv == 1 ? '일반회원':'관리자'}</td>
<th>이메일</th>
<td>${dto.email}</td>
</tr>
<tr>
<td colspan="4">${dto.intro}</td>
</tr>
</table>
</main>
<script>
</script>
</body>
</html>
c. inc (조각페이지)
asset.jsp: CSS, JS
<%@ page language="java" contentType="text/html; charset=UTF-8"
pageEncoding="UTF-8"%>
<!-- inc > asset.jsp -->
<title>Toy Project</title>
<link rel="shortcut icon" href="/toy/asset/favicon.ico"> <!-- 아이콘 -->
<link rel="stylesheet" href="http://pinnpublic.dothome.co.kr/cdn/example-min.css">
<link rel="stylesheet" href="/toy/asset/css/toy.css">
<script src="https://code.jquery.com/jquery-1.12.4.js"></script>
<script src="http://pinnpublic.dothome.co.kr/cdn/example-min.js"></script>
header.jsp
<%@ page language="java" contentType="text/html; charset=UTF-8"
pageEncoding="UTF-8"%>
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
<!-- inc > header.jsp -->
<header id="header">
<h1>
<c:if test="${empty id}">
<span>Toy</span>
</c:if>
<c:if test="${not empty id}">
<span>
<span class="material-symbols-outlined">toys</span>..
Toy
</span>
</c:if>
<span>Project</span>
</h1>
<nav>
<c:if test="${not empty id}">
<span style="font-size: 15px; color: #CCC; margin-right:10px;">${name}(${id})</span>
</c:if>
<a href="/toy/index.do">Home</a>
<c:if test="${empty id}">
<a href="/toy/user/register.do">Register</a>
<a href="/toy/user/login.do">Login</a>
</c:if>
<c:if test="${not empty id}">
<a href="/toy/user/info.do">Info</a>
<a href="/toy/user/unregister.do">Unregister</a>
<a href="/toy/user/logout.do">Logout</a>
</c:if>
<a href="/toy/board/list.do">Board</a>
</nav>
</header>
d. board
list.jsp
<%@ page language="java" contentType="text/html; charset=UTF-8"
pageEncoding="UTF-8"%>
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<%@ include file="/WEB-INF/views/inc/asset.jsp" %>
<style>
#list th:nth-child(1) { width: 50px; }
#list th:nth-child(2) { width: auto; }
#list th:nth-child(3) { width: 70px; }
#list th:nth-child(4) { width: 120px; }
#list th:nth-child(5) { width: 50px; }
#list td { text-align: center; }
#list td:nth-child(2) { text-align: left; }
.is-new {
font-size: 14px;
color: tomato;
}
#search-form {
text-align: center;
margin-bottom: 15px;
}
#pagebar {
text-align: center;
margin-bottom: 15px;
}
.comment-count {
font-size: 12px;
}
.comment-count::before {
content: '..';
}
</style>
</head>
<body>
<!-- list.jsp -->
<%@ include file="/WEB-INF/views/inc/header.jsp" %>
<main id="main">
<h1 class="sub">게시판
<small>
<c:if test="${map.search == 'n'}">
목록보기
</c:if>
<c:if test="${map.search == 'y'}">
검색결과
</c:if>
</small>
</h1>
<%--
<div style="text-align:right;">
<input type="number" id="page" class="short" min="1" max="${totalPage}" value="${nowPage}">
<input type="button" value="이동하기" onclick="location.href='/toy/board/list.do?page=' + $('#page').val();">
</div>
--%>
<div style="text-align:right;">
<select id="selPage" onchange="location.href='/toy/board/list.do?page=' + $(this).val();">
<c:forEach var="i" begin="1" end="${totalPage}">
<option value="${i}">${i}</option>
</c:forEach>
</select>
페이지
</div>
<%--
<div>
<input type="range" min="1" max="${totalPage}" style="width: 100%;" value="${nowPage}" onchange="location.href='/toy/board/list.do?page=' + $(this).val();">
</div>
--%>
<table id="list">
<tr>
<th>번호</th>
<th>제목</th>
<th>이름</th>
<th>날짜</th>
<th>읽음</th>
</tr>
<c:forEach items="${list}" var="dto">
<tr>
<td>${dto.seq}</td>
<td>
<a href="/toy/board/view.do?seq=${dto.seq}&search=${map.search}&column=${map.column}&word=${map.word}">${dto.subject}</a> <!-- dto의 시퀀스를 넘기면서 view로 이동 -->
<c:if test="${dto.ccnt > 0}">
<span class="comment-count">${dto.ccnt}</span>
</c:if>
<c:if test="${dto.isnew == 1}">
<span class="is-new">new</span>
</c:if>
</td>
<td>${dto.name}</td>
<td>${dto.regdate}</td>
<td>${dto.readcount}</td>
</tr>
</c:forEach>
</table>
<!-- 페이지바 -->
<div id="pagebar">${pagebar}</div>
<!-- 검색 -->
<!-- 페이지를 따로 만들지 않으므로 자기 자신을 돌려받음 -->
<!-- 대부분 폼태그의 전송방식은 POST방식을 쓰지만, 이번에는 GET 방식을 사용한다. -->
<form id="search-form" action="/toy/board/list.do" method="GET">
<select name="column">
<!-- 제목에서 검색할지, 이름으로 검색할지 등등 -->
<!-- 동시에 검색할 수 있게 하려면 기능상 구현은 쉽지만 DB가 무거워지고 Server에 부담이 되므로 잘 하지 않는다. -->
<option value="subject">제목</option>
<option value="content">내용</option>
<option value="name">이름</option>
</select>
<input type="text" name="word" class="long" required>
<input type="submit" value="검색하기">
</form>
<div>
<button type="button" class="list" onclick="location.href='/toy/board/list.do'">목록보기</button>
<c:if test="${not empty id}">
<button type="button" class="add primary" onclick="location.href='/toy/board/add.do'">글쓰기</button>
</c:if>
</div>
</main>
<script>
<c:if test="${map.search == 'y'}">
$('select[name=column]').val('${map.column}');
$('input[name=word]').val('${map.word}');
</c:if>
$('#selPage').val(${nowPage});
</script>
</body>
</html>
add.jsp
<%@ page language="java" contentType="text/html; charset=UTF-8"
pageEncoding="UTF-8"%>
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<%@ include file="/WEB-INF/views/inc/asset.jsp" %>
<style>
</style>
</head>
<body>
<!-- add.jsp -->
<%@ include file="/WEB-INF/views/inc/header.jsp" %>
<main id="main">
<h1>게시판 <small>글쓰기</small></h1>
<form method="POST" action="/toy/board/add.do">
<table class="vertical">
<tr>
<th>제목</th>
<td><input type="text" name="subject" id="subject" required class="full" autofocus></td>
</tr>
<tr>
<th>내용</th>
<td><textarea name="content" id="content" required class="full"></textarea></td>
</tr>
</table>
<div>
<button type="button" class="back" onclick="location.href='/toy/board/list.do'">돌아가기</button>
<button type="submit" class="add primary">글쓰기</button>
</div>
</form>
</main>
<script>
</script>
</body>
</html>
view.jsp
<%@ page language="java" contentType="text/html; charset=UTF-8"
pageEncoding="UTF-8"%>
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core"%>
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<%@ include file="/WEB-INF/views/inc/asset.jsp"%>
<style>
#view tr:nth-child(4) {
height: 100px;
}
#add-comment td:nth-child(1) {
width: auto;
text-align: center;
}
#add-comment td:nth-child(2) {
width: 100px;
text-align: center;
}
#list-comment td:nth-child(1) {
width: auto; /* display: table-cell; */
}
#list-comment td:nth-child(2) {
width: 170px;
text-align: center;
}
#list-comment td:nth-child(1)>div {
display: flex;
/* td에게 display table cell을 주지 않게 되면 더이상 테이블이 아니게 되므로 display를 건드리지 않도록 한다. 그래서 중간에 컨테이너를 만들어서 내용에 flex를 걸도록 한다. */
justify-content: space-between;
}
#list-comment td:nth-child(1)>div>div:nth-child(2) {
font-size: 12px;
color: #777;
}
</style>
</head>
<body>
<!-- template.jsp -->
<%@ include file="/WEB-INF/views/inc/header.jsp"%>
<main id="main">
<h1 class="sub">
게시판 <small>상세보기</small>
</h1>
<table class="vertical" id="view">
<tr>
<th>번호</th>
<td>${dto.seq}</td>
</tr>
<tr>
<th>이름</th>
<td>${dto.name}(${dto.id})</td>
</tr>
<tr>
<th>제목</th>
<td>${dto.subject}</td>
</tr>
<tr>
<th>내용</th>
<td>${dto.content}</td>
</tr>
<tr>
<th>날짜</th>
<td>${dto.regdate}</td>
</tr>
<tr>
<th>조회수</th>
<td>${dto.readcount}</td>
</tr>
</table>
<!--
하나의 폼 안에 텍스트 박스가 하나가 되면 여기서 Enter는 submit이라는 기능으로 이어지게 된다.
여기서 텍스트 박스를 하나 더 만들게 되면 Enter는 전송이 안 된다. 폼 태그 안에 텍스트 박스가 하나면 입력 받을 내용이 두개가 생기는 것이기 떄문이다.
그래서 폼 태그를 지워버린다.
-->
<!-- <form> method="POST" action="/toy/board/comment.do" Ajax 객체로 보내므로 submit을 하지 않아서 필요 없음-->
<!-- 댓글 쓰기 -->
<c:if test="${not empty id}">
<!-- 로그인을 한 사람만 댓글 쓰기 가능 -->
<table id="add-comment">
<tr>
<td><input type="text" name="comment" id="comment"
class="full"></td>
<td><button type="button" class="comment" id="btnComment">댓글쓰기</button></td>
<!-- Ajax로 클릭을 잡기 위해 버튼에 id btnComment 부여 -->
</tr>
</table>
</c:if>
<!-- 댓글 목록 -->
<table id="list-comment">
<tbody>
</tbody>
<!-- tbody가 명시적으로 있어야 자바스크립트에서 동적으로 tr태그를 만들 수 있다. -->
<!-- <tr>
<td>
<div>
<div>댓글 내용</div>
<div>2023-11-03 09:21:13</div>
</div>
</td>
<td>
<div>아이작(isaac)</div>
<div>
<button type="button" class="edit">수정</button>
<button type="button" class="del">삭제</button>
</div>
</td>
</tr> -->
</table>
<div>
<button type="button" class="back"
onclick="location.href='/toy/board/list.do';">돌아가기</button>
<!-- dto.id == id 해당 회원이 작성한 글만 수정하고 삭제할 수 있다. -->
<!-- lv == 2 관리자 등급의 계정도 글을 수정하고 삭제할 수 있다.-->
<c:if test="${not empty id && (dto.id == id || lv == 2)}">
<button type="button" class="edit"
onclick="location.href='/toy/board/edit.do?seq=${dto.seq}';">수정하기</button>
<button type="button" class="del"
onclick="location.href='/toy/board/del.do?seq=${dto.seq}';">삭제하기</button>
</c:if>
</div>
</main>
<script>
//댓글 쓰기
$('#btnComment').click(function() {
$.ajax({
type: 'POST',
url: '/toy/board/comment.do',
data: {
content: $('#comment').val(), //콘텐츠
bseq: ${dto.seq} //실제 게시물의 글번호
},
dataType: 'json',
success: function(result) {
//alert(result.result);
if (result.result == 1) {
load(); //목록 새로고침
}
$('#comment').val(''); //초기화
},
error: function(a,b,c) {
console.log(a,b,c);
}
});
});
$('#comment').keydown(function() {
if (event.keyCode == 13) { //엔터(\r)
$('#btnComment').click();
}
});
load();
//댓글 목록 가져오기 > 화면에 출력
function load() {
$.ajax({
type: 'GET',
url: '/toy/board/comment.do',
data: 'bseq=${dto.seq}', //부모글번호
dataType: 'json',
success: function(result) {
//result == 댓글 목록
//console.log(item);
$('#list-comment tbody').html(''); //기존 내용 삭제
//list-comment 테이블에 동적으로 댓글 추가
//역슬래시를 붙여야 $를 인식한다.
$(result).each((index, item) => {
//console.log(item);
let temp = `
<tr>
<td>
<div>
<div>\${item.content}</div>
<div>\${item.regdate}</div>
</div>
</td>
<td>
<div>\${item.name}(\${item.id})</div>
`;
//자바스크립트의 if이기 때문에 ${item}을 쓸 수 있다.
if (item.id == '${id}') {
temp += `
<c:if test="${not empty id}">
<div>
<button type="button" class="edit" onclick="editComment(\${item.seq});">수정</button>
<button type="button" class="del" onclick="delComment(\${item.seq});">삭제</button>
</div>
</c:if>
`;
}
temp += `
</td>
</tr>
`;
$('#list-comment tbody').append(temp);
});
},
error: function(a,b,c) {
console.log(a,b,c);
}
});
}
function editComment(seq) {
//$(event.target).parent().parent().prev().children().eq(0).children().eq(0).text());
//아버지의 할이버지의 첫 번째 형제의 첫 번째 손자
let val = $(event.target).parent().parent().prev().children().eq(0).children().eq(0).text();
$('.edit-comment').remove();
//수정한 댓글을 기존 댓글에 저장하기 위해 \${seq} 템플릿 스트링으로 seq를 넘김
let temp = `
<tr class="edit-comment">
<td><input type="text" name="ecomment" id="ecomment" class="long" value="\${val}"></td>
<td>
<button type="button" class="edit" onclick="editCommentOk(\${seq});">완료</button>
<button type="button" class="cancel" onclick="$('.edit-comment').remove();";>취소</button>
</td>
</tr>
`;
//$(event.target).parent().parent().parent().parent() > tr
$(event.target).parent().parent().parent().after(temp);
}
function editCommentOk(seq) {
//수정할 내용: ecomment
//ecomment에 id를 붙여놓았기 때문에 수정할 내용을 알 수 있다.
//alert($('#ecomment').val()); //수정할 댓글 내용
//alert(seq); //수정을 할 번호
$.ajax({
type: 'POST',
url: '/toy/board/editcomment.do',
data: {
content: $('#ecomment').val(),
seq: seq
},
dataType: 'json',
success: function(result) {
if (result.result == 1) {
load(); //새로 고침
}
},
error: function(a,b,c) {
console.log(a,b,c);
}
});
}
function delComment(seq) {
if (confirm('delete?')) {
$.ajax({
type: 'POST',
url: '/toy/board/delcomment.do',
data: 'seq=' + seq,
dataType: 'json',
success: function(result) {
if (result.result == 1) {
load(); //목록 새로고침
}
},
error: function(a,b,c) {
console.log(a,b,c);
}
});
}
}
</script>
</body>
</html>
edit.jsp
<%@ page language="java" contentType="text/html; charset=UTF-8"
pageEncoding="UTF-8"%>
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<%@ include file="/WEB-INF/views/inc/asset.jsp" %>
<style>
</style>
</head>
<body>
<!-- add.jsp > edit.jsp -->
<%@ include file="/WEB-INF/views/inc/header.jsp" %>
<main id="main">
<h1>게시판 <small>수정하기</small></h1>
<form method="POST" action="/toy/board/edit.do">
<table class="vertical">
<tr>
<th>제목</th>
<td><input type="text" name="subject" id="subject" required class="full" value="${dto.subject}"></td>
</tr>
<tr>
<th>내용</th>
<td><textarea name="content" id="content" required class="full">${dto.content}</textarea></td>
</tr>
</table>
<div>
<button type="button" class="back" onclick="location.href='/toy/board/view.do?seq=${dto.seq}'">돌아가기</button>
<button type="submit" class="edit primary">수정하기</button>
</div>
<input type="hidden" name="seq" value="${dto.seq}">
</form>
</main>
<script>
</script>
</body>
</html>
del.jsp
<%@ page language="java" contentType="text/html; charset=UTF-8"
pageEncoding="UTF-8"%>
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<%@ include file="/WEB-INF/views/inc/asset.jsp" %>
<style>
</style>
</head>
<body>
<!-- del.jsp -->
<%@ include file="/WEB-INF/views/inc/header.jsp" %>
<main id="main">
<h1>게시판 <small>삭제하기</small></h1>
<form method="POST" action="/toy/board/del.do">
<div>
<button type="button" class="back" onclick="location.href='/toy/board/view.do?seq=${seq}';">돌아가기</button>
<button type="submit" class="del primary">삭제하기</button>
</div>
<input type="hidden" name="seq" value="${seq}">
</form>
</main>
<script>
</script>
</body>
</html>