07_Framework
글쓰기 버튼 클릭 시, 글쓰는 화면으로 이동함
<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%>
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
<%-- map에 저장된 값들을 각각 변수에 저장 --%>
<c:set var="pagination" value="${map.pagination}" />
<c:set var="boardList" value="${map.boardList}" />
<c:set var="boardName" value="${boardTypeList[boardCode-1].BOARD_NAME}"/>
<!DOCTYPE html>
<html lang="ko">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>${boardName}</title>
<link rel="stylesheet" href="/resources/css/board/boardList-style.css">
</head>
<body>
<main>
<jsp:include page="/WEB-INF/views/common/header.jsp"/>
<section class="board-list">
<h1 class="board-name">${boardName}</h1>
<div class="list-wrapper">
<table class="list-table">
<thead>
<tr>
<th>글번호</th>
<th>제목</th>
<th>작성자</th>
<th>작성일</th>
<th>조회수</th>
<th>좋아요</th>
</tr>
</thead>
<tbody>
<c:choose>
<%-- 조회된 게시글 목록이 비어있거나 null 경우 --%>
<c:when test="${empty boardList}">
<!-- 게시글 목록 조회 결과가 비어있다면 -->
<tr>
<th colspan="6">게시글이 존재하지 않습니다.</th>
</tr>
</c:when>
<c:otherwise>
<c:forEach items="${boardList}" var="board">
<!-- 게시글 목록 조회 결과가 있다면 -->
<tr>
<td>${board.boardNo}</td>
<td>
<%-- 썸네일이 있을 경우 --%>
<c:if test="${not empty board.thumbnail}">
<img class="list-thumbnail" src="${board.thumbnail}">
</c:if>
<%-- ${boardCode} : @Pathvariable 로 request scope에 추가된 값 --%>
<a href="/board/${boardCode}/${board.boardNo}?cp=${pagination.currentPage}">${board.boardTitle}</a>
[${board.commentCount}]
</td>
<td>${board.memberNickname}</td>
<td>${board.boardCreateDate}</td>
<td>${board.readCount}</td>
<td>${board.likeCount}</td>
</tr>
</c:forEach>
</c:otherwise>
</c:choose>
</tbody>
</table>
</div>
<div class="btn-area">
<!-- 로그인 상태일 경우 글쓰기 버튼 노출 -->
<c:if test="${not empty loginMember}">
<button id="insertBtn">글쓰기</button>
</c:if>
</div>
<div class="pagination-area">
<ul class="pagination">
<!-- 첫 페이지로 이동 -->
<li><a href="/board/${boardCode}?cp=1"><<</a></li>
<!-- 이전 목록 마지막 번호로 이동 -->
<li><a href="/board/${boardCode}?cp=${pagination.prevPage}"><</a></li>
<!-- 특정 페이지로 이동 -->
<c:forEach var="i" begin="${pagination.startPage}" end="${pagination.endPage}" step="1">
<c:choose>
<%-- 현재 보고있는 페이지 --%>
<c:when test="${i == pagination.currentPage}">
<li><a class="current">${i}</a></li>
</c:when>
<%-- 현재 페이지를 제외한 나머지 --%>
<c:otherwise>
<li><a href="/board/${boardCode}?cp=${i}">${i}</a></li>
</c:otherwise>
</c:choose>
</c:forEach>
<!-- 다음 목록 시작 번호로 이동 -->
<li><a href="/board/${boardCode}?cp=${pagination.nextPage}">></a></li>
<!-- 끝 페이지로 이동 -->
<li><a href="/board/${boardCode}?cp=${pagination.maxPage}">>></a></li>
</ul>
</div>
<!-- 검색창 -->
<form action="#" method="get" id="boardSearch">
<select name="key" id="searchKey">
<option value="t">제목</option>
<option value="c">내용</option>
<option value="tc">제목+내용</option>
<option value="w">작성자</option>
</select>
<input type="text" name="query" id="searchQuery" placeholder="검색어를 입력해주세요.">
<button>검색</button>
</form>
</section>
</main>
<!-- 썸네일 클릭 시 모달창 출력 -->
<div class="modal">
<span id="modalClose">×</span>
<img id="modalImage" src="/resources/images/user.png">
</div>
<jsp:include page="/WEB-INF/views/common/footer.jsp"/>
<script src="/resources/js/board/boardList.js"></script>
</body>
</html>
const insertBtn = document.querySelector("#insertBtn");
// 글쓰기 버튼 클릭 시
if(insertBtn != null) { // 로그인 여부에 따라 insertBtn이 있는가 없는가에 대한 예외처리
insertBtn.addEventListener("click", () => {
// 해당 주소로 요청을 보내주세요
// JS 객체중 location
// location.href = "주소"
// 해당 주소로 요청 (GET 방식)
location.href = `/board2/${location.pathname.split("/")[2]}/insert`;
// /board2/1/insert
});
}
package edu.kh.project.board.controller;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.SessionAttributes;
import edu.kh.project.board.model.service.BoardService2;
@Controller
@RequestMapping("/board2")
@SessionAttributes({"loginMember"})
public class BoardController2 {
@Autowired
private BoardService2 service;
// 게시글 작성 화면 전환
@GetMapping("/{boardCode:[0-9]+}/insert")
public String boardInsert(@PathVariable("boardCode") int boardCode) {
// @PathVariable : 주소 값 가져오기 + request scope에 값 올리기
return "board/boardWrite";
}
}
로그인 후 글쓰기 버튼 클릭 시,
해당 화면으로 넘어감
<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%>
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
<%@ taglib prefix="fn" uri="http://java.sun.com/jsp/jstl/functions" %>
<c:forEach items="${boardTypeList}" var="boardType">
<c:if test="${boardType.BOARD_CODE == boardCode}" >
<c:set var="boardName" value="${boardType.BOARD_NAME}"/>
</c:if>
</c:forEach>
<!DOCTYPE html>
<html lang="ko">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>${boardName}</title>
<link rel="stylesheet" href="/resources/css/board/boardWrite-style.css">
</head>
<body>
<main>
<jsp:include page="/WEB-INF/views/common/header.jsp"/>
<form action="/board2/${boardCode}/insert" method="POST" class="board-write" id="boardWriteFrm"
enctype="multipart/form-data">
<h1 class="board-name">${boardName}</h1>
<!-- 제목 -->
<h1 class="board-title">
<input type="text" name="boardTitle" placeholder="제목" value="">
</h1>
<!-- 썸네일 영역 -->
<h5>썸네일</h5>
<div class="img-box">
<div class="boardImg thumbnail">
<label for="img0">
<img class="preview" src="">
</label>
<input type="file" name="images" class="inputImage" id="img0" accept="image/*">
<span class="delete-image">×</span>
</div>
</div>
<!-- 업로드 이미지 영역 -->
<h5>업로드 이미지</h5>
<div class="img-box">
<div class="boardImg">
<label for="img1">
<img class="preview" src="">
</label>
<input type="file" name="images" class="inputImage" id="img1" accept="image/*">
<span class="delete-image">×</span>
</div>
<div class="boardImg">
<label for="img2">
<img class="preview" src="">
</label>
<input type="file" name="images" class="inputImage" id="img2" accept="image/*">
<span class="delete-image">×</span>
</div>
<div class="boardImg">
<label for="img3">
<img class="preview" src="">
</label>
<input type="file" name="images" class="inputImage" id="img3" accept="image/*">
<span class="delete-image">×</span>
</div>
<div class="boardImg">
<label for="img4">
<img class="preview" src="">
</label>
<input type="file" name="images" class="inputImage" id="img4" accept="image/*">
<span class="delete-image">×</span>
</div>
</div>
<!-- 내용 -->
<div class="board-content">
<textarea name="boardContent"></textarea>
</div>
<!-- 버튼 영역 -->
<div class="board-btn-area">
<button type="submit" id="writebtn">등록</button>
</div>
</form>
</main>
<jsp:include page="/WEB-INF/views/common/footer.jsp"/>
<script src="/resources/js/board/boardWrite.js"></script>
</body>
</html>
XSS 공격
스크립트를 조작해서 만드는 것
ex) <script>alert("무한반복");</script>
package edu.kh.project.board.controller;
import java.io.IOException;
import java.util.List;
import javax.servlet.http.HttpSession;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.SessionAttribute;
import org.springframework.web.bind.annotation.SessionAttributes;
import org.springframework.web.multipart.MultipartFile;
import org.springframework.web.servlet.mvc.support.RedirectAttributes;
import edu.kh.project.board.model.dto.Board;
import edu.kh.project.board.model.service.BoardService2;
import edu.kh.project.member.model.dto.Member;
@Controller
@RequestMapping("/board2")
@SessionAttributes({"loginMember"})
public class BoardController2 {
@Autowired
private BoardService2 service;
// 게시글 작성 화면 전환
@GetMapping("/{boardCode:[0-9]+}/insert")
public String boardInsert(@PathVariable("boardCode") int boardCode) {
// @PathVariable : 주소 값 가져오기 + request scope에 값 올리기
return "board/boardWrite";
}
// 게시글 작성
@PostMapping("/{boardCode:[0-9]+}/insert")
public String boardInsert(
@PathVariable("boardCode") int boardCode
, Board board // 커맨드 객체 (필드에 파라미터 담겨있음!)
, @RequestParam(value="images", required = false) List<MultipartFile> images
, @SessionAttribute("loginMember") Member loginMember
, RedirectAttributes ra
, HttpSession session
) throws IllegalStateException, IOException {
// 파라미터 : 제목, 내용, 파일(0~5개)
// 파일 저장 경로 : HttpSession
// 세션 : 로그인한 회원의 번호
// 리다이렉트 시 데이터 전달 : RedirectAttributes ra (message)
/* List<MultipartFile>
* - 업로드된 이미지가 없어도 List에 MultipartFile 요소는 존재함.
*
* - 단, 업로드된 이미지가 없는 MultipartFile 요소는
* 파일 크기(size)가 0 또는 파일명(getOriginalFileName()) "" 빈칸
*
* */
// 1. 로그인한 회원 번호를 얻어와 board에 세팅
board.setMemberNo(loginMember.getMemberNo());
// 2. boardCode도 board에 세팅
board.setBoardCode(boardCode);
// 3. 업로드 된 이미지 서버에 실제로 저장되는 경로
// + 웹에서 요청 시 이미지를 볼 수 있는 경로 (웹 접근 경로)
String webPath = "/resources/images/board/";
String filePath = session.getServletContext().getRealPath(webPath);
// 게시글 삽입 서비스 호출 후 삽입된 게시글 번호 반환 받기
int boardNo = service.boardInsert(board, images, webPath, filePath);
// 게시글 삽입 성공 시
// -> 방금 삽입한 게시글의 상세 조회 페이지 리다이렉트
// -> /board/{boardCode}/{boardNo}
String message = null;
String path = "redirect:";
if(boardNo > 0) {
message = "게시글이 등록 되었습니다";
path += "/board/" + boardCode + "/" + boardNo;
} else {
message = "게시글 등록 실패................";
path += "insert";
}
ra.addFlashAttribute("message", message);
return path;
}
}
package edu.kh.project.board.model.dto;
import java.util.List;
import lombok.Getter;
import lombok.Setter;
import lombok.ToString;
// DTO(Data Transfer Object) : 데이터 전달용 객체
// - '꼭 테이블 컬럼과 똑같이 만들 필요 없음!'
// --> CRUD 시 필요한 데이터를 담을 수 있는 형태
@Getter
@Setter
@ToString
public class Board {
private int boardNo;
private String boardTitle;
private String boardContent;
private String boardCreateDate;
private String boardUpdateDate;
private int readCount;
private int boardCode;
// 서브쿼리
private int commentCount; // 댓글 수
private int likeCount; // 좋아요 수
// 회원 join
private String memberNickname;
private int memberNo;
private String profileImage;
// BOARD_IMG 테이블 join
private String thumbnail;
// 이미지 목록
private List<BoardImage> imageList;
// 댓글 목록
private List<Comment> commentList;
}
package edu.kh.project.board.model.service;
import java.io.IOException;
import java.util.List;
import org.springframework.web.multipart.MultipartFile;
import edu.kh.project.board.model.dto.Board;
public interface BoardService2 {
/** 게시글 삽입
* @param board
* @param images
* @param webPath
* @param filePath
* @return boardNo
*/
int boardInsert(Board board, List<MultipartFile> images,
String webPath, String filePath)
throws IllegalStateException, IOException;
}
package edu.kh.project.board.model.service;
import java.io.File;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import edu.kh.project.board.model.exception.FileUploadException;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.web.multipart.MultipartFile;
import edu.kh.project.board.model.dao.BoardDAO2;
import edu.kh.project.board.model.dto.Board;
import edu.kh.project.board.model.dto.BoardImage;
import edu.kh.project.common.utility.Util;
@Service
public class BoardServiceImpl2 implements BoardService2{
@Autowired
private BoardDAO2 dao;
// 게시글 삽입
@Transactional(rollbackFor = Exception.class)
@Override
public int boardInsert(Board board, List<MultipartFile> images, String webPath, String filePath)
throws IllegalStateException, IOException {
// 0. XSS 방지 처리
board.setBoardContent( Util.XSSHandling( board.getBoardContent() ) /*XSS 방지 처리를 한 내용*/ );
board.setBoardTitle( Util.XSSHandling( board.getBoardTitle() )/*XSS 방지 처리를 한 내용*/ );
// 1. BOARD 테이블 INSERT 하기 (제목, 내용, 작성자, 게시판 코드)
// -> boardNo (시퀀스로 생성한 번호) 반환 받기
int boardNo = dao.boardInsert(board);
// 2. 게시글 삽입 성공 시
// 업로드된 이미지가 있다면 BOARD_IMG 테이블에 삽입하는 DAO 호출
if(boardNo > 0) { // 게시글 삽입 성공 시
// List<MultipartFile> images
// -> 업로드된 파일이 담긴 객체 MultipartFile이 5개 존재
// -> 단, 업로드된 파일이 없어도 MultipartFile 객체는 존재
// 실제 업로드된 파일의 정보를 기록할 List
List<BoardImage> uploadList = new ArrayList<BoardImage>();
// images에 담겨있는 파일 중 실제 업로드된 파일만 분류
for(int i = 0; i < images.size(); i++) {
// i번째 요소에 업로드된 파일이 있다면
if(images.get(i).getSize() > 0) {
BoardImage img = new BoardImage();
// img 에 파일 정보를 담아서 uploadList 에 추가
img.setImagePath(webPath); // 웹 접근 경로
img.setBoardNo(boardNo); // 게시글 번호
img.setImageOrder(i); // 이미지 순서
// 파일 원본명
String fileName = images.get(i).getOriginalFilename();
img.setImageOriginal(fileName); // 원본명
img.setImageReName( Util.fileRename(fileName) ); // 변경명
uploadList.add(img);
}
} // 분류 for 문 종료
// 분류 작업 후 uploadList가 비어 있지 않은 경우
// == 업로드한 파일이 있다
if( !uploadList.isEmpty() ) {
// BOARD_IMG 테이블에 insert 하는 dao 호출
int result = dao.insertImageList(uploadList);
// result == 삽입된 행의 개수
// 전체 insert 성공 여부 확인
// 삽입된 행의 개수와 uploadList 의 개수가 같다면
// == 전체 insert 성공
if(result == uploadList.size()) {
// 서버에 파일을 저장 (transferTo)
for(int i = 0; i < uploadList.size(); i++) {
int index = uploadList.get(i).getImageOrder();
String rename = uploadList.get(i).getImageReName();
images.get(index).transferTo( new File(filePath + rename) );
}
} else { // 일부 또는 전체 insert 실패
// rollback 필요
// @Transactional(rollbackFor = Exception.class)
// -> 예외가 발생 해야지만 롤백
// [결론]
// 예외를 강제 발생 시켜서 rollback 해야된다
// 사용자 정의 예외 만들어 발생시키자!
throw new FileUploadException();
}
}
}
return boardNo;
}
}
package edu.kh.project.board.model.exception;
// unchecked exception : 예외처리 선택 (RuntimeException)
// checked exception : 예외처리 필수
public class FileUploadException extends RuntimeException{
public FileUploadException() {
super("파일 업로드 중 예외 발생");
}
public FileUploadException(String message) {
super(message);
}
}
package edu.kh.project.common.utility;
import java.text.SimpleDateFormat;
public class Util {
public static String fileRename(String originFileName) {
SimpleDateFormat sdf = new SimpleDateFormat("yyyyMMddHHmmss");
String date = sdf.format(new java.util.Date(System.currentTimeMillis()));
int ranNum = (int) (Math.random() * 100000); // 5자리 랜덤 숫자 생성
String str = "_" + String.format("%05d", ranNum);
String ext = originFileName.substring(originFileName.lastIndexOf("."));
return date + str + ext;
}
// Cross Site Scripting (XSS) 방지 처리
// - 웹 애플리케이션에서 발생하는 취약점
// - 권한이 없는 사용자가 사이트에 악의적인 스크립트를 작성하는 것
// ex) 게시판, 웹 메일 -> 자바스크립트
// XSS 공격은 입력값에 대한 검증이 이뤄지지 않아 발생하는 취약점
// -> 모든 사용자에 대한 모든 입력값에 대하여 필터링 해야함.
// '<' '>' 와 같은 기호들을 엔티티코드 변환해줌
// < - <
// > - >
// & - &
// " - "
public static String XSSHandling(String content) {
content = content.replaceAll("&", "&");
content = content.replaceAll("<", "<");
content = content.replaceAll(">", ">");
content = content.replaceAll("\"", """);
return content;
}
}
package edu.kh.project.board.model.dao;
import java.util.List;
import org.mybatis.spring.SqlSessionTemplate;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Repository;
import edu.kh.project.board.model.dto.Board;
import edu.kh.project.board.model.dto.BoardImage;
@Repository
public class BoardDAO2 {
@Autowired
private SqlSessionTemplate sqlSession;
/** 게시글 삽입
* @param board
* @return boardNo
*/
public int boardInsert(Board board) {
int result = sqlSession.insert("boardMapper.boardInsert", board);
// 삽입 성공 시
if(result > 0) result = board.getBoardNo();
return result;
}
/** 이미지 리스트 삽입
* @param uploadList
* @return result
*/
public int insertImageList(List<BoardImage> uploadList) {
return sqlSession.insert("boardMapper.insertImageList", uploadList);
}
}
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd" >
<mapper namespace="boardMapper">
<!-- Board DTO에 대한 resultMap -->
<resultMap type="Board" id="board_rm">
<id property="boardNo" column="BOARD_NO"/>
<result property="boardTitle" column="BOARD_TITLE"/>
<result property="boardContent" column="BOARD_CONTENT"/>
<result property="boardCreateDate" column="B_CREATE_DATE"/>
<result property="boardUpdateDate" column="B_UPDATE_DATE"/>
<result property="readCount" column="READ_COUNT"/>
<result property="commentCount" column="COMMENT_COUNT"/>
<result property="likeCount" column="LIKE_COUNT"/>
<result property="memberNickname" column="MEMBER_NICKNAME"/>
<result property="memberNo" column="MEMBER_NO"/>
<result property="profileImage" column="PROFILE_IMG"/>
<result property="thumbnail" column="THUMBNAIL"/>
<!-- collection 태그
select로 조회된 결과를 컬렉션(List)에 담아
지정된 필드에 세팅
property : List를 담을 DTO의 필드명
select : 실행할 select의 id
column : 조회 결과 중 지정된 컬럼의 값을 파라미터로 전달
javaType : List(컬렉션)의 타입을 지정
ofType : List(컬렉션)의 제네릭(타입 제한) 지정
-->
<collection property="imageList"
select="selectImageList"
column="BOARD_NO"
javaType="java.util.ArrayList"
ofType="BoardImage"
/>
<collection property="commentList"
select="selectCommentList"
column="BOARD_NO"
javaType="java.util.ArrayList"
ofType="Comment"
/>
</resultMap>
<!-- BoardImage resultMap -->
<resultMap type="BoardImage" id="boardImage_rm">
<id property="imageNo" column="IMG_NO"/>
<result property="imagePath" column="IMG_PATH"/>
<result property="imageReName" column="IMG_RENAME"/>
<result property="imageOriginal" column="IMG_ORIGINAL"/>
<result property="imageOrder" column="IMG_ORDER"/>
<result property="boardNo" column="BOARD_NO"/>
</resultMap>
<!-- Comment resultMap -->
<resultMap type="Comment" id="comment_rm">
<id property="commentNo" column="COMMENT_NO"/>
<result property="commentContent" column="COMMENT_CONTENT"/>
<result property="commentCreateDate" column="C_CREATE_DATE"/>
<result property="boardNo" column="BOARD_NO"/>
<result property="memberNo" column="MEMBER_NO"/>
<result property="commentDeleteFlag" column="COMMENT_DEL_FL"/>
<result property="parentNo" column="PARENT_NO"/>
<result property="memberNickname" column="MEMBER_NICKNAME"/>
<result property="profileImage" column="PROFILE_IMG"/>
</resultMap>
<!--
resultType이 "map"인 경우
K : 컬럼명(BOARD_CODE, BOARD_NAME)
V : 컬럼 값( 1 , 공지 사항 )
[{BOARD_NAME= 공지사항, BOARD_CODE=1}, {BOARD_NAME= 자유게시판, BOARD_CODE=2}, ...]
-->
<!-- 게시판 종류 목록 조회 -->
<select id="selectBoardTypeList" resultType="map">
SELECT * FROM "BOARD_TYPE" ORDER BY 1
</select>
<!-- 특정 게시판의 삭제되지 않은 게시글 수 조회 -->
<select id="getListCount" resultType="_int">
SELECT COUNT(*) FROM BOARD
WHERE BOARD_DEL_FL = 'N'
AND BOARD_CODE = #{boardCode}
</select>
<!-- CDATA 태그 : 해당 태그 내부에 작성된 것은 모두 문자로 취급 -->
<select id="selectBoardList" resultMap="board_rm">
SELECT BOARD_NO, BOARD_TITLE, MEMBER_NICKNAME, READ_COUNT,
<![CDATA[
CASE
WHEN SYSDATE - B_CREATE_DATE < 1/24/60
THEN FLOOR( (SYSDATE - B_CREATE_DATE) * 24 * 60 * 60 ) || '초 전'
WHEN SYSDATE - B_CREATE_DATE < 1/24
THEN FLOOR( (SYSDATE - B_CREATE_DATE) * 24 * 60) || '분 전'
WHEN SYSDATE - B_CREATE_DATE < 1
THEN FLOOR( (SYSDATE - B_CREATE_DATE) * 24) || '시간 전'
ELSE TO_CHAR(B_CREATE_DATE, 'YYYY-MM-DD')
END B_CREATE_DATE,
]]>
(SELECT COUNT(*) FROM "COMMENT" C
WHERE C.BOARD_NO = B.BOARD_NO) COMMENT_COUNT,
(SELECT COUNT(*) FROM BOARD_LIKE L
WHERE L.BOARD_NO = B.BOARD_NO) LIKE_COUNT,
(SELECT IMG_PATH || IMG_RENAME FROM BOARD_IMG I
WHERE I.BOARD_NO = B.BOARD_NO
AND IMG_ORDER = 0) THUMBNAIL
FROM "BOARD" B
JOIN "MEMBER" USING(MEMBER_NO)
WHERE BOARD_DEL_FL = 'N'
AND BOARD_CODE = #{boardCode}
ORDER BY BOARD_NO DESC
</select>
<select id="selectBoard" resultMap="board_rm">
SELECT BOARD_NO, BOARD_TITLE, BOARD_CONTENT, BOARD_CODE,
READ_COUNT, MEMBER_NICKNAME, MEMBER_NO, PROFILE_IMG,
TO_CHAR(B_CREATE_DATE, 'YYYY"년" MM"월" DD"일" HH24:MI:SS') B_CREATE_DATE,
TO_CHAR(B_UPDATE_DATE, 'YYYY"년" MM"월" DD"일" HH24:MI:SS') B_UPDATE_DATE,
(SELECT COUNT(*)
FROM "BOARD_LIKE" L
WHERE L.BOARD_NO = B.BOARD_NO) LIKE_COUNT
FROM "BOARD" B
JOIN "MEMBER" USING(MEMBER_NO)
WHERE BOARD_DEL_FL = 'N'
AND BOARD_CODE = #{boardCode}
AND BOARD_NO = #{boardNo}
</select>
<!-- 특정 게시글 이미지 조회 -->
<select id="selectImageList" resultMap="boardImage_rm">
SELECT * FROM BOARD_IMG
WHERE BOARD_NO = #{boardNo}
ORDER BY IMG_ORDER
</select>
<!-- 특정 게시글 댓글 조회(바뀔 예정) -->
<select id="selectCommentList" resultMap="comment_rm">
SELECT COMMENT_NO, COMMENT_CONTENT,
TO_CHAR(C_CREATE_DATE, 'YYYY"년" MM"월" DD"일" HH24"시" MI"분" SS"초"') C_CREATE_DATE,
BOARD_NO, MEMBER_NO, MEMBER_NICKNAME, PROFILE_IMG, PARENT_NO, COMMENT_DEL_FL
FROM "COMMENT"
JOIN MEMBER USING(MEMBER_NO)
WHERE BOARD_NO = #{boardNo}
ORDER BY COMMENT_NO
</select>
<!-- 좋아요 여부 확인 -->
<select id="boardLikeCheck" resultType="_int">
SELECT COUNT(*) FROM BOARD_LIKE
WHERE BOARD_NO = #{boardNo}
AND MEMBER_NO = #{memberNo}
</select>
<!-- 조회 수 증가 -->
<update id="updateReadCount">
UPDATE "BOARD" SET
READ_COUNT = READ_COUNT + 1
WHERE BOARD_NO = #{boardNo}
</update>
<!-- 좋아요 삽입 -->
<insert id="insertBoardLike">
INSERT INTO "BOARD_LIKE" VALUES (#{boardNo}, #{memberNo})
</insert>
<!-- 좋아요 삭제 -->
<delete id="deleteBoardLike">
DELETE FROM "BOARD_LIKE"
WHERE BOARD_NO = #{boardNo}
AND MEMBER_NO = #{memberNo}
</delete>
<!-- 좋아요 조회 -->
<select id="countBoardLike" resultType="_int">
SELECT COUNT(*) FROM "BOARD_LIKE" WHERE BOARD_NO = #{boardNo}
</select>
<!-- 게시글 삽입 -->
<!--
동적 SQL
- 프로그램 수행 중 SQL을 변경하는 기능
<selectKey> 태그 : INSERT/UPDATE 시 사용할 키(시퀀스)를
조회해서 파라미터의 지정된 필드 대입
useGeneratedKeys : DB 내부적으로 생성한키 (시퀀스) 를
전달된 파라미터의 필드로 대입 가능 여부 지정
order="BEFORE" : 메인 SQL이 수행되기 전에 selectKey가 수행되도록 지정
keyProperty : selectKey 조회 결과를 저장할 파라미터의 필드
Board의 boardNo 필드에다가 조회 결과를 저장하겠다.
-->
<insert id="boardInsert" parameterType="Board" useGeneratedKeys="true">
<selectKey order="BEFORE" resultType="_int" keyProperty="boardNo">
SELECT SEQ_BOARD_NO.NEXTVAL FROM DUAL
</selectKey>
INSERT INTO BOARD
VALUES( #{boardNo},
#{boardTitle},
#{boardContent},
DEFAULT, DEFAULT, DEFAULT, DEFAULT,
#{memberNo},
#{boardCode} )
</insert>
<!--
동적 SQL 중 <foreach>
- 특정 sql 구문을 반복할 때 사용
- 반복되는 사이에 구분자를 추가할 수 있음.
-->
<!-- 이미지 리스트(여러개)삽입 -->
<insert id="insertImageList" parameterType="list">
INSERT INTO "BOARD_IMG"
SELECT SEQ_IMG_NO.NEXTVAL, A.*
FROM (
<foreach collection="list" item="img" separator=" UNION ALL ">
SELECT #{img.imagePath} IMG_PATH,
#{img.imageReName} IMG_RENAME,
#{img.imageOriginal} IMG_ORIGINAL,
#{img.imageOrder} IMG_ORDER,
#{img.boardNo} BOARD_NO
FROM DUAL
</foreach>
) A
</insert>
</mapper>
로그인 > 글쓰기 버튼 클릭 후 게시글 등록 시,
게시글이 등록됨
DB에도 정보가 저장됨