<?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 컨트롤러, 서비스, 일반 컨트롤러 클래스로 구성된 게시판 애플리케이션의 일부입니다.

각 코드 블록별로 자세히 설명드리겠습니다.