<?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="board">
<!--// 시 데이터 벗겨낸 다음에. 그 안에 if 태그 다시 넣어야겠죠.
// 살짝 문법 차이인 거지 sample 이랑 board sql 이랑 같은거. 실전에서는 매퍼 안에 주석은 가급적 넣지 말 것. 난리 남. -->
<select id="selectBoardList" parameterType="hashmap" resultType="hashmap">
<![CDATA[
SELECT RNUM, IDX, TITLE, HIT_CNT, CREA_NM, CREA_DTM FROM(
SELECT ROW_NUMBER() OVER(ORDER BY IDX DESC) AS RNUM ,
IDX,
TITLE,
HIT_CNT,
(SELECT NICK_NM FROM SCOTT.TB_MEMBER WHERE ID = CREA_ID) AS CREA_NM,
TO_CHAR( CREA_DTM , 'YYYY-MM-DD')CREA_DTM
FROM SCOTT.TB_BOARD
WHERE DEL_GB = 'N'
]]>
<if test="keyword != null and keyword != ''">
<choose>
<!-- 콤보박스 선택: 제목 (TITLE) -->
<when test='type == "TITLE"'>
AND TITLE LIKE '%' || #{keyword} || '%'
</when>
<!-- 콤보박스 선택: 내용 (CONTENTS) -->
<when test='type == "CONTENTS"'>
AND CONTENTS LIKE '%' || #{keyword} || '%'
</when>
<!-- 콤보박스 선택: 작성자 ID (ID) -->
<when test='type == "ID"'>
AND CREA_ID LIKE '%' || #{keyword} || '%'
</when>
</choose>
</if>
<![CDATA[
)WHERE RNUM BETWEEN #{start} AND #{end}
]]>
</select>
<select id="selectBoardTotalPage" parameterType="hashmap" resultType="String">
SELECT CEIL(COUNT(*)/10.0)
FROM SCOTT.TB_BOARD
WHERE DEL_GB = 'N'
<if test="keyword != null and keyword != ''">
<choose>
<!-- 콤보박스 선택: 제목 (TITLE) -->
<when test='type == "TITLE"'>
AND TITLE LIKE '%' || #{keyword} || '%'
</when>
<!-- 콤보박스 선택: 내용 (CONTENTS) -->
<when test='type == "CONTENTS"'>
AND CONTENTS LIKE '%' || #{keyword} || '%'
</when>
<!-- 콤보박스 선택: 작성자 ID (ID) -->
<when test='type == "ID"'>
AND CREA_ID LIKE '%' || #{keyword} || '%'
</when>
</choose>
</if>
</select>
<update id="updateBoardDetailHit" parameterType="hashmap" >
<![CDATA[
UPDATE SCOTT.TB_BOARD SET
HIT_CNT = HIT_CNT +1
WHERE IDX = #{idx}
]]>
</update>
<select id="selectBoardDetail" parameterType="hashmap" resultType="hashmap" >
<![CDATA[
SELECT
IDX
, TITLE
, CONTENTS
, HIT_CNT
, TO_CHAR(CREA_DTM, 'YYYY-MM-DD hh:mm:ss') AS CREA_DTM
, (SELECT NICK_NM FROM SCOTT.TB_MEMBER WHERE ID = CREA_ID) AS CREA_NM
FROM SCOTT.TB_BOARD
WHERE IDX = #{idx}
]]>
</select>
<insert id="insertBoardDetail" parameterType="hashmap" >
<selectKey keyProperty="idx" resultType="string" order="BEFORE">
SELECT SEQ_TB_BOARD_IDX.NEXTVAL FROM DUAL
</selectKey>
<![CDATA[
INSERT INTO TB_BOARD
(
IDX,
TITLE,
CONTENTS,
HIT_CNT,
DEL_GB,
CREA_DTM,
CREA_ID,
PASSWORD
)
VALUES
(
#{idx},
#{title},
#{contents,jdbcType=VARCHAR},
0,
'N',
SYSDATE,
#{creaId},
#{password}
)
]]>
</insert>
<update id="updateBoardDetail" parameterType="hashmap" >
UPDATE SCOTT.TB_BOARD SET
TITLE = #{title}
, CONTENTS = #{contents}
WHERE DEL_GB = 'N'
AND IDX = #{idx}
</update>
<update id="deleteBoardDetail" parameterType="hashmap" >
UPDATE SCOTT.TB_BOARD SET
DEL_GB = 'Y'
WHERE DEL_GB = 'N'
AND IDX = #{idx}
</update>
</mapper>
<%@ page language="java" contentType="text/html; charset=UTF-8"
pageEncoding="UTF-8"%>
<%@ taglib prefix="c" uri="<http://java.sun.com/jsp/jstl/core>" %>
<!-- Vue를 이용한 코딩 => JSTL,EL 필요없음(for문 돌릴필요없음) -->
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>게시판 목록</title>
<link href="<https://cdn.jsdelivr.net/npm/bootstrap@5.3.2/dist/css/bootstrap.min.css>" rel="stylesheet" integrity="sha384-T3c6CoIi6uLrA9TneNEoa7RxnatzjcDSCmG1MXxSR1GAsXEV/Dwwykc2MPK8M2HN" crossorigin="anonymous">
<script src="<https://cdn.jsdelivr.net/npm/bootstrap@5.3.2/dist/js/bootstrap.bundle.min.js>" integrity="sha384-C6RzsynM9kWDrMNeT87bh95OGNyZPhcTNXj1NW7RuBCsyN/o0jlpcV8Qyq46cDfL" crossorigin="anonymous"></script>
<script src="<https://cdn.jsdelivr.net/npm/vue/dist/vue.min.js>"></script> <!-- 2.0 뷰 코드-->
<script src="<https://unpkg.com/axios/dist/axios.min.js>"></script> <!--악시오스-->
</head>
<body>
<div class="container">
<nav class="navbar navbar-expand-lg bg-body-tertiary">
<div class="container-fluid">
<a class="navbar-brand" href="#">Navbar</a>
<button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target="#navbarSupportedContent" aria-controls="navbarSupportedContent" aria-expanded="false" aria-label="Toggle navigation">
<span class="navbar-toggler-icon"></span>
</button>
<div class="collapse navbar-collapse" id="navbarSupportedContent">
<ul class="navbar-nav me-auto mb-2 mb-lg-0">
<li class="nav-item">
<a class="nav-link active" aria-current="page" href="#">Home</a>
</li>
<li class="nav-item">
<a href="<c:url value='/board/write.do' />" class="nav-link">새글</a>
</li>
<li class="nav-item">
<a class="nav-link disabled" aria-disabled="true">Disabled</a>
</li>
</ul>
<form class="d-flex" role="search">
<select class="form-select" aria-label="Default select example" v-model:value="type">
<option selected>선택</option>
<option value="TITLE">제목</option>
<option value="CONTENTS">내용</option>
<option value="ID">작성자 ID</option>
</select>
<input class="form-control me-2" type="search" placeholder="Search" aria-label="Search" v-model:value="keyword">
<button @click.prevent="boardList()" class="btn btn-outline-success" type="submit">Search</button>
</form>
</div>
</div>
</nav>
<div class="row">
<table class="table">
<tr>
<th width=5% class="text-center">번호</th>
<th width=5% class="text-center">IDX</th>
<th width=45% class="text-center">제목</th>
<th width=15% class="text-center">이름</th>
<th width=20% class="text-center">작성일</th>
<th width=10% class="text-center">조회수</th>
</tr>
<!-- VueJS의 for문 -->
<tr v-for="vo in board_list">
<th width=5% class="text-center">{{vo.rnum}}</th>
<th width=5% class="text-center">{{vo.idx}}</th>
<th width=45%><a :href="'<c:url value='/board/detail.do' />?idx='+vo.idx">{{vo.title}}</a></th>
<th width=15% class="text-center">{{vo.creaNm}}</th>
<th width=20% class="text-center">{{vo.creaDtm}}</th>
<th width=10% class="text-center">{{vo.hitCnt}}</th>
</tr>
</table>
<table class="table">
<tr>
<td class="text-center">
<input type=button value="이전" class="btn btn-sm btn-danger" > 현재 {{curpage}} 페이지 / 전체 {{totalpage}} 전체페이지 <input type=button value="다음" class="btn btn-sm btn-danger" >
</td>
</tr>
</table>
</div>
</div>
<script>
var vue = new Vue({
//el : 관리 영역 지정 => container
el:'.container', // el 태그가. 뷰에서 관리하는 코드. 스크립트를 관리하는 영역을 지정. 클래스가 컨테이너를 찾아라는 거.
data:{
board_list:[],
curpage:1,
totalpage:0,
keyword:'',
type:'',
},
methods:{
boardList : function(pageNo){
console.log(typeof(pageNo));
let _this = this;
if(typeof(pageNo) === 'undefined'){
pageNo = _this.curpage;
}
axios.get("<c:url value='/board/api/list' />",{
params:{
page:pageNo,
type:_this.type,
keyword:_this.keyword,
}
}).then(function(result){
console.log(result.data);
if(result.data.code == "0"){
_this.board_list = result.data.list;
_this.curpage=result.data.curpage;
_this.totalpage=result.data.totalpage;
_this.keyword=result.data.keyword;
_this.type=result.data.type;
}else{
console.log(result.data.msg);
alert("오류발생 : 관리자에게 문의 주세요");
}
});
}
},
mounted:function(){
this.boardList();
}
})
</script>
</body>
</html>
package first.board.controller;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import javax.annotation.Resource;
import org.apache.log4j.Logger;
import org.json.simple.JSONArray;
import org.json.simple.JSONObject;
import org.springframework.http.MediaType;
import org.springframework.web.bind.annotation.CrossOrigin;
import org.springframework.web.bind.annotation.DeleteMapping;
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.PutMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestPart;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.multipart.MultipartFile;
import first.board.dto.BoardDTO;
import first.board.service.BoardService;
import first.common.common.CommandMap;
@CrossOrigin(origins = "<http://localhost:5173>")
@RestController
public class BoardRestController {
Logger log = Logger.getLogger(this.getClass()); //로그 찍고
@Resource(name="boardService")
private BoardService boardService; //서비스 선언
/**
* Rest BoardList조회
* @param commandMap
* @return
*/
@GetMapping(value = "/board/api/list",produces = "text/plain;charset=utf-8") // 얘를 통해서 데이터를 주고 받는 거.
public String selectBoardList(CommandMap commandMap) {
Map map = commandMap.getMap();
//Map<String, Object> resultObj = boardService.board_list_vue(map);
JSONObject resultObj = boardService.selectBoardList(map);
String result = resultObj.toJSONString(); // 최종적으로는 제이슨 문법으로 리턴이 됨.
return result;
}
/**
* 상세글 보기
* @param commandMap
* @return
*/
@GetMapping(value="/board/api/detail/{idx}",produces = "text/plain;charset=utf-8")
public String selectBoardDetail(@PathVariable("idx") String idx) {
String result="";
Map<String, Object> map =new HashMap<String, Object>();
map.put("idx", idx);
JSONObject resultObj = boardService.selectBoardDetail(map);
result = resultObj.toJSONString();
return result;
}
@PostMapping(value="/board/api/detail", consumes = {MediaType.APPLICATION_JSON_VALUE, MediaType.MULTIPART_FORM_DATA_VALUE} ,produces = "text/plain;charset=utf-8")
public String insertBoardDetail(@RequestPart(value = "file", required = false) MultipartFile file, @RequestPart(value="document") JSONObject param) {
String result = "";
log.debug(param);
JSONObject resultObj = boardService.insertBoardDetail(param);
result = resultObj.toJSONString();
return result;
}
@PostMapping(value="/board/api/detail/{idx}", consumes = {MediaType.APPLICATION_JSON_VALUE, MediaType.MULTIPART_FORM_DATA_VALUE} ,produces = "text/plain;charset=utf-8")
public String updateBoardDetail(@RequestPart(value = "file", required = false) MultipartFile file, @RequestPart(value="document") JSONObject param , @PathVariable("idx") String idx) {
String result = "";
log.debug("TEST : " + param);
JSONObject resultObj = boardService.updateBoardDetail(param);
result = resultObj.toJSONString();
return result;
}
@DeleteMapping(value="/board/api/detail/{idx}",produces = "text/plain;charset=utf-8")
public String deleteBoardDetail(@PathVariable("idx") String idx) {
String result = "";
Map<String, Object> param = new HashMap<String, Object>();
param.put("idx", idx);
JSONObject resultObj = boardService.deleteBoardDetail(param);
result = resultObj.toJSONString();
return result;
}
}
package first.board.service;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import javax.annotation.Resource;
import org.apache.commons.collections.map.HashedMap;
import org.apache.log4j.Logger;
import org.json.simple.JSONArray;
import org.json.simple.JSONObject;
import org.springframework.stereotype.Service;
import first.board.dao.BoardDAO;
@Service("boardService")
public class BoardServiceImpl implements BoardService {
Logger log = Logger.getLogger(this.getClass()); //로그 한번 찍어주고
@Resource(name="boardDAO")
private BoardDAO boardDAO;
@Override
public JSONObject selectBoardList(Map map) {
Map<String, Object> result = new HashMap<String, Object>();
String page = (String) map.get("page");
if(page==null)
page="1";
int curpage=Integer.parseInt(page); // 서비스 상에서 페이지 처리. 되도록 건들지는 말 것.
int rowSize=10;
int start=(rowSize*curpage)-(rowSize-1);
int end=rowSize*curpage;
map.put("curpage", curpage);
map.put("start", start);
map.put("end", end);
// 조회 값 출력
List<Map<String, Object>> list = boardDAO.selectBoardList(map); // 페이징 처리된 기준으로. 데이터 출력.
// 전체 리스트 출력 // 페이지 처리 하려면 전체 사이즈가 필요하니까.
String strTotalCount = boardDAO.selectBoardTotalPage(map);
if(strTotalCount == null) {
strTotalCount="0";
}
int totalpage = Integer.parseInt(strTotalCount); // 파싱하도록 되어 있는데. 스트링에 파싱값 없으면 에러가 나지. 그거 에러나게 하지 않기 위해. 위에서 초기화로 건네 준 거.
String strCode = "0";
String strMsg = "";
JSONArray arr=new JSONArray(); // 제이슨 어레이를 통해서. 파라미터를 집어 넣게 됨. 넥사크로 때 했던. 바이딩 하는 형태랑 흡사.
try {
for(Map<String,Object> vo:list)
{
JSONObject obj=new JSONObject();
obj.put("rnum", vo.get("RNUM"));
obj.put("idx", vo.get("IDX"));
obj.put("title", vo.get("TITLE"));
obj.put("hitCnt", vo.get("HIT_CNT"));
obj.put("creaNm", vo.get("CREA_NM"));
obj.put("creaDtm", vo.get("CREA_DTM"));
arr.add(obj);
}
} catch (Exception e) {
strCode = "-1";
strMsg = e.getMessage();
}
//result=arr.toJSONString(); // 요 값이. 내가 캡쳐한. 네트워크, 프리뷰에 있는 구조에 있는 바로 그 값.
JSONObject resultObject = new JSONObject();
resultObject.put("curpage", curpage);
resultObject.put("totalpage",totalpage);
resultObject.put("code", strCode);
resultObject.put("msg", strMsg);
resultObject.put("list", arr);
return resultObject;
}
@Override
public JSONObject selectBoardDetail(Map map) {
//idx값이 없는경우
String strCode = "0";
String strMsg = "";
JSONObject resultObject = new JSONObject();
JSONObject detailObject = new JSONObject();
if(!map.containsKey("idx")) {
strCode = "-1";
strMsg = "필수값이 누락되었습니다.";
} else {
try {
//게시글 카운트 업데이트
boardDAO.updateBoardHitCnt(map);
//게시글 상세정보 읽기
Map<String, Object> detail = (Map<String,Object>)boardDAO.selectBoardDetail(map);
detailObject.put("idx", detail.get("IDX"));
detailObject.put("title", detail.get("TITLE"));
detailObject.put("contents", detail.get("CONTENTS"));
detailObject.put("hitCnt", detail.get("HIT_CNT"));
detailObject.put("creaDtm", detail.get("CREA_DTM"));
detailObject.put("creaNm", detail.get("CREA_NM"));
resultObject.put("detail", detailObject);
//게시글 첨부파일(예정)
}catch (Exception e) {
// TODO: handle exception
strCode = "-999";
strMsg = e.getMessage();
}
}
resultObject.put("code", strCode);
resultObject.put("msg", strMsg);
return resultObject;
}
@Override
public JSONObject insertBoardDetail(JSONObject param) {
JSONObject resultObject = new JSONObject();
String strCode = "0";
String strMsg = "";
try {
param.put("password","itpartners");
boardDAO.insertBoardDetail(param);
} catch (Exception e) {
strCode = "-1";
strMsg = e.getMessage();
}
resultObject.put("code", strCode);
resultObject.put("msg", strMsg);
return resultObject;
}
@Override
public JSONObject updateBoardDetail(JSONObject param) {
JSONObject resultObject = new JSONObject();
String strCode = "0";
String strMsg = "";
try {
boardDAO.updateBoardDetail(param) ;
} catch (Exception e) {
strCode = "-1";
strMsg = e.getMessage();
}
resultObject.put("code", strCode);
resultObject.put("msg", strMsg);
return resultObject;
}
@Override
public JSONObject deleteBoardDetail(Map param) {
JSONObject resultObject = new JSONObject();
String strCode = "0";
String strMsg = "";
try {
int daoResult = Integer.parseInt( (String) boardDAO.deleteBoardDetail(param) );
if(daoResult > 0 ) {
}else {
strCode = "-1";
}
} catch (Exception e) {
strCode = "-1";
strMsg = e.getMessage();
}
resultObject.put("code", strCode);
resultObject.put("msg", strMsg);
return resultObject;
}
}
package first.board.controller;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.servlet.ModelAndView;
import first.common.common.CommandMap;
/**/
@Controller
public class BoardController {
@RequestMapping(value="/board/list")
public ModelAndView openBoardList(CommandMap commandMap) throws Exception
{
ModelAndView mv = new ModelAndView("/board/boardList");
return mv;
}
@RequestMapping(value="/board/detail")
public ModelAndView openBoardDetail(CommandMap commandMap) throws Exception
{
ModelAndView mv = new ModelAndView("/board/boardDetail");
mv.addObject("idx",commandMap.get("idx"));
return mv;
}
@RequestMapping(value="/board/write")
public ModelAndView openBoardWrite(CommandMap commandMap) throws Exception
{
ModelAndView mv = new ModelAndView("/board/boardWrite");
return mv;
}
@RequestMapping(value="/board/update")
public ModelAndView openBoardUpdate(CommandMap commandMap) throws Exception
{
ModelAndView mv = new ModelAndView("/board/boardUpdate");
mv.addObject("idx",commandMap.get("idx"));
return mv;
}
}
제공해주신 코드는 MyBatis XML 매퍼 파일, JSP (HTML/Vue.js) 파일, 그리고 Spring Boot/Framework 기반의 REST 컨트롤러, 서비스, 일반 컨트롤러 클래스로 구성된 게시판 애플리케이션의 일부입니다.
각 코드 블록별로 자세히 설명드리겠습니다.