CSS
sm
<div class="container">
<h3>반응형 레이아웃 테스트</h3>
<div class="row">
<div class="col-sm-6">
<h4>제목</h4>
<p>Lorem, ipsum dolor sit amet consectetur adipisicing elit. Autem laboriosam eos nam quos explicabo tempora assumenda magnam laborum velit odit? Non quam iusto repellat doloribus mollitia veritatis delectus consectetur nesciunt?</p>
</div>
<div class="col-sm-6">
<h4>제목</h4>
<p>Lorem ipsum dolor sit amet consectetur, adipisicing elit. Minima numquam ipsum nam, alias quos culpa deleniti eum? Saepe cumque culpa quia. Ratione nobis possimus totam temporibus laborum culpa iste illo.</p>
</div>
</div>
</div>
sm md lg xl xxl
576px 768px 992px 1200px 1400px
세로 Phone | 가로 phone | table | desktop | wide |
| | | | desktop |
| | | | |
| | | | |
img-fluid
max-width:100%;<div class="row">
<div class="col-md-6 col-lg-3">
<h4>나의 사진</h4>
<img class="img-fluid" src="images/image1.png">
</div>
<div class="col-md-6 col-lg-3">
<h4>너의 사진</h4>
<img class="img-fluid" src="images/image2.png">
</div>
</div>
⭐ 가장 마지막에 적은 css가 적용
<style>
.col-6{
width:50%;
}
.col-sm-6{
width:25%
}
.col-md-1{
width:8.333%;
}
</style>
<div class="col-6 col-sm-6 col-md-1"></div>
➜ 항상 존재하지 않고 조건부로 존재하기에 해당 원리를 이용해서 반응형 페이지를 생성
JAVA & JSP
p
- `pt, pe, pb, ps`
m
- `mt, me, mb, ms`
➜ 1-5 단계(4px,8px,16px,24px,48px)
<li><a href="${pageContext.request.contextPath}/board/list.jsp"></a></li>
Set<String> whiteList = Set.of(
"/index.jsp",
"/user/loginform.jsp", "/user/login.jsp",
"/user/signup-form.jsp", "/user/signup.jsp",
"/images/", "/upload/", "/board/list.jsp"
);
CREATE TABLE board(
num NUMBER PRIMARY KEY,
writer VARCHAR2(20) NOT NULL,
title VARCHAR2(50) NOT NULL,
content CLOB,
viewCount NUMBER DEFAULT 0,
createdAt DATE DEFAULT SYSDATE
);
CREATE SEQUENCE board_seq;
BoardDto 생성
public class BoardDto {
private int num;
private String writer;
private String title;
private String content;
private int viewCount;
private String createdAt;
// setter, getter
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 getCreatedAt() {
return createdAt;
}
public void setCreatedAt(String createdAt) {
this.createdAt = createdAt;
}
}
<jsp:include page="/WEB-INF/include/navbar.jsp">
<jsp:param value="board" name="thisPage"/>
</jsp:include>
<div class="container pt-1">
<a class="btn btn-outline-primary btn-sm" href="new-form.jsp">
새 글 작성 <i class="bi bi-pencil-square"></i>
</a>
<h1>게시글 목록 입니다</h1>
<table class="table table-bordered">
<thead>
<tr>
<th>글번호</th>
<th>작성자</th>
<th>제목</th>
<th>조회수</th>
<th>작성일</th>
</tr>
</thead>
<tbody>
</tbody>
</table>
</div>
<a class="btn btn-outline-primary btn-sm" href="new-form.jsp">
새 글 작성 <i class="bi bi-pencil-square"></i>
</a>
<jsp:include page="/WEB-INF/include/resource.jsp"></jsp:include>
<div class="container">
<h1>게시글 작성 양식</h1>
<form action="save.jsp" method="post">
<div class="mb-2">
<label class="form-label" for="title">제목</label>
<input class="form-control" type="text" name="title" id="title" />
</div>
<div class="mb-2">
<label class="form-label" for="content">내용</label>
<textarea class="form-control" name="content" id="content" rows="10"></textarea>
</div>
<button class="btn btn-sm bg-primary-subtle text-white btn-sm" type="submit">작성하기</button>
</form>
</div>
BoardDao
// 자신의 참조값을 저장할 static 필드
private static BoardDao dao;
// static 초기화 블럭에서 객체 생성해서 static 필드에 저장
static {
dao = new BoardDao();
}
// 외부에서 객체 생성하지 못하도록 생성자의 접근 지정자를 private 로 지정
private BoardDao() {}
// 참조값을 리턴해주는 static 메소드 제공
public static BoardDao getInstance() {
return dao;
}
// 글 번호를 미리 select 해서 리턴해주는 메소드
public int getSequence() {
// 글 번호를 저장할 지역변수 미리 만들기
int num = 0;
Connection conn = null;
PreparedStatement pstmt = null;
ResultSet rs = null;
try {
conn = new DbcpBean().getConn();
String sql = """
SELECT board_seq.NEXTVAL AS num
FROM DUAL
""";
pstmt = conn.prepareStatement(sql);
rs = pstmt.executeQuery();
while (rs.next()) {
num = rs.getInt("num");
}
} catch (Exception e) {
e.printStackTrace();
} finally {
try {
if (rs != null)
rs.close();
if (pstmt != null)
pstmt.close();
if (conn != null)
conn.close();
} catch (Exception e) {
}
}
return num;
}
public boolean insert(BoardDto dto) {
Connection conn = null;
PreparedStatement pstmt = null;
int rowCount = 0;
try {
conn = new DbcpBean().getConn();
String sql = """
INSERT INTO board
(num, writer, title, content)
VALUES(?, ?, ?, ?)
""";
pstmt = conn.prepareStatement(sql);
pstmt.setInt(1, dto.getNum());
pstmt.setString(2, dto.getWriter());
pstmt.setString(3, dto.getTitle());
pstmt.setString(4, dto.getContent());
rowCount = pstmt.executeUpdate();
} catch (Exception e) {
e.printStackTrace();
} finally {
try {
if (pstmt != null)
pstmt.close();
if (conn != null)
conn.close();
} catch (Exception e) {
}
}
if (rowCount > 0) {
return true;
} else {
return false;
}
}
⭐ 원래 Dao의 INSERT에서 NEXTVAL로 전달한다면 글번호 전달 불가능
DB에 시퀀스 값을 select 하여 시퀀스 값을 바로 넘겨야 한다
<%
// 폼 전송되는 title 과 content
String title = request.getParameter("title");
String content = request.getParameter("content");
// 글 작성자는 세션 객체로부터 얻어낸다
String writer = (String)session.getAttribute("userName");
// DB 에 저장하기
BoardDto dto = new BoardDto();
dto.setWriter(writer);
dto.setTitle(title);
dto.setContent(content);
// 글 번호를 미리 얻어낸다
int num = BoardDao.getInstance().getSequence();
// 글 번호도 dto 에 담는다
dto.setNum(num);
boolean isSuccess = BoardDao.getInstance().insert(dto);
// 응답하기
<div class="container">
<%if(isSuccess) {%>
<script>
alert("저장 완료");
location.href = "view.jsp?num=<%=num%>";
</script>
<%} else{%>
<p>
글 저장 실패
<a href="new-form.jsp">다시 작성</a>
</p>
<%} %>
</div>
public BoardDto getByNum(int num) {
BoardDto dto = null;
Connection conn = null;
PreparedStatement pstmt = null;
ResultSet rs = null;
try {
conn = new DbcpBean().getConn();
String sql = """
SELECT writer, title, content, viewCount, createdAt
FROM board
WHERE num = ?
""";
pstmt = conn.prepareStatement(sql);
pstmt.setInt(1, num);
rs = pstmt.executeQuery();
if (rs.next()) {
dto = new BoardDto();
dto.setNum(num);
dto.setWriter(rs.getString("writer"));
dto.setTitle(rs.getString("title"));
dto.setContent(rs.getString("content"));
dto.setViewCount(rs.getInt("viewCount"));
dto.setCreatedAt(rs.getString("createdAt"));
}
} catch (Exception e) {
e.printStackTrace();
} finally {
try {
if (rs != null)
rs.close();
if (pstmt != null)
pstmt.close();
if (conn != null)
conn.close();
} catch (Exception e) {
}
}
return dto;
}
<%
// get 방식 파라미터로 전달되는 글 번호 얻어내기
int num = Integer.parseInt(request.getParameter("num"));
// DB 에서 해당 글의 자세한 정보를 얻어낸다
BoardDto dto = BoardDao.getInstance().getByNum(num);
%>
<div class="container">
<nav aria-label="breadcrumb">
<ol class="breadcrumb">
<li class="breadcrumb-item">
<a href="${pageContext.request.contextPath}/">Home</a>
</li>
<li class="breadcrumb-item">
<a href="${pageContext.request.contextPath}/board/list.jsp">Board</a>
</li>
<li class="breadcrumb-item active">Detail</li>
</ol>
</nav>
<h1>게시글 상세보기</h1>
<table class="table table-striped">
<colgroup>
<col class="col-2" />
<col class="col" />
</colgroup>
<tr>
<th>글번호</th>
<td><%=num%></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>
</table>
<div><pre><%=dto.getContent() %></pre></div>
<div class="card mt-4">
= <div class="card-header bg-light">
<strong>본문 내용</strong>
</div>
<div class="card-body p-1">
<pre class="mb-0" style="background-color: #f8f9fa; border-radius: 5px; padding: 1rem; white-space: pre-wrap; font-family: '맑은 고딕', 'Consolas', monospace;"><%= dto.getContent() %></pre>
</div>
</div>
</div>
public List<BoardDto> selectAll(){
// 글 목록을 담을 객체 생성
List<BoardDto> list = new ArrayList<>();
Connection conn = null;
PreparedStatement pstmt = null;
ResultSet rs = null;
try {
conn = new DbcpBean().getConn();
String sql = """
SELECT num, writer, title, viewCount, createdAt
FROM board
ORDER BY num DESC
""";
pstmt = conn.prepareStatement(sql);
rs = pstmt.executeQuery();
while (rs.next()) {
BoardDto dto = new BoardDto();
dto.setNum(rs.getInt("num"));
dto.setWriter(rs.getString("writer"));
dto.setTitle(rs.getString("title"));
dto.setViewCount(rs.getInt("viewCount"));
dto.setCreatedAt(rs.getString("createdAt"));
/* select 된 row 하나당 BoardDto 객체를 하나씩 생성해서
글 하나의 정보를 담고
BoardDto 객체의 참조값을 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();
} catch (Exception e) {
}
}
return list;
}
<%
// get 방식 파라미터로 전달되는 글 번호 얻어내기
int num = Integer.parseInt(request.getParameter("num"));
// DB 에서 해당 글의 자세한 정보를 얻어낸다
BoardDto dto = BoardDao.getInstance().getByNum(num);
%>
<div class="container pt-3">
<nav aria-label="breadcrumb">
<ol class="breadcrumb">
<li class="breadcrumb-item">
<a href="${pageContext.request.contextPath}/">Home</a>
</li>
<li class="breadcrumb-item">
<a href="${pageContext.request.contextPath}/board/list.jsp">Board</a>
</li>
<li class="breadcrumb-item active">Datail</li>
</ol>
</nav>
<h1>게시글 상세보기</h1>
<table class="table table-striped">
<colgroup>
<col class="col-2" />
<col class="col" />
</colgroup>
<tr>
<th>글번호</th>
<td><%=num%></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>
</table>
<div><pre><%=dto.getContent() %></pre></div>
<div class="card mt-4">
<div class="card-header bg-light">
<strong>본문 내용</strong>
</div>
<div class="card-body p-1">
<pre class="mb-0" style="background-color: #f8f9fa; border-radius: 5px; padding: 1rem; white-space: pre-wrap; font-family: '맑은 고딕', 'Consolas', monospace;"><%= dto.getContent() %></pre>
</div>
</div>
</div>
⚠️ 클라이언트가 작성한 글 제목이나 내용을 그대로 클라이언트에게 출력하는 것은
javascript 주입 공격을 받을 수 있다
따라서 해당 문자열을 escape 해서 출력하는 것이 안전하다

<div><pre><%=StringEscapeUtils.escapeHtml4(dto.getContent())%></pre></div>
<div class="card mt-4">
<div class="card-header bg-light">
<strong>본문 내용</strong>
</div>
<div class="card-body p-1">
<pre class="mb-0" style="background-color: #f8f9fa; border-radius: 5px; padding: 1rem; white-space: pre-wrap; font-family: '맑은 고딕', 'Consolas', monospace;"><%=StringEscapeUtils.escapeHtml4(dto.getContent())%></pre>
</div>
</div>
<%if(dto.getWriter().equals(userName)) {%>
<div class="text-end pt-2">
<a class="btn btn-danger btn-sm" href="delete.jsp?num=<%=dto.getNum()%>">Delete</a>
</div>
<%} %>
public boolean deleteByNum(int num) {
Connection conn = null;
PreparedStatement pstmt = null;
int rowCount = 0;
try {
conn = new DbcpBean().getConn();
String sql = """
DELETE
FROM board
WHERE num = ?
""";
pstmt = conn.prepareStatement(sql);
pstmt.setInt(1, num);
rowCount = pstmt.executeUpdate();
} catch (Exception e) {
e.printStackTrace();
} finally {
try {
if (pstmt != null)
pstmt.close();
if (conn != null)
conn.close();
} catch (Exception e) {
}
}
if (rowCount > 0) {
return true;
} else {
return false;
}
}
<%
// GET 방식 파라미터로 전달되는 글번호를 읽어와서
int num = Integer.parseInt(request.getParameter("num"));
// 글 작성자와 로그인된 userName 이 동일한지 비교해서 동일하지 않으면 에러를 응답
String writer = BoardDao.getInstance().getByNum(num).getWriter(); // 삭제할 글 작성자
String userName = (String)session.getAttribute("userName");
// 만일 글 작성자와 로그인된 userName 하고 같지 않으면
if(!writer.equals(userName)){
// 에러 페이지 응답
response.sendError(HttpServletResponse.SC_FORBIDDEN,"님의 글 지우기 불가능");
return; // 메소드를 여기서 종료
}
// DB 에서 해당 글을 삭제하고
BoardDao.getInstance().deleteByNum(num);
// 응답한다
%>
<script>
alert("삭제 완료");
location.href = "${pageContext.request.contextPath}/board/list.jsp";
</script>
<%if(dto.getWriter().equals(userName)) {%>
<div class="text-end pt-2">
<a class="btn btn-warning btn-sm" href="edit.jsp?num=<%=dto.getNum()%>">Edit</a>
</div>
<%} %>
<%
int num = Integer.parseInt(request.getParameter("num"));
BoardDto dto = BoardDao.getInstance().getByNum(num);
%>
<div class="container pt-3">
<nav aria-label="breadcrumb">
<ol class="breadcrumb">
<li class="breadcrumb-item">
<a href="${pageContext.request.contextPath}/">Home</a>
</li>
<li class="breadcrumb-item">
<a href="${pageContext.request.contextPath}/board/list.jsp">Board</a>
</li>
<li class="breadcrumb-item active">Edit</li>
</ol>
</nav>
<h1>글 수정 페이지</h1>
<form action="update.jsp" method="post">
<div>
<label class="form-label" for="num">글번호</label>
<input class="form-control" type="text" name="num" id="num"
value="<%=dto.getNum() %>" readonly/>
</div>
<div>
<label class="form-label" for="writer">작성자</label>
<input type="text" class="form-control" name="writer" id="writer"
value="<%=dto.getWriter()%>" readonly/>
</div>
<div>
<label class="form-label" for="title">제목</label>
<input type="text" class="form-control" name="title" id="title"
value="<%=dto.getTitle()%>"/>
</div>
<div>
<label class="form-label" for="content">내용</label>
<textarea class="form-control" name="content"
id="content" rows="10"><%=dto.getContent()%></textarea>
</div>
<button class="btn btn-success btn-sm">수정</button>
<button class="btn btn-danger btn-sm">Reset</button>
</form>
</div>
public boolean update(BoardDto dto) {
Connection conn = null;
PreparedStatement pstmt = null;
int rowCount = 0;
try {
conn = new DbcpBean().getConn();
String sql = """
UPDATE board
SET title=?, content=?
WHERE num=?
""";
pstmt = conn.prepareStatement(sql);
pstmt.setString(1, dto.getTitle());
pstmt.setString(2, dto.getContent());
pstmt.setInt(3, dto.getNum());
rowCount = pstmt.executeUpdate();
} catch (Exception e) {
e.printStackTrace();
} finally {
try {
if (pstmt != null)
pstmt.close();
if (conn != null)
conn.close();
} catch (Exception e) {
}
}
if (rowCount > 0) {
return true;
} else {
return false;
}
}
<%
// 폼 전송되는 내용을 읽어와서
int num = Integer.parseInt(request.getParameter("num"));
String title = request.getParameter("title");
String content = request.getParameter("content");
// 글 작성자와 로그인된 userName 이 동일한지 비교해서 동일하지 않으면 에러를 응답
String writer = BoardDao.getInstance().getByNum(num).getWriter(); // 수정할 글 작성자
String userName = (String)session.getAttribute("userName");
// 만일 글 작성자와 로그인된 userName 하고 같지 않으면
if(!writer.equals(userName)){
// 에러 페이지 응답
response.sendError(HttpServletResponse.SC_FORBIDDEN,"님의 글 수정 불가");
return; // 메소드를 여기서 종료
}
// DB 에 수정 반영하고
BoardDto dto = new BoardDto();
dto.setNum(num);
dto.setTitle(title);
dto.setContent(content);
BoardDao.getInstance().update(dto);
// 응답한다
%>
<script>
alert("수정 완료");
location.href="${pageContext.request.contextPath}/board/view.jsp?num=<%=num%>";
</script>
navbar.jsp 코드 추가
<li class="nav-item">
<a class="nav-link <%=thisPage.equals("board") ? "active":""%>"
href="${pageContext.request.contextPath }/board/list.jsp">Board</a>
</li>