59-1. 사진게시판
작성 및 조회
<sementic>
모든 요소들이 div로 이루어져 있다보니 구분하기가 어렵다. => sementic 태그 사용!
header/footer/section/nacv.aside/ article 를 써서 태그로 쓸 수 있음 (직관적구조)
1. menubar.jsp
사진게시판 버튼 누르면 이동하게끔
<div class="menu"><a href="<%= contextPath %>/list.th">사진게시판</a></div>
2. thumbnailListForm.jsp
사진게시판 화면
3. ThumbnailListController.java(Servlet)
package com.kh.jsp.board.controller;
import java.io.IOException;
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 com.kh.jsp.board.model.sevice.BoardService;
import com.kh.jsp.board.model.vo.Board;
/**
* Servlet implementation class ThumbnailListController
*/
@WebServlet("/list.th")
public class ThumbnailListController extends HttpServlet {
private static final long serialVersionUID = 1L;
/**
* @see HttpServlet#HttpServlet()
*/
public ThumbnailListController() {
super();
// TODO Auto-generated constructor stub
}
/**
* @see HttpServlet#doGet(HttpServletRequest request, HttpServletResponse response)
*/
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
// 화면을 띄우기 전 => 테이블로부터 조회
ArrayList<Board> list = new BoardService().selectThumbnailList();
request.setAttribute("list", list);
request.getRequestDispatcher("views/board/thumbnailListView.jsp").forward(request, response);
}
/**
* @see HttpServlet#doPost(HttpServletRequest request, HttpServletResponse response)
*/
protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
// TODO Auto-generated method stub
doGet(request, response);
}
}
4. thumbnailListView.jsp
<%@ page language="java" contentType="text/html; charset=UTF-8"
pageEncoding="UTF-8"%>
<%@ page import="java.util.ArrayList, com.kh.jsp.board.model.vo.Board" %>
<%
ArrayList<Board> list = (ArrayList<Board>)request.getAttribute("list");
%>
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>사진아 나와라 얍</title>
<style>
.list-area{
text-align : center;
border : 1px solid white;
}
.thumbnail{
border : 1px solid white;
width : 300px;
display : inline-block;
margin : 7px;
background-color : mediumpurple;
}
.thumbnail > img{
width : 250px;
height : 200px;
padding : 10px;
}
.thumbnail:hover{
cursor : pointer;
opacity : 0.9;
}
</style>
</head>
<body>
<%@ include file="../common/menubar.jsp" %>
<div class="outer">
<br>
<h2 align="center">사진 게시판</h2>
<br>
<% if(loginUser != null) { %>
<div style="width:870px;" align="right">
<a href="<%= contextPath %>/enrollForm.th" class="btn btn-sm btn-primary">글작성</a>
</div>
<% } %>
<div class="list-area">
<% if(list.isEmpty()) { %>
<!-- 등록된 게시글이 없을 경우 -->
등록된 게시글이 없습니다. <br>
<% } else { %>
<!-- 게시글이 존재할 경우 -->
<% for(Board b : list) { %>
<div class="thumbnail" align="center">
<img src="<%= b.getTitleImg() %>">
<p>
No. <%= b.getBoardNo() %> / <%= b.getBoardTitle() %> <br>
조회수 : <%= b.getCount() %>
</p>
</div>
<% } %>
<% } %>
<!--
<div class="thumbnail" align="center">
<img src="https://i.namu.wiki/i/WkPZlLjf0g5i_o4r5j08iC9l29cT-RDp6jc1oQsheTjeVW_hJ31K2Qt_JV6PzSqR0DhsCWkWm8IxM7e-HNKBdQ.webp" alt="페페">
<p>
No. 2 / 슬픈개구리페페 <br>
조회수 : 220
</p>
</div>
<div class="thumbnail" align="center">
<img src="https://i.namu.wiki/i/WkPZlLjf0g5i_o4r5j08iC9l29cT-RDp6jc1oQsheTjeVW_hJ31K2Qt_JV6PzSqR0DhsCWkWm8IxM7e-HNKBdQ.webp" alt="페페">
<p>
No. 3 / 슬픈개구리페페 <br>
조회수 : 330
</p>
</div>
-->
</div>
</div>
</body>
</html>
5. ThumbnailEnrollFormController.java(Servlet)
글작성
package com.kh.jsp.board.controller;
import java.io.IOException;
import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
/**
* Servlet implementation class ThumbnailEnrollFormController
*/
@WebServlet("/enrollForm.th")
public class ThumbnailEnrollFormController extends HttpServlet {
private static final long serialVersionUID = 1L;
/**
* @see HttpServlet#HttpServlet()
*/
public ThumbnailEnrollFormController() {
super();
// TODO Auto-generated constructor stub
}
/**
* @see HttpServlet#doGet(HttpServletRequest request, HttpServletResponse response)
*/
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
request.getRequestDispatcher("views/board/thumbnailEnrollForm.jsp").forward(request, response);
}
/**
* @see HttpServlet#doPost(HttpServletRequest request, HttpServletResponse response)
*/
protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
// TODO Auto-generated method stub
doGet(request, response);
}
}
6. thumbnailEnrollForm.jsp
글작성 화면
input type="file"은 '파일선택'은 숨기고, 사진은 미리보기로 보여줄거임
inputFile : 현재 변화가 생긴 <input type="file"> 요소객체
inputFile.files.length : 파일을 첨부 1, 선택 취소 0
files속성 : 업로드된 파일의 정보들을 배열형식으로 여러개 묶어서 가지고 있음
let reader = new FileReader(); FileReader객체로부터 파일을 읽어들이는 메소드를 호출
reader.readAsDataURL(inputFile.files[0]); 해당 파일을 읽어들이는 순간 파일만의 고유한 겁나긴 url이 부여됨
reader.onload = function(e){
Servlet가기전에 작성자를 hidden으로 넘겨주어야 한다.
<%@ page language="java" contentType="text/html; charset=UTF-8"
pageEncoding="UTF-8"%>
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Insert title here</title>
<style>
#enroll-form > table{
border : 1px solid white;
}
#enroll-form input, #enroll-form textarea{
width : 100%;
}
</style>
</head>
<body>
<%@ include file="../common/menubar.jsp" %>
<div class="outer">
<br>
<h2 align="center">글써~</h2>
<br>
<form action="<%= contextPath%>/insert.th" id="enroll-form" method="post" enctype="multipart/form-data">
<input type="hidden" name="userNo" value="<%= loginUser.getUserNo() %>">
<table align="center" border="1">
<tr>
<th width="150">제목</th>
<td colspan="3"><input type="text" name="title" required></td>
</tr>
<tr>
<th>내용</th>
<td colspan="3">
<textarea name="content" style="resize : none" rows="10"></textarea>
</td>
</tr>
<!-- 미리보기 영역 -->
<tr>
<th>대표이미지</th>
<td colspan="3" align="center">
<img src="https://usagi-post.com/wp-content/uploads/2020/05/no-image-found-360x250-1.png" alt="대표이미지" id="titleImg" width="250" height="180">
</td>
</tr>
<tr>
<th>상세이미지</th>
<td><img src="https://usagi-post.com/wp-content/uploads/2020/05/no-image-found-360x250-1.png" alt="상세이미지1" id="contentImg1" width="150" height="110"></td>
<td><img src="https://usagi-post.com/wp-content/uploads/2020/05/no-image-found-360x250-1.png" alt="상세이미지2" id="contentImg2" width="150" height="110"></td>
<td><img src="https://usagi-post.com/wp-content/uploads/2020/05/no-image-found-360x250-1.png" alt="상세이미지3" id="contentImg3" width="150" height="110"></td>
</tr>
</table>
<br>
<div id="file-area">
<input type="file" name="file1" id="file1" required onchange="loadImg(this, 1);">
<input type="file" name="file2" id="file2" onchange="loadImg(this, 2);">
<input type="file" name="file3" id="file3" onchange="loadImg(this, 3);">
<input type="file" name="file4" id="file4" onchange="loadImg(this, 4);">
</div>
<!-- onchange : input태그(뿐 아니라 select등..)의 내용물이 변경되었을 때 발생하는 이벤트 속성-->
<script>
function loadImg(inputFile, num){
// inputFile : 현재 변화가 생긴 <input type="file"> 요소객체
// num : 몇 번째 input요소인지 확인 후 해당 영역에 미리보기를 하기위해 받아줌
// console.log(inputFile);
// console.log(inputFile.files);
// inputFile.files.length : 파일을 첨부 1, 선택 취소 0
// => 파일의 존재 유무를 알 수 있다. inputFiel.files[0]에 선택된 파일이 담겨있음
// files속성 : 업로드된 파일의 정보들을 배열형식으로 여러개 묶어서 가지고 있음
if(inputFile.files.length == 1) {// 파일이 첨부
// 선택된 파일을 읽어서 영역에 맞는 미리보기
// 파일을 읽어들일 FileReader객체 생성
let reader = new FileReader();
// console.log(inputFile.files[0]);
// FileReader객체로부터 파일을 읽어들이는 메소드를 호출
reader.readAsDataURL(inputFile.files[0]);
// 해당 파일을 읽어들이는 순간 파일만의 고유한 겁나긴 url이 부여됨
// 해당 url을 src속성의 값으로 부여할 것(attr)
// 파일 읽기가 완료되면 실행할 익명함수를 정의
reader.onload = function(e){
// e의 target => e.target => 이벤트 발생한 친구
// console.log(e.target);
// e.target.result에 파일의 url이 담긴다.
// 각 영역에 맞춰서 이미지 미리보기
switch(num){
case 1 : $('#titleImg').attr('src', e.target.result); break;
case 2 : $('#contentImg1').attr('src', e.target.result); break;
case 3 : $('#contentImg2').attr('src', e.target.result); break;
case 4 : $('#contentImg3').attr('src', e.target.result); break;
}
}
} else {
const str = 'https://usagi-post.com/wp-content/uploads/2020/05/no-image-found-360x250-1.png';
switch(num) {
case 1 : $('#titleImg').attr('src', str); break;
case 2 : $('#contentImg1').attr('src', str); break;
case 3 : $('#contentImg2').attr('src', str); break;
case 4 : $('#contentImg3').attr('src', str); break;
}
}
};
$(function(){
$('#file-area').hide();
$('#titleImg').click(function(){
$('#file1').click();
});
$('#contentImg1').click(function(){
$('#file2').click();
});
$('#contentImg2').click(function(){
$('#file3').click();
});
$('#contentImg3').click(function(){
$('#file4').click();
});
});
</script>
<br>
<div align="center">
<button type="submit" class="btn btn-sm btn-primary">작성하기</button>
<button type="reset" class="btn btn-sm btn-info">다시쓰기</button>
</div>
<br><br><br>
</form>
</div>
</body>
</html>
7. ThumbnailInsertController.java(Servlet)
파일에 properties를 보면 경로를 볼 수 있다.
package com.kh.jsp.board.controller;
import java.io.IOException;
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 org.apache.tomcat.util.http.fileupload.servlet.ServletFileUpload;
import com.kh.jsp.board.model.sevice.BoardService;
import com.kh.jsp.board.model.vo.Attachment;
import com.kh.jsp.board.model.vo.Board;
import com.kh.jsp.common.MyFileRenamePolicy;
import com.oreilly.servlet.MultipartRequest;
/**
* Servlet implementation class ThumbnailInsertController
*/
@WebServlet("/insert.th")
public class ThumbnailInsertController extends HttpServlet {
private static final long serialVersionUID = 1L;
/**
* @see HttpServlet#HttpServlet()
*/
public ThumbnailInsertController() {
super();
// TODO Auto-generated constructor stub
}
/**
* @see HttpServlet#doGet(HttpServletRequest request, HttpServletResponse response)
*/
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
// POST 방식 -> 인코딩
// 1) 인코딩
request.setCharacterEncoding("UTF-8");
// 2) "첨부파일" => multipart/form-data => 조건제시 => 서버로 파일을 올려주자
if(ServletFileUpload.isMultipartContent(request)) {
// 1) MultipartRequest객체 생성
// 1_1. 전송 용량 제한(10Mbyte)
int maxSize = 1024 * 1024 * 10;
// 1_2. 저장할 경로를 구해야함!
String savePath = request.getServletContext().getRealPath("/resources/thumbnail_upfiles/");
// 2. MultipartRequest객체 생성하면서 파일의 이름을 수정하면서 업로드!
MultipartRequest multiRequest = new MultipartRequest(request, savePath, maxSize, "UTF-8", new MyFileRenamePolicy());
// 2) multiRequest로부터 값 뽑기 => getParameter메소드 이용
String boardTitle = multiRequest.getParameter("title");
String boardContent = multiRequest.getParameter("content");
String userNo = multiRequest.getParameter("userNo");
// 3) VO로 가공
// BOARD
Board b = new Board();
b.setBoardTitle(boardTitle);
b.setBoardContent(boardContent);
b.setBoardWriter(userNo);
// Attachment => 사진게시판 작성 폼에 메인이미지 required
// => 최소한 게시글 한 개당 한 개의 첨부파일은 존재한다!!
// 여러개의 VO를 묶어서 다룰 경우 ArrayList를 쓰면 편하지 않을까?
ArrayList<Attachment> list = new ArrayList();
// 키값 : file1 ~ file4
for(int i = 1; i <= 4; i++) {
// 키값만 미리 변수!
String key = "file" + i;
// 현재 반복하고 있는 키값으로 파일을 업로드 했는지 파악!
if(multiRequest.getOriginalFileName(key) != null) { // 파일이 존재한다!!
// 첨부파일이 존재한다! Attachment객체 생성
// 필드 : 원본명, 수정명, 파일경로
Attachment at = new Attachment();
at.setOriginName(multiRequest.getOriginalFileName(key)); // 원본명
at.setChangeName(multiRequest.getFilesystemName(key)); // 수정명
at.setFilePath("resources/thumbnail_upfiles");
// 대표이미지를 FileLevel을 1로 설정
// 파일레벨
if(i == 1) {
// 대표이미지
at.setFileLevel(1);
} else {
at.setFileLevel(2);
}
list.add(at);
}
}
// 4) 서비스 요청
int result = new BoardService().insertThumbnailBoard(b, list);
// 5) 결과에 따른 응답 뷰 지정
if(result > 0) { // 성공 => list.th로 요청
request.getSession().setAttribute("alertMsg", "게시글 작성 성공 ~ !");
response.sendRedirect(request.getContextPath() + "/list.th");
}
}
}
/**
* @see HttpServlet#doPost(HttpServletRequest request, HttpServletResponse response)
*/
protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
// TODO Auto-generated method stub
doGet(request, response);
}
}
8. BoardService.java
// 사진게시판 작성
public int insertThumbnailBoard(Board b, ArrayList<Attachment> list) {
Connection conn = getConnection();
// 1개의 트랜잭션에서 최소 2개 최대 5개의 INSERT가 필요함!
int result1 = new BoardDao().insertThumbnailBoard(conn, b);
int result2 = new BoardDao().insertAttachmentList(conn, list);
if((result1 * result2) > 0) {
commit(conn);
} else {
rollback(conn);
}
close(conn);
return (result1 * result2);
}
// 사진게시판 조회
public ArrayList<Board> selectThumbnailList(){
Connection conn = getConnection();
ArrayList<Board> list = new BoardDao().selectThumbnailList(conn);
close(conn);
return list;
}
9. BoardDao.java
// 사진게시판
// 작성
public int insertThumbnailBoard(Connection conn, Board b) {
int result = 0;
PreparedStatement pstmt = null;
String sql = prop.getProperty("insertThumbnailBoard");
try {
pstmt = conn.prepareStatement(sql);
pstmt.setString(1, b.getBoardTitle());
pstmt.setString(2, b.getBoardContent());
pstmt.setInt(3, Integer.parseInt(b.getBoardWriter()));
result = pstmt.executeUpdate();
} catch (SQLException e) {
e.printStackTrace();
} finally {
close(pstmt);
}
return result;
}
public int insertAttachmentList(Connection conn, ArrayList<Attachment> list) {
int result = 1;
PreparedStatement pstmt = null;
String sql = prop.getProperty("insertAttachmentList");
try {
// 리스트의 요소 개수만큼 Attachment 테이블에 행을 추가
for(Attachment at : list) {
// 반복할 때마다 미완성된 SQL문을 담은 pstmt객체 생성
pstmt = conn.prepareStatement(sql);
// 완성형태로 만들기 = > at에서 뽑아서
pstmt.setString(1, at.getOriginName());
pstmt.setString(2, at.getChangeName());
pstmt.setString(3, at.getFilePath());
pstmt.setInt(4, at.getFileLevel());
// 실행
result *= pstmt.executeUpdate();
}
} catch (SQLException e) {
e.printStackTrace();
} finally {
close(pstmt);
}
return result;
// 혹은 int result = 0세팅해놀고 return list.size() == result ? 1 : 0;
}
// 사진게시판 조회
public ArrayList<Board> selectThumbnailList(Connection conn){
ArrayList<Board> list = new ArrayList();
PreparedStatement pstmt = null;
ResultSet rset = null;
String sql = prop.getProperty("selectThumbnailList");
try {
pstmt = conn.prepareStatement(sql);
rset = pstmt.executeQuery();
while(rset.next()) {
Board b = new Board();
b.setBoardNo(rset.getInt("BOARD_NO"));
b.setBoardTitle(rset.getString("BOARD_TITLE"));
b.setCount(rset.getInt("COUNT"));
// 번호, 제목, 조회수, 저장경로, 바뀐이름
// String img = rset.getString("FILE_PATH") + "/" + rset.getString("CHANGE_NAME");
// b.setTitleImg(img);
// String은 불변객체 => String 만들고 붙이고.. 힘듦 => DB에서 가져오는 건 어떨까(||로 붙여버려서 가져오기)
b.setTitleImg(rset.getString("TITLEIMG"));
list.add(b);
}
} catch (SQLException e) {
e.printStackTrace();
} finally {
close(rset);
close(pstmt);
}
return list;
}
10. sql
<entry key="insertThumbnailBoard">
INSERT
INTO
BOARD
(
BOARD_NO,
BOARD_TYPE,
BOARD_TITLE,
BOARD_CONTENT,
BOARD_WRITER
)
VALUES
(
SEQ_BNO.NEXTVAL,
2,
?,
?,
?
)
</entry>
<entry key="insertAttachmentList">
INSERT
INTO
ATTACHMENT
(
FILE_NO,
REF_BNO,
ORIGIN_NAME,
CHANGE_NAME,
FILE_PATH,
FILE_LEVEL
)
VALUES
(
SEQ_FNO.NEXTVAL,
SEQ_BNO.CURRVAL,
?,
?,
?,
?
)
</entry>
<entry key="selectThumbnailList">
SELECT
BOARD_NO,
BOARD_TITLE,
COUNT,
FILE_PATH||'/'||CHANGE_NAME TITLEIMG
FROM
BOARD
JOIN
ATTACHMENT ON (BOARD_NO = REF_BNO)
WHERE
BOARD_TYPE = 2
AND
FILE_LEVEL = 1
AND
BOARD.STATUS = 'Y'
ORDER
BY
BOARD_NO DESC
</entry>
11. Board.java
// 추가
private String titleImg;
public String getTitleImg() {
return titleImg;
}
public void setTitleImg(String titleImg) {
this.titleImg = titleImg;
}