국비교육(22-23)

35일차(1)/jsp(21) : 게시판 목록 보기, 새 글 작성, 글 상세보기 기능 구현

서리/Seori 2022. 11. 25. 13:54

35일차(1)/jsp(21) : 게시판 목록 보기, 새 글 작성, 글 상세보기 기능 구현

 

- 글 목록 보기 (list.jsp)

- 새 글 작성하기 (insertform.jsp, insert.jsp)

- 글 상세보기 (detail.jsp)

 

- table.sql에 이런 내용을 추가해주고, 실제 DB에서도 테이블을 생성한다.

 

<index.jsp>에 게시판 링크추가

<%@ page language="java" contentType="text/html; charset=UTF-8"
    pageEncoding="UTF-8"%>    
<%
	//session scope에 id라는 키값으로 저장된 값이 있는지 읽어와 본다.(없으면 null)
	String id=(String)session.getAttribute("id");
	
%>
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>/index.jsp</title>
</head>
<body>
	<div class="container">
		<%if(id != null) {%>
			<p>
				<a href="${pageContext.request.contextPath }/users/private/info.jsp"><%=id %></a>님 로그인중....
				<a href="${pageContext.request.contextPath }/users/logout.jsp">로그아웃</a>
			</p>
		<%}else{ %>
			<a href="${pageContext.request.contextPath }/users/loginform.jsp">로그인</a>
		<%} %>
		<h1>인덱스 페이지입니다.</h1>
		<ul>
			<li><a href="${pageContext.request.contextPath }/users/signup_form.jsp">회원가입</a></li>		
			<li><a href="${pageContext.request.contextPath }/private/study.jsp">회원전용공간(공부)</a></li>
			<li><a href="${pageContext.request.contextPath }/private/game.jsp">회원전용공간(게임)</a></li>
			<li><a href="file/list.jsp">자료실</a></li>
			<li><a href="cafe/list.jsp">글목록보기</a></li>
		</ul>
	</div>
</body>
</html>

 

/cafe/ <list.jsp>

<%@ page language="java" contentType="text/html; charset=UTF-8"
    pageEncoding="UTF-8"%>
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>/cafe/list.jsp</title>
</head>
<body>
	<div class="container">
		<a href="${pageContext.request.contextPath }/cafe/private/insertform.jsp">새글 작성</a>
		<h3>카페 글 목록입니다.</h3>
		<table>
			<thead>
				<tr>
					<th>글번호</th>
					<th>작성자</th>
					<th>제목</th>
					<th>조회수</th>
					<th>작성일</th>
				</tr>				
			</thead>
			<tbody>
				
			</tbody>		
		</table>	
	</div>
</body>
</html>

 

<a href="${pageContext.request.contextPath }/cafe/private/insertform.jsp">새글 작성</a>

 

- 새 글 작성은 회원만 할 수 있도록! private 폴더 안에 생성해준다.

- 제목을 누르면 글 자세히 보기로 이동하게 만들 예정

 

- loginfilter에 " /cafe/private/* " 도 추가하기.

 

- test.cafe.dto / test.cafe.dao 패키지 생성

 

<CafeDto>

package test.cafe.dto;

public class CafeDto {
	private int num;
	private String writer;
	private String title; //글제목
	private String content; //글내용
	private int viewCount; //조회수
	private String regdate;
	//페이징 처리할때 필요한 필드
	private int startNum;
	private int endRowNum;
	//생성자
	public CafeDto() {}
	public CafeDto(int num, String writer, String title, String content, int viewCount, String regdate, int startNum,
			int endRowNum) {
		super();
		this.num = num;
		this.writer = writer;
		this.title = title;
		this.content = content;
		this.viewCount = viewCount;
		this.regdate = regdate;
		this.startNum = startNum;
		this.endRowNum = endRowNum;
	}
	public int getNum() {
		return num;
	}
	public void setNum(int num) {
		this.num = num;
	}
	public String getWriter() {
		return writer;
	}
	public void setWriter(String writer) {
		this.writer = writer;
	}
	public String getTitle() {
		return title;
	}
	public void setTitle(String title) {
		this.title = title;
	}
	public String getContent() {
		return content;
	}
	public void setContent(String content) {
		this.content = content;
	}
	public int getViewCount() {
		return viewCount;
	}
	public void setViewCount(int viewCount) {
		this.viewCount = viewCount;
	}
	public String getRegdate() {
		return regdate;
	}
	public void setRegdate(String regdate) {
		this.regdate = regdate;
	}
	public int getStartNum() {
		return startNum;
	}
	public void setStartNum(int startNum) {
		this.startNum = startNum;
	}
	public int getEndRowNum() {
		return endRowNum;
	}
	public void setEndRowNum(int endRowNum) {
		this.endRowNum = endRowNum;
	}
	
}

 

 

<CafeDao> - getData, getList, insert 메소드

package test.cafe.dao;

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

import test.cafe.dto.CafeDto;
import test.util.DbcpBean;

public class CafeDao {

	private static CafeDao dao;
	/*
	 * static 메소드는 생성자를 호출하지 않고 클래스명ㅇ로 바로 호출을 하기 때문에
	 * 메소드 호출전에 무언가 준비작업을 하고 싶다면 static 블럭 안에서 하면 된다.
	 * static 블럭은 해당 클래스를 최초로 사용할때 한번만 실행되기 때문에
	 * 초기화 작업을 하기에 적당한 블럭이다.  
	 */
	static {
		if(dao==null) {
		dao=new CafeDao();
		}
	}
	
	private CafeDao() {}
	
	public static CafeDao getInstance() {
		return dao;
	}

	//글 하나의 정보를 리턴해주는 메소드
	public CafeDto getData(int num) {
		//필요한 객체를 담을 지역변수를 미리 만들어둔다.
		CafeDto dto=null;
		Connection conn = null;
		PreparedStatement pstmt = null;
		ResultSet rs = null;
		try {
			//Connection Pool에서 Connection 객체를 하나 얻어온다.
			conn = new DbcpBean().getConn();
			//실행할 sql문의 뼈대 구성하기
			String sql = "SELECT writer, title, content, viewCount, regdate"
					+ " FROM board_cafe"
					+ " WHERE num=?";
			//sql문의 ?에 바인딩할게 있으면 한다.			
			pstmt = conn.prepareStatement(sql);
			pstmt.setInt(1, num);
			//SELECT문을 수행하고 결과값을 받아온다.
			rs = pstmt.executeQuery();
			//반복문 돌면서 resultSet에서 필요한 값을 얻어낸다.
			if (rs.next()) {
				dto=new CafeDto();
	            dto.setNum(num);
	            dto.setWriter(rs.getString("writer"));
	            dto.setTitle(rs.getString("title"));
	            dto.setContent(rs.getString("content"));
	            dto.setViewCount(rs.getInt("viewCount"));
	            dto.setRegdate(rs.getString("regdate"));
			}
		} catch (Exception e) {
			e.printStackTrace();
		} finally {
			try {
				if (rs != null)
					rs.close();
				if (pstmt != null)
					pstmt.close();
				if (conn != null)
					conn.close(); //Connection Pool에 Connection 반납하기

			} catch (Exception e2) {
			}
		}
		return dto;
	}	

	//글 목록 리턴하기	
	public List<CafeDto> getList() {
		//회원 목록을 담을 객체 생성
		List<CafeDto> list = new ArrayList<>();

		//필요한 객체를 담을 지역변수를 미리 만들어둔다.
		Connection conn = null;
		PreparedStatement pstmt = null;
		ResultSet rs = null;
		try {
			//Connection Pool에서 Connection 객체를 하나 얻어온다.
			conn = new DbcpBean().getConn();
			//실행할 sql문의 뼈대 구성하기
			String sql = "SELECT num, writer, title, viewCount, regdate"
					+ " FROM board_cafe"
					+ " ORDER BY num DESC";
			//sql문의 ?에 바인딩할게 있으면 한다.

			pstmt = conn.prepareStatement(sql);
			//SELECT문을 수행하고 결과값을 받아온다.
			rs = pstmt.executeQuery();
			//반복문 돌면서 resultSet에서 필요한 값을 얻어낸다.
			while (rs.next()) {
				CafeDto dto=new CafeDto();
	            dto.setNum(rs.getInt("num"));
	            dto.setWriter(rs.getString("writer"));
	            dto.setTitle(rs.getString("title"));
	            dto.setViewCount(rs.getInt("viewCount"));
	            dto.setRegdate(rs.getString("regdate"));
	            //CafeDto 객체의 참조값을 List 에 누적 시키기
	            list.add(dto);
			}
		} catch (Exception e) {
			e.printStackTrace();
		} finally {
			try {
				if (rs != null)
					rs.close();
				if (pstmt != null)
					pstmt.close();
				if (conn != null)
					conn.close(); //Connection Pool에 Connection 반납하기

			} catch (Exception e2) {
			}
		}
		return list;
	}	
	
	//새글을 저장하는 메소드
	public boolean insert(CafeDto dto) {
		Connection conn = null;
		PreparedStatement pstmt = null;
		int rowCount = 0;
		try {
			conn = new DbcpBean().getConn();
			String sql = "INSERT INTO board_cafe"
					+ " (num, writer, title, content, viewCount, regdate)"
					+ " VALUES(board_cafe_seq.NEXTVAL, ?, ?, ?, 0, SYSDATE)";
			pstmt = conn.prepareStatement(sql);
			//?에 바인딩
			pstmt.setString(1, dto.getWriter());
			pstmt.setString(2, dto.getTitle());
			pstmt.setString(3, dto.getContent());
			//INSERT or UPDATE or DELETE문을 수행하고 수정,삭제,추가된 row의 개수 리턴
			rowCount = pstmt.executeUpdate();
		} catch (Exception e) {
			e.printStackTrace();
		} finally {
			try {
				if (pstmt != null)
					pstmt.close();
				if (conn != null)
					conn.close();
			} catch (Exception e2) {
			}
		}

		if (rowCount > 0) {
			return true;
		} else {
			return false;
		}
	}	
}

 

- static 블럭은 클래스가 최초로 사용될때 한번만 호출된다.

- 위에서는 static {} 상태로만 작성해도 된다.

- new를 한번만 만들어서 사용하기 위해서 작성!

 

 

- private 폴더 생성.

/cafe/private/<insertform.jsp>

<%@ page language="java" contentType="text/html; charset=UTF-8"
    pageEncoding="UTF-8"%>
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>/cafe/private/insertform.jsp</title>
</head>
<body>
	<div class="container">
		<h3>새글 작성 폼입니다.</h3>
		<form action="insert.jsp" method="post">
			<div>
				<label for="title">제목</label>
				<input type="text" name="title" id="title" />
			</div>
			<div>
				<label for="content">내용</label>
				<textarea name="content" id="content" rows="10" /></textarea>				
			</div>
			<button type="submit">저장</button>
		</form>
	</div>	
</body>
</html>

- input, textarea 요소에서 name 을 빠뜨리지 않도록 유의하기...

 

- 간단하게 만들어진 폼. 버튼을 누르면 제출된다.

 


 

- 글 목록보기 기능 구현

<CafeDao>에 메소드 추가 (insert, getList)

//글 목록 리턴하기	
public List<CafeDto> getList() {
    //회원 목록을 담을 객체 생성
    List<CafeDto> list = new ArrayList<>();

    //필요한 객체를 담을 지역변수를 미리 만들어둔다.
    Connection conn = null;
    PreparedStatement pstmt = null;
    ResultSet rs = null;
    try {
        //Connection Pool에서 Connection 객체를 하나 얻어온다.
        conn = new DbcpBean().getConn();
        //실행할 sql문의 뼈대 구성하기
        String sql = "SELECT num, writer, title, viewCount, regdate"
                + " FROM board_cafe"
                + " ORDER BY num DESC";
        //sql문의 ?에 바인딩할게 있으면 한다.

        pstmt = conn.prepareStatement(sql);
        //SELECT문을 수행하고 결과값을 받아온다.
        rs = pstmt.executeQuery();
        //반복문 돌면서 resultSet에서 필요한 값을 얻어낸다.
        while (rs.next()) {
            CafeDto dto=new CafeDto();
            dto.setNum(rs.getInt("num"));
            dto.setWriter(rs.getString("writer"));
            dto.setTitle(rs.getString("title"));
            dto.setViewCount(rs.getInt("viewCount"));
            dto.setRegdate(rs.getString("regdate"));
        }
    } catch (Exception e) {
        e.printStackTrace();
    } finally {
        try {
            if (rs != null)
                rs.close();
            if (pstmt != null)
                pstmt.close();
            if (conn != null)
                conn.close(); //Connection Pool에 Connection 반납하기

        } catch (Exception e2) {
        }
    }
    return list;
}


//새글을 저장하는 메소드
public boolean insert(CafeDto dto) {
    Connection conn = null;
    PreparedStatement pstmt = null;
    int rowCount = 0;
    try {
        conn = new DbcpBean().getConn();
        String sql = "INSERT INTO board_cafe"
                + " (num, writer, title, content, viewCount, regdate)"
                + " VALUES(board_cafe_seq.NEXTVAL, ?, ?, ?, 0, SYSDATE)";
        pstmt = conn.prepareStatement(sql);
        //?에 바인딩
        pstmt.setString(1, dto.getWriter());
        pstmt.setString(2, dto.getTitle());
        pstmt.setString(3, dto.getContent());
        //INSERT or UPDATE or DELETE문을 수행하고 수정,삭제,추가된 row의 개수 리턴
        rowCount = pstmt.executeUpdate();
    } catch (Exception e) {
        e.printStackTrace();
    } finally {
        try {
            if (pstmt != null)
                pstmt.close();
            if (conn != null)
                conn.close();
        } catch (Exception e2) {
        }
    }

    if (rowCount > 0) {
        return true;
    } else {
        return false;
    }
}

 

- viewCount 조회수는 초기값을 0으로 넣어준다.

- 아니면 테이블 생성시에 디폴트값을 0으로 지정할 수도있다.

 

List<CafeDto> list = new ArrayList<>();

- CafeDto type을 담을 수 있는 제너릭 리스트

 

CafeDto dto=new CafeDto();

- 글의 개수만큼 new되어야 한다. 그래서 while문 안에 있다. 필요한 객체를 어디서 생성하느냐도 중요!

 

 

<insert.jsp>

<%@page import="test.cafe.dto.CafeDto"%>
<%@page import="test.cafe.dao.CafeDao"%>
<%@ page language="java" contentType="text/html; charset=UTF-8"
    pageEncoding="UTF-8"%>
<%
	//폼 전송되는 내용을 추출해서	
	String title=request.getParameter("title");
	String content=request.getParameter("content");
	//CafeDto 에 담고	
	CafeDto dto=new CafeDto();
	dto.setTitle(title);
	dto.setContent(content);
	//글 작성자는 로그인된 아이디(세션영역에 저장된 id값)
	String writer=(String)session.getAttribute("id");
	dto.setWriter(writer);
	//DB에 저장하기
	boolean isSuccess=CafeDao.getInstance().insert(dto);
%>
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>insert.jsp</title>
</head>
<body>
	<div class="container">	
		<% if(isSuccess){%>
			<p>
				새글을 저장했습니다.
				<a href="${pageContext.request.contextPath }/cafe/list.jsp">확인</a>
			</p>
		<%}else{%>
			<p>
				게시물 작성 실패!
				<a href="insertform.jsp"></a>
			</p>		
		<%}%>	
	</div>
</body>
</html>

- 새 글을 저장하는 메소드. 작성자를 로그인된 id(session에 저장된 값)로 매칭시켜주는 것에 유의!

 

- textarea는 입력할때 개행기호(\r\n)도 입력을 받는다.

- 여러 줄을 입력받을 수 있는 입력도구는 대부분 개행기호를 인식한다.

 


 

- 글 내용 상세보기

 

- 제목 부분에 링크를 걸어서 자세히 보기(content) 기능을 추가한다!

 

cafe/<detail.jsp>

<%@page import="test.cafe.dao.CafeDao"%>
<%@page import="test.cafe.dto.CafeDto"%>
<%@ page language="java" contentType="text/html; charset=UTF-8"
    pageEncoding="UTF-8"%>
<%
	//자세히 보여줄 글의 번호를 읽어온다.
	int num=Integer.parseInt(request.getParameter("num"));
	//DB에서 해당 글의 정보를 읽어와서
	CafeDto dto=CafeDao.getInstance().getData(num);
	//응답한다.
%>
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>cafe/detail.jsp</title>
</head>
<body>
	<div class="container">
		<h3>글 상세 보기</h3>
		<table>
			<tr>
				<th>글번호</th>
				<td><%=dto.getNum() %></td>
			</tr>
			<tr>
				<th>작성자</th>
				<td><%=dto.getWriter() %></td>				
			</tr>
			<tr>
				<th>제목</th>
				<td><%=dto.getTitle() %></td>				
			</tr>
			<tr>
				<th>조회수</th>
				<td><%=dto.getViewCount() %></td>
			</tr>
			<tr>
				<th>작성일</th>
				<td><%=dto.getRegdate() %></td>
			</tr>
			<%-- 
				textarea를 이용해서 문자열을 입력받으면 tab기호, 공백, 개행기호도 같이 입력받는다.
				해당 기호를 재현하는 방법은 세가지가 있는데
				1. textarea에 출력하기
				2. <pre></pre> 요소안에 출력하기
				3. 개행기호를 찾아서 <br>로 대체하기
			 --%>
			<tr>
				<th>내용</th>
				<td colspan="2">
					<div><%=dto.getContent() %></div>
				</td>				
			</tr>			
		</table>
		<% 
			//로그인된 아이디가 있으면 읽어온다(null 일수도 있다)
			String id=(String)session.getAttribute("id");			
		%>
		<%-- 만일 글 작성자가 로그인된 아이디와 같다면 수정, 삭제 링크를 제공한다. --%>
		<%if(dto.getWriter().equals(id)) {%>
			<a href="private/updateform.jsp?num=<%=dto.getNum()%>">수정</a>
			<a href="javascript:" onclick="deleteConfirm()">삭제</a>
			<script>
				function deleteConfirm(){
					const isDelete=confirm("이 글을 삭제 하겠습니까?");
					if(isDelete){
						location.href="private/delete.jsp?num=<%=dto.getNum()%>";
					}
				}
			</script>		
		<%} %>
	</div>
</body>
</html>

 

<a href="detail.jsp?num=<%=tmp.getNum() %>"><%=tmp.getTitle() %></a>

- 제목을 클릭했을때, 자세히 보여줄 글 번호를 넘겨주어야한다.(get방식 전송)

 

 

 

<td><%=dto.getContent() %></td>

- 다른 내용과 동일하게 이렇게 넣으면 입력창 안의 개행기호가 무시된다.

 

** 개행기호를 처리하는 방법이 3가지 있다.

1. textarea에 출력하기
2. <pre></pre> 요소안에 출력하기
3. 개행기호를 찾아서 <br>로 대체하기

 

<td><textarea rows="10"><%=dto.getContent() %></textarea></td>

- textarea는 value 속성이 따로 없다. 이렇게 끼워넣어서 작성하면 된다.

- 'readonly' 를 넣어주면 읽기 전용으로 작성할 수 있다.(커서가 들어가지 않는다)

 

<td><pre><%=dto.getContent() %></pre></td>

- pre 요소를 사용하면 이렇게 작성된다. 사이에 끼워넣기!

 

- textarea와 pre 요소를 비교해본 것!