대괄호,
eq(인덱스번호)
AOBController 새로 만들었음
package com.spring.sample.web.testa.controller;
import java.util.HashMap;
import java.util.Map;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.servlet.ModelAndView;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.spring.sample.common.service.IPagingService;
import com.spring.sample.web.testa.dao.IACDao;
@Controller
public class AOBController {
@Autowired
public IACDao dao;
@Autowired
public IPagingService ips;
@RequestMapping(value = "AOB")
public ModelAndView aob(ModelAndView mav) {
mav.setViewName("testa/AOB/ob");
return mav;
}
@RequestMapping(value = "/AOBAction/{gbn}",
method = RequestMethod.POST,
produces = "text/json;charset=UTF-8")
@ResponseBody
public String AOBAction(
@PathVariable String gbn,
@RequestParam HashMap<String, String> params) throws Throwable {
ObjectMapper mapper = new ObjectMapper ();
Map<String, Object> model = new HashMap<String, Object>();
int cnt = 0;
try {
switch(gbn) {
case "insert" : cnt = dao.insert("ob.insertOb", params);
break;
case "update" : cnt = dao.update("ob.updateOb", params);
break;
case "delete" : cnt = dao.update("ob.deleteOb", params);
break;
}
if(cnt > 0) {
model.put("msg", "success");
} else {
model.put("msg", "fail");
}
} catch (Exception e) {
e.printStackTrace();
model.put("msg", "error");
}
return mapper.writeValueAsString(model);
}
}
원래 있던 IACDao, ACDao 이용함.
testa - AOB - ob.jsp
새로 만들었음.
자바스크립트 Object에서의 [] : 해당 키 값으로 내용을 불러오거나 넣을 수 있다. 자바의 Map에서 get, put 역할.
<%@ page language="java" contentType="text/html; charset=UTF-8"
pageEncoding="UTF-8"%>
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>한줄게시판</title>
<!-- Common CSS -->
<link rel="stylesheet" type="text/css" href="resources/css/common/cmn.css" />
<!-- Popup CSS -->
<link rel="stylesheet" type="text/css" href="resources/css/common/popup.css" />
<style type="text/css">
#searchTxt {
width : 161px;
height: 28px;
padding: 0px 2px;
text-indent: 5px;
vertical-align: middle;
border : 1px solid #d7d7d7;
outline-color : #70adf9;
}
.search_area {
width : 800px;
text-align: right;
margin : 0 auto;
}
.board_area {
width : 800px;
margin : 0 auto;
}
.wrap {
width : 800px;
margin : 0 auto;
}
.mtb {
margin : 5px 0px;
}
.login {
vertical-align: baseline;
}
.con {
width : calc(100% - 22px);
height : 60px;
border : 1px soild #d7d7d7;
resize : none;
padding : 10px;
}
.update {
display : none;
}
.con_td {
font-size : 0;
}
.paging_area {
display: block;
position: relative;
left : 0;
margin-bottom : 10px;
}
.search_area {
text-align : center;
}
</style>
<!-- JQuery -->
<script type="text/javascript"
src="resources/script/jquery/jquery-1.12.4.min.js"></script>
<!-- Popup JS -->
<script type="text/javascript"
src="resources/script/common/popup.js"></script>
<script type="text/javascript">
$(document).ready(function() {
$(".board_table #loginBtn").on("click", function() {
location.href = "testALogin";
});
$("#insertBtn").on("click", function() {
if($.trim($("#con").val()) == "") {
makeAlert("알림", "내용을 입력하세요.", function() {
$("#con").focus();
});
} else {
action("insert");
}
});
});
var msg = {
"insert" : "등록",
"update" : "수정",
"delete" : "삭제",
}
function action(flag) {
// Javascript Object에서의 [] : 해당 키값으로 내용을 불러오거나 넣을 수 있다. Java의 Map에서 get, put역할
console.log(msg[flag]);
var params = $("#actionForm").serialize(); // 보낼수있는 형태로 만든 후 보낸다
$.ajax({
url : "AOBAction/" + flag, // 경로
type : "POST", // 전송방식(GET : 주소형태, POST : 주소 헤더형태)
dataType : "json", // 데이터 형태
data : params, // 보낼 데이터
success : function(res) {// 성공했을때 경과를 res에 받고 함수 실행
switch(res.msg) {
case "success" :
// 내용 초기화
$("#con").val("");
// 목록 재조회
break;
case "fail" :
makeAlert("알림", msg[flag] + "에 실패하였습니다.");
break;
case "error" :
makeAlert("알림", msg[flag] + "중 문제가 발생하였습니다.");
break;
}
},
error : function(request, status, error) { // 실패했을때 함수 실행
console.log(request.responseText); // 실패 상세내역
}
}); // Ajax End
} // action Function End
</script>
</head>
<body>
<c:import url="/testAHeader"></c:import>
<hr/>
<div class="wrap">
<div class="board_area">
<!-- 작성 또는 로그인 -->
<!-- 목록 -->
<table class="board_table">
<colgroup>
<!-- 작성자 -->
<col width="100" />
<!-- 내용 -->
<col width="500" />
<!-- 날짜 -->
<col width="100" />
<!-- 버튼 -->
<col width="100" />
</colgroup>
<thead>
<c:choose>
<c:when test="${empty sMemNo}"><!-- 비 로그인 시 -->
<tr>
<th colspan="4">
로그인이 필요한 서비스 입니다.
<div class="cmn_btn login" id="loginBtn">로그인</div>
</th>
</tr>
</c:when>
<c:otherwise><!-- 로그인 시 -->
<tr>
<th>${sMemNm}</th>
<td colspan="2" class="con_td">
<form action="#" id="actionForm">
<input type="hidden" name="no" id="no"/>
<input type="hidden" name="memNo" value="${sMemNo}"/>
<textarea class="con" name="con" id="con"></textarea>
</form>
</td>
<th>
<div class="insert">
<div class="cmn_btn" id="insertBtn">등록</div>
</div>
<div class="update">
<div class="cmn_btn mtb" id="updateBtn">수정</div><br/>
<div class="cmn_btn mtb" id="cancelBtn">취소</div>
</div>
</th>
</tr>
</c:otherwise>
</c:choose>
<tr>
<th>작성자</th>
<th>내용</th>
<th>작성일</th>
<th> </th>
</tr>
</thead>
<tbody>
<tr>
<td>홍길동</td>
<td>테스트</td>
<td>10:16</td>
<td>
<div class="cmn_btn mtb">수정</div><br/>
<div class="cmn_btn mtb">삭제</div>
</td>
</tr>
</tbody>
</table>
<!-- 페이징 -->
<div class="paging_area"></div>
</div>
<div class="search_area">
<form action="#" id="searchForm">
<input type="hidden" name="page" id="page" value="1" />
<select name="searchGbn" id="searchGbn">
<option value="0">작성자</option>
<option value="1">내용</option>
</select>
<input type="text" name="searchText" id="searchText" />
<div class="cmn_btn_ml" id="searchBtn">검색</div>
</form>
</div>
</div>
</body>
</html>
testa - login - main.jsp
댓글 창 주소 AOB 가는 링크 추가해주기
<%@ page language="java" contentType="text/html; charset=UTF-8"
pageEncoding="UTF-8"%>
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Insert title here</title>
<script type="text/javascript"
src="resources/script/jquery/jquery-1.12.4.min.js"></script>
<script type="text/javascript">
$(document).ready(function() {
});
</script>
</head>
<body>
<c:import url="/testAHeader"></c:import>
<br/>
<a href="ATList">게시글 목록</a>
<br/>
<a href="AOB">댓글</a>
</body>
</html>
새 테이블 OB 만들기

제약조건 걸어주기

시퀀스 만들기

지각해서 여기부터 필기!
추가, 수정, 삭제, 목록
쿼리 짜기
일단 xml 파일 먼저 다 만들었다.
MEM 테이블 데이터는 현재 아래와 같음.

-추가
내가 회원 데이터가 4번부터 있어서 4번으로 했다.
INSERT INTO OB(NO, MEM_NO, CON)
VALUES (OB_SEQ.NEXTVAL, 4, 'TEST');
COMMIT;
-수정
넣는 거 한 번 실패해서 1번이 없고 2번부터 시작이라 2번을 바꿔줌
UPDATE OB SET CON = 'TEST_UPDATE'
WHERE NO = 2;
COMMIT;
-삭제
DEL 0으로 바꿔주는 건 업데이트로 똑같이 하고..
-페이징을 위한 카운트
SELECT COUNT(*) AS CNT
FROM OB O INNER JOIN MEM M
ON O.MEM_NO = M.MEM_NO
AND M.DEL = 1
WHERE O.DEL = 1
-- 작성자 검색
-- AND M.MEM_NM LIKE '%' || 'TEST' || '%'
-- 내용 검색
AND O.CON LIKE '%' || 'UPDATE' || '%'
;
-목록 조회
SELECT O.NO, O.MEM_NO, O.MEM_NM, O.CON, O.DT
FROM (SELECT O.NO, O.MEM_NO, M.MEM_NM, O.CON,
CASE WHEN TO_CHAR(O.DT, 'YY.MM.DD') = TO_CHAR(SYSDATE, 'YY.MM.DD')
THEN TO_CHAR(O.DT, 'HH24:MI')
ELSE TO_CHAR(O.DT, 'YY.MM.DD')
END AS DT,
ROW_NUMBER() OVER(ORDER BY O.NO DESC) AS RNUM
FROM OB O INNER JOIN MEM M
ON O.MEM_NO = M.MEM_NO
AND M.DEL = 1
WHERE O.DEL = 1) O
WHERE O.RNUM BETWEEN 1 AND 10
;
OB_SQL.xml
새로 만들기
<?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="ob">
<insert id="insertOb" parameterType="hashmap">
INSERT INTO OB(NO, MEM_NO, CON)
VALUES (OB_SEQ.NEXTVAL, #{memNo}, #{con})
</insert>
<update id="updateOb" parameterType="hashmap">
UPDATE OB SET CON = #{con}
WHERE NO = #{no}
</update>
<update id="deleteOb" parameterType="hashmap">
UPDATE OB SET DEL = 0
WHERE NO = #{no}
</update>
<select id="getObCnt" parameterType="hashmap" resultType="Integer">
SELECT COUNT(*) AS CNT
FROM OB O INNER JOIN MEM M
ON O.MEM_NO = M.MEM_NO
AND M.DEL = 1
WHERE O.DEL = 1
<if test="searchText != null and searchText != ''">
<choose>
<when test="searchGbn eq 0">
AND M.MEM_NM LIKE '%' || #{searchText} || '%'
</when>
<when test="searchGbn eq 1">
AND O.CON LIKE '%' || #{searchText} || '%'
</when>
</choose>
</if>
</select>
<select id="getObList" parameterType="hashmap" resultType="hashmap">
SELECT O.NO, O.MEM_NO, O.MEM_NM, O.CON, O.DT
FROM (SELECT O.NO, O.MEM_NO, M.MEM_NM, O.CON,
CASE WHEN TO_CHAR(O.DT, 'YY.MM.DD') = TO_CHAR(SYSDATE, 'YY.MM.DD')
THEN TO_CHAR(O.DT, 'HH24:MI')
ELSE TO_CHAR(O.DT, 'YY.MM.DD')
END AS DT,
ROW_NUMBER() OVER(ORDER BY O.NO DESC) AS RNUM
FROM OB O INNER JOIN MEM M
ON O.MEM_NO = M.MEM_NO
AND M.DEL = 1
WHERE O.DEL = 1
<if test="searchText != null and searchText != ''">
<choose>
<when test="searchGbn eq 0">
AND M.MEM_NM LIKE '%' || #{searchText} || '%'
</when>
<when test="searchGbn eq 1">
AND O.CON LIKE '%' || #{searchText} || '%'
</when>
</choose>
</if>) O
WHERE O.RNUM BETWEEN #{start} AND #{end}
</select>
</mapper>
카운트는 resultType이 Integer
주소 http://localhost:8090/SampleSpring/AOB
들어가서

로그인

댓글 창 링크 클릭

댓글 작성

아직 목록 상에서는 안 뜨지만
디벨로퍼 가서 데이터 보면 들어와 있음.

목록 페이징
ob.jsp
testa - T - list.jsp에서 drawPaging 복사해서 넣기
// testa - T - list.jsp에서 그대로 가져옴
function drawPaging(pd) {
var html = "";
//처음
html += "<span class=\"page_btn page_first\" page=\"1\">처음</span>";
//이전
if($("#page").val() == "1") { //input에 들어가는 건 기본적으로 문자열이라 "1"
html += "<span class=\"page_btn page_prev\" page=\"1\">이전</span>";
} else {
html += "<span class=\"page_btn page_prev\" page=\"" + ($("#page").val() * 1 - 1) + "\">이전</span>";
//1 곱해주는 이유는 숫자로 만들어주는 것
}
for(var i = pd.startP; i <= pd.endP; i++) {
//현재 페이지와 i가 같을 때 bold 줌
if($("#page").val() * 1 == i) { //현재 페이지
html += "<span class=\"page_btn_on\" page=\"" + i + "\">" + i + "</span>";
} else { //다른 페이지
html += "<span class=\"page_btn\" page=\"" + i + "\">" + i + "</span>";
}
}
//다음
if($("#page").val() * 1 == pd.maxP) { //현재 페이지가 마지막 페이지라면
html += "<span class=\"page_btn page_next\" page=\"" + pd.maxP + "\">다음</span>";
} else {
html += "<span class=\"page_btn page_next\" page=\"" + ($("#page").val() * 1 + 1) + "\">다음</span>";
}
//마지막
html += "<span class=\"page_btn page_last\" page=\"" + pd.maxP + "\">마지막</span>";
$(".paging_area").html(html);
}
목록 띄우기
drawList 함수 만들어서
바디 안의 td 내용 잘라와서 넣고 html 변수에 누적시키기
수정, 삭제 버튼에 아직 아무것도 안 달았으니까 클래스 다르게 추가해주기. update_btn, delete_btn
역슬래시 쉽게 달아주는 방법
드래그 해서 ctrl + F
" 찾아서 \" 로 교체
이때 Scope 확인하기!

function drawList(list) {
var html = "";
for(var data of list) {
html += "<tr no=\"" + data.NO + "\">";
html += "<td>" + data.MEM_NM + "</td>";
html += "<td>" + data.CON + "</td>";
html += "<td>" + data.DT + "</td>";
html += "<td>";
if("${sMemNo}" == data.MEM_NO) { // 작성자이면 수정,삭제 버튼 나타남
html += "<div class=\"cmn_btn mtb update_btn\">수정</div><br/>";
html += "<div class=\"cmn_btn mtb delete_btn\">삭제</div>";
}
html += "</td>";
html += "</tr>";
}
$("tbody").html(html); // 누적된 html을 tbody에 붙여야 함
}
for문이 끝났을 때 html에 누적되어 있을 테니 tbody의 내용을 갈아 엎어주기까지 해줘야 함.
그리고 document ready 안에 맨 위에 reloadList(); 추가까지!
<script type="text/javascript">
$(document).ready(function() {
reloadList();
이제 내가 댓글을 등록할 때마다 리스트에서 바로 확인 할 수 있고
내가 작성자(rla)인 댓글만 수정, 삭제 버튼이 보인다.

다른 사용자가 쓴 댓글의 버튼은 안 보인다.
검색
검색어 유지용 추가
<div class="search_area">
<!-- 검색어 유지용 -->
<input type="hidden" id="oldGbn" value="0" />
<input type="hidden" id="oldText" />
<form action="#" id="searchForm">
<input type="hidden" name="page" id="page" value="1" />
<select name="searchGbn" id="searchGbn">
<option value="0">작성자</option>
<option value="1">내용</option>
</select>
<input type="text" name="searchText" id="searchText" />
<div class="cmn_btn_ml" id="searchBtn">검색</div>
</form>
</div>
document reasdy 안에
paging_area와 searchBtn에 대해 추가
//페이징 클릭 시
$(".paging_area").on("click", "span", function() {
$("#page").val($(this).attr("page"));
//기존 값 유지
$("#searchGbn").val($("#oldGbn").val());
$("#searchText").val($("#oldText").val());
reloadList();
});
// 검색 버튼 클릭 시
// 기존 것 넘겨줘도 됨
$("#searchBtn").on("click", function() {
$("#page").val("1");
//기존 값 새 값으로 변경
$("#oldGbn").val($("#searchGbn").val());
$("#oldText").val($("#searchText").val());
reloadList();
});
success에 대해 switch문 추가
insert와 delete는 둘 다 동일하게 처리하고,
update 는 수정이라 1페이지로 갈 일 없음
function action(flag) {
// Javascript Object에서의 [] : 해당 키값으로 내용을 불러오거나 넣을 수 있다. Java의 Map에서 get, put역할
console.log(msg[flag]);
var params = $("#actionForm").serialize(); // 보낼수있는 형태로 만든 후 보낸다
$.ajax({
url : "AOBAction/" + flag, // 경로
type : "POST", // 전송방식(GET : 주소형태, POST : 주소 헤더형태)
dataType : "json", // 데이터 형태
data : params, // 보낼 데이터
success : function(res) {// 성공했을때 경과를 res에 받고 함수 실행
switch(res.msg) {
case "success" :
// 내용 초기화
$("#con").val("");
$("#no").val("");
// 목록 재조회
switch(flag) {
case "insert" :
case "delete" :
// 조회 데이터 초기화
$("#page").val("1");
$("#searchGbn").val("0");
$("#searchText").val("");
$("#oldGbn").val("0");
$("#oldText").val("");
break;
case "update" :
//기존 값 유지
$("#searchGbn").val($("#oldGbn").val());
$("#searchText").val($("#oldText").val());
break;
}
reloadList();
break;
case "fail" :
makeAlert("알림", msg[flag] + "에 실패하였습니다.");
break;
case "error" :
makeAlert("알림", msg[flag] + "중 문제가 발생하였습니다.");
break;
}
},
error : function(request, status, error) { // 실패했을때 함수 실행
console.log(request.responseText); // 실패 상세내역
}
}); // Ajax End
} // action Function End
댓글을 새로 작성하면 검색 결과가 없었던 걸로 되면서 1페이지로 돌아온다.
slimscroll
src - main - webapp - resources - script - jquery - jquery.slimscroll.js
추가할 거임
ob.jsp에서
<!-- Slimscroll JS -->
<script type="text/javascript"
src="resources/script/jquery/jquery.slimscroll.js"></script>
<script type="text/javascript">
$(document).ready(function() {
$("body").slimscroll({
height: "100%",
axis: "y"
});
써도 되는데 원래 사이즈가 있어서 바디에 그냥 스크롤 달아주는 게 이쁘다.
그니까 다시 주석처리하고 body에 overflow auto 주기
삭제 버튼
수정, 삭제 버튼은 클래스로 된 버튼이다.
그걸 클릭했을 때 번호를 가져가서 삭제 처리 해줘야 한다.
항상 떠있는 게 tbody라 이벤트는 tbody에 줄 거다.
document ready 안에 추가.
삭제 버튼을 눌렀을 때 그 글의 번호를 가져가려면 그 버튼을 가진 td의 tr까지 두 단계를 올라가야 한다.
길어서 변수 no 만들어서 담음.
// 목록의 삭제 버튼 클릭시
$("tbody").on("click", ".delete_btn", function() {
var no = $(this).parent().parent().attr("no");
// 팝업
makePopup({
title : "알림",
contents : "삭제 하시겠습니까?",
buttons : [{
name : "삭제",
func:function() {
// 삭제 전에 번호 가져가야 함
$("#no").val(no);
action("delete");
closePopup(); // 제일 위의 팝업 닫기
}
}, {
name : "취소"
}]
});
});
flag가 delete가 되어서 아래 action 작동해서 삭제 처리 됨.
수정 버튼
목록의 수정 버튼 클릭했을 때 그 댓글의 내용을 댓글 입력 창으로 가져와야 한다.
지금 내용은 두번째 자식으로 가지고 있다.
셀렉터로 가는 방법이 있고 eq로 가는 방법이 있음.
document ready 안에 추가.
eq(인덱스번호) : 자식들 중 인덱스 몇 번째인지 찾아서 취득.
// 목록의 수정 버튼 클릭 시
$("tbody").on("click", ".update_btn", function() {
var no = $(this).parent().parent().attr("no");
$("#no").val(no);
// eq(인덱스 번호) : 자식들 중 인덱스 몇 번째인지 찾아서 취득
// 댓글 입력 칸에 수정하려고 클릭한 댓글 내용이 들어옴
var con = $(this).parent().parent().children().eq(1).html();
$("#con").val(con);
// 등록 버튼 감추기 + 수정,취소 버튼 나타나게 하기
$(".insert").hide();
$(".update").show();
});
// 수정 영역의 취소 버튼 클릭 시
// 취소 버튼은 없었던 일로 하면 됨.
// 취소 버튼은 thead에 있음
$("thead #cancelBtn").on("click", function() {
// 입력 내용 초기화
$("#no").val("");
$("#con").val("");
// 등록 버튼 나타나기 + 수정,취소 버튼 감추기 (반대로 하는 거임)
$(".insert").show();
$(".update").hide();
});
document ready 안에
// 수정 영역의 수정 버튼 클릭 시
$("thead #updateBtn").on("click", function() {
action("update");
});
action(flag) 함수 안에
case "update" :
//기존 값 유지
$("#searchGbn").val($("#oldGbn").val());
$("#searchText").val($("#oldText").val());
// 수정한 뒤 다시 등록 버튼 활성화 시키기
// 입력 내용 초기화
$("#no").val("");
$("#con").val("");
// 등록 버튼 나타나기 + 수정,취소 버튼 감추기 (반대로 하는 거임)
$(".insert").show();
$(".update").hide();
break;
}
reloadList();
break;
포커스도 추가해주기
// 목록의 수정 버튼 클릭 시
$("tbody").on("click", ".update_btn", function() {
var no = $(this).parent().parent().attr("no");
$("#no").val(no);
// eq(인덱스 번호) : 자식들 중 인덱스 몇 번째인지 찾아서 취득
// 댓글 입력 칸에 수정하려고 클릭한 댓글 내용이 들어옴
var con = $(this).parent().parent().children().eq(1).html();
$("#con").val(con);
// 등록 버튼 감추기 + 수정,취소 버튼 나타나게 하기
$(".insert").hide();
$(".update").show();
//작성 영역에 포커스
$("#con").focus();
});
그럼 이제 수정도 되고 리스트에도 보임
여기에 글 번호를 달아주면 이 리스트가 그 글의 댓글들이 되는 거다.
글 상세보기의 글 번호가 이 댓글 리스트의 조건이 됨.
중요한 웹 문자 변환 작업!!
댓글 내용에 스크립트를 담아서 등록하면 터져버리는 문제가 있다.
내용에 꺽쇄가 들어가면 태그로 인식하기 때문.
태그로 인식하지 않고 화면에 그리도록 웹 문자로 변환해주는 작업이 필요하다.
1) 함수 안에
function action(flag) {
// con의 <,> 들을 웹 문자로 변환 -> 디비에도 저 글자로 들어감
$("#con").val($("#con").val().replace(/</gi, "<"));
$("#con").val($("#con").val().replace(/>/gi, ">"));
2) 수정할 때도
// 목록의 수정 버튼 클릭 시
$("tbody").on("click", ".update_btn", function() {
var no = $(this).parent().parent().attr("no");
$("#no").val(no);
// eq(인덱스 번호) : 자식들 중 인덱스 몇 번째인지 찾아서 취득
// 댓글 입력 칸에 수정하려고 클릭한 댓글 내용이 들어옴
var con = $(this).parent().parent().children().eq(1).html();
// 수정 내용 넣기 전 <>변환
con = con.replace(/</gi, "<");
con = con.replace(/>/gi, ">");

꺽쇄가 있어도 잘 들어가고 디비에는 꺽쇄 대신 lt, gt가 들어가있다.

이 처리를 안 해주면
꺽쇄 뒤가 주석 처리 되어버리는? SQL Injection 문제가 발생한다.
그래서 아무거나 입력해도 로그인이 되기도 하고 불법적인 데이터에 취약해진다.
전체 코드
AOBController
package com.spring.sample.web.testa.controller;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.servlet.ModelAndView;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.spring.sample.common.service.IPagingService;
import com.spring.sample.web.testa.dao.IACDao;
@Controller
public class AOBController {
@Autowired
public IACDao dao;
@Autowired
public IPagingService ips;
@RequestMapping(value = "AOB")
public ModelAndView aob(ModelAndView mav) {
mav.setViewName("testa/AOB/ob");
return mav;
}
@RequestMapping(value = "/AOBAction/{gbn}",
method = RequestMethod.POST,
produces = "text/json;charset=UTF-8")
@ResponseBody
public String AOBAction(
@PathVariable String gbn,
@RequestParam HashMap<String, String> params) throws Throwable {
ObjectMapper mapper = new ObjectMapper ();
Map<String, Object> model = new HashMap<String, Object>();
int cnt = 0;
try {
switch(gbn) {
case "insert" : cnt = dao.insert("ob.insertOb", params);
break;
case "update" : cnt = dao.update("ob.updateOb", params);
break;
case "delete" : cnt = dao.update("ob.deleteOb", params);
break;
}
if(cnt > 0) {
model.put("msg", "success");
} else {
model.put("msg", "fail");
}
} catch (Exception e) {
e.printStackTrace();
model.put("msg", "error");
}
return mapper.writeValueAsString(model);
}
@RequestMapping(value = "/AOBList",
method = RequestMethod.POST,
produces = "text/json;charset=UTF-8")
@ResponseBody
public String AOBList(
@RequestParam HashMap<String, String> params) throws Throwable {
ObjectMapper mapper = new ObjectMapper ();
Map<String, Object> model = new HashMap<String, Object>();
int cnt = dao.getInt("ob.getObCnt", params);
HashMap<String, Integer> pd
= ips.getPagingData(Integer.parseInt(params.get("page")),
cnt, 10, 5); //페이지 정보
params.put("start", Integer.toString(pd.get("start")));
params.put("end", Integer.toString(pd.get("end")));
List<HashMap<String, String>> list = dao.getList("ob.getObList", params);
model.put("list", list);
model.put("pd", pd);
return mapper.writeValueAsString(model);
}
}
IACDao
package com.spring.sample.web.testa.dao;
import java.util.HashMap;
import java.util.List;
public interface IACDao {
//이렇게 쌍으로 다님
//숫자 취득
public int getInt(String sql) throws Throwable; //값 안 받고 조회만
public int getInt(String sql, HashMap<String, String> params) throws Throwable;
//문자열 취득
public String getString(String sql) throws Throwable;
public String getString(String sql, HashMap<String, String> params) throws Throwable;
//HashMap 취득
public HashMap<String, String> getMap(String sql) throws Throwable;
public HashMap<String, String> getMap(String sql, HashMap<String, String> params) throws Throwable;
//문자열 취득
public List<HashMap<String, String>> getList(String sql) throws Throwable;
public List<HashMap<String, String>> getList(String sql, HashMap<String, String> params) throws Throwable;
//등록
public int insert(String sql) throws Throwable;
public int insert(String sql, HashMap<String, String> params) throws Throwable;
//수정
public int update(String sql) throws Throwable;
public int update(String sql, HashMap<String, String> params) throws Throwable;
//삭제
public int delete(String sql) throws Throwable;
public int delete(String sql, HashMap<String, String> params) throws Throwable;
}
ACDao
package com.spring.sample.web.testa.dao;
import java.util.HashMap;
import java.util.List;
import org.apache.ibatis.session.SqlSession;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Repository;
@Repository
public class ACDao implements IACDao {
@Autowired
public SqlSession sqlSession;
@Override
public int getInt(String sql) throws Throwable {
return sqlSession.selectOne(sql);
}
@Override
public int getInt(String sql, HashMap<String, String> params) throws Throwable {
return sqlSession.selectOne(sql, params);
}
@Override
public String getString(String sql) throws Throwable {
return sqlSession.selectOne(sql);
}
@Override
public String getString(String sql, HashMap<String, String> params) throws Throwable {
return sqlSession.selectOne(sql, params);
}
@Override
public HashMap<String, String> getMap(String sql) throws Throwable {
return sqlSession.selectOne(sql);
}
@Override
public HashMap<String, String> getMap(String sql, HashMap<String, String> params) throws Throwable {
return sqlSession.selectOne(sql, params);
}
@Override
public List<HashMap<String, String>> getList(String sql) throws Throwable {
return sqlSession.selectList(sql);
}
@Override
public List<HashMap<String, String>> getList(String sql, HashMap<String, String> params) throws Throwable {
return sqlSession.selectList(sql, params);
}
@Override
public int insert(String sql) throws Throwable {
return sqlSession.insert(sql);
}
@Override
public int insert(String sql, HashMap<String, String> params) throws Throwable {
return sqlSession.insert(sql, params);
}
@Override
public int update(String sql) throws Throwable {
return sqlSession.update(sql);
}
@Override
public int update(String sql, HashMap<String, String> params) throws Throwable {
return sqlSession.update(sql, params);
}
@Override
public int delete(String sql) throws Throwable {
return sqlSession.delete(sql);
}
@Override
public int delete(String sql, HashMap<String, String> params) throws Throwable {
return sqlSession.delete(sql, params);
}
}
ob.jsp
<%@ page language="java" contentType="text/html; charset=UTF-8"
pageEncoding="UTF-8"%>
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>한줄게시판</title>
<!-- Common CSS -->
<link rel="stylesheet" type="text/css" href="resources/css/common/cmn.css" />
<!-- Popup CSS -->
<link rel="stylesheet" type="text/css" href="resources/css/common/popup.css" />
<style type="text/css">
#searchTxt {
width : 161px;
height: 28px;
padding: 0px 2px;
text-indent: 5px;
vertical-align: middle;
border : 1px solid #d7d7d7;
outline-color : #70adf9;
}
.search_area {
width : 800px;
text-align: right;
margin : 0 auto;
}
.board_area {
width : 800px;
margin : 0 auto;
}
.wrap {
width : 800px;
margin : 0 auto;
}
.mtb {
margin : 5px 0px;
}
.login {
vertical-align: baseline;
}
.con {
width : calc(100% - 22px);
height : 60px;
border : 1px soild #d7d7d7;
resize : none;
padding : 10px;
}
.update {
display : none;
}
.con_td {
font-size : 0;
}
.paging_area {
display: block;
position: relative;
left : 0;
margin-bottom : 10px;
}
.search_area {
text-align : center;
}
body {
overflow: auto;
}
</style>
<!-- JQuery -->
<script type="text/javascript"
src="resources/script/jquery/jquery-1.12.4.min.js"></script>
<!-- Popup JS -->
<script type="text/javascript"
src="resources/script/common/popup.js"></script>
<!-- Slimscroll JS -->
<script type="text/javascript"
src="resources/script/jquery/jquery.slimscroll.js"></script>
<script type="text/javascript">
$(document).ready(function() {
/*$("body").slimscroll({
height: "100%",
axis: "y"
});*/
reloadList();
$(".board_table #loginBtn").on("click", function() {
location.href = "testALogin";
});
$("#insertBtn").on("click", function() {
if($.trim($("#con").val()) == "") {
makeAlert("알림", "내용을 입력하세요.", function() {
$("#con").focus();
});
} else {
action("insert");
}
});
//페이징 클릭 시
$(".paging_area").on("click", "span", function() {
$("#page").val($(this).attr("page"));
//기존 값 유지
$("#searchGbn").val($("#oldGbn").val());
$("#searchText").val($("#oldText").val());
reloadList();
});
// 검색 버튼 클릭 시
// 기존 것 넘겨줘도 됨
$("#searchBtn").on("click", function() {
$("#page").val("1");
//기존 값 새 값으로 변경
$("#oldGbn").val($("#searchGbn").val());
$("#oldText").val($("#searchText").val());
reloadList();
});
// 목록의 삭제 버튼 클릭시
$("tbody").on("click", ".delete_btn", function() {
// 삭제 버튼 눌렀을 때 그 글의 번호를 가져가려면 그 버튼을 가진 td의 tr까지 올라가야 함
// 두 단계 올라가야 하고 길어서 변수에 담기
var no = $(this).parent().parent().attr("no");
// 팝업
makePopup({
title : "알림",
contents : "삭제 하시겠습니까?",
buttons : [{
name : "삭제",
func:function() {
// 삭제 전에 번호 가져가야 함
$("#no").val(no);
action("delete");
closePopup(); // 제일 위의 팝업 닫기
}
}, {
name : "취소"
}]
});
});
// 목록의 수정 버튼 클릭 시
$("tbody").on("click", ".update_btn", function() {
var no = $(this).parent().parent().attr("no");
$("#no").val(no);
// eq(인덱스 번호) : 자식들 중 인덱스 몇 번째인지 찾아서 취득
// 댓글 입력 칸에 수정하려고 클릭한 댓글 내용이 들어옴
var con = $(this).parent().parent().children().eq(1).html();
// 수정 내용 넣기 전 <>변환
con = con.replace(/</gi, "<");
con = con.replace(/>/gi, ">");
$("#con").val(con);
// 등록 버튼 감추기 + 수정,취소 버튼 나타나게 하기
$(".insert").hide();
$(".update").show();
//작성 영역에 포커스
$("#con").focus();
});
// 수정 영역의 취소 버튼 클릭 시
// 취소 버튼은 없었던 일로 하면 됨.
// 취소 버튼은 thead에 있음
$("thead #cancelBtn").on("click", function() {
// 입력 내용 초기화
$("#no").val("");
$("#con").val("");
// 등록 버튼 나타나기 + 수정,취소 버튼 감추기 (반대로 하는 거임)
$(".insert").show();
$(".update").hide();
});
// 수정 영역의 수정 버튼 클릭 시
$("thead #updateBtn").on("click", function() {
action("update");
});
});
var msg = {
"insert" : "등록",
"update" : "수정",
"delete" : "삭제",
}
function action(flag) {
// con의 <,> 들을 웹 문자로 변환 -> 디비에도 저 글자로 들어감
$("#con").val($("#con").val().replace(/</gi, "<"));
$("#con").val($("#con").val().replace(/>/gi, ">"));
// Javascript Object에서의 [] : 해당 키값으로 내용을 불러오거나 넣을 수 있다. Java의 Map에서 get, put역할
console.log(msg[flag]);
var params = $("#actionForm").serialize(); // 보낼수있는 형태로 만든 후 보낸다
$.ajax({
url : "AOBAction/" + flag, // 경로
type : "POST", // 전송방식(GET : 주소형태, POST : 주소 헤더형태)
dataType : "json", // 데이터 형태
data : params, // 보낼 데이터
success : function(res) {// 성공했을때 경과를 res에 받고 함수 실행
switch(res.msg) {
case "success" :
// 내용 초기화
$("#con").val("");
$("#no").val("");
// 목록 재조회
switch(flag) {
case "insert" :
case "delete" :
// 조회 데이터 초기화
$("#page").val("1");
$("#searchGbn").val("0");
$("#searchText").val("");
$("#oldGbn").val("0");
$("#oldText").val("");
break;
case "update" :
//기존 값 유지
$("#searchGbn").val($("#oldGbn").val());
$("#searchText").val($("#oldText").val());
// 수정한 뒤 다시 등록 버튼 활성화 시키기
// 입력 내용 초기화
$("#no").val("");
$("#con").val("");
// 등록 버튼 나타나기 + 수정,취소 버튼 감추기 (반대로 하는 거임)
$(".insert").show();
$(".update").hide();
break;
}
reloadList();
break;
case "fail" :
makeAlert("알림", msg[flag] + "에 실패하였습니다.");
break;
case "error" :
makeAlert("알림", msg[flag] + "중 문제가 발생하였습니다.");
break;
}
},
error : function(request, status, error) { // 실패했을때 함수 실행
console.log(request.responseText); // 실패 상세내역
}
}); // Ajax End
} // action Function End
//목록 조회 호출
function reloadList() {
var params = $("#searchForm").serialize();
$.ajax({
url: "AOBList",
type: "POST",
dataType: "json",
data : params,
success: function(res) {
drawList(res.list);
drawPaging(res.pd);
},
error : function(request, status, error) {
console.log(request.responseText);
}
});
}
function drawList(list) {
var html = "";
for(var data of list) {
html += "<tr no=\"" + data.NO + "\">";
html += "<td>" + data.MEM_NM + "</td>";
html += "<td>" + data.CON + "</td>";
html += "<td>" + data.DT + "</td>";
html += "<td>";
if("${sMemNo}" == data.MEM_NO) { // 작성자이면 수정,삭제 버튼 나타남
html += "<div class=\"cmn_btn mtb update_btn\">수정</div><br/>";
html += "<div class=\"cmn_btn mtb delete_btn\">삭제</div>";
}
html += "</td>";
html += "</tr>";
}
$("tbody").html(html); // 누적된 html을 tbody에 붙여야 함
}
// testa - T - list.jsp에서 그대로 가져옴
function drawPaging(pd) {
var html = "";
//처음
html += "<span class=\"page_btn page_first\" page=\"1\">처음</span>";
//이전
if($("#page").val() == "1") { //input에 들어가는 건 기본적으로 문자열이라 "1"
html += "<span class=\"page_btn page_prev\" page=\"1\">이전</span>";
} else {
html += "<span class=\"page_btn page_prev\" page=\"" + ($("#page").val() * 1 - 1) + "\">이전</span>";
//1 곱해주는 이유는 숫자로 만들어주는 것
}
for(var i = pd.startP; i <= pd.endP; i++) {
//현재 페이지와 i가 같을 때 bold 줌
if($("#page").val() * 1 == i) { //현재 페이지
html += "<span class=\"page_btn_on\" page=\"" + i + "\">" + i + "</span>";
} else { //다른 페이지
html += "<span class=\"page_btn\" page=\"" + i + "\">" + i + "</span>";
}
}
//다음
if($("#page").val() * 1 == pd.maxP) { //현재 페이지가 마지막 페이지라면
html += "<span class=\"page_btn page_next\" page=\"" + pd.maxP + "\">다음</span>";
} else {
html += "<span class=\"page_btn page_next\" page=\"" + ($("#page").val() * 1 + 1) + "\">다음</span>";
}
//마지막
html += "<span class=\"page_btn page_last\" page=\"" + pd.maxP + "\">마지막</span>";
$(".paging_area").html(html);
}
</script>
</head>
<body>
<c:import url="/testAHeader"></c:import>
<hr/>
<div class="wrap">
<div class="board_area">
<!-- 작성 또는 로그인 -->
<!-- 목록 -->
<table class="board_table">
<colgroup>
<!-- 작성자 -->
<col width="100" />
<!-- 내용 -->
<col width="500" />
<!-- 날짜 -->
<col width="100" />
<!-- 버튼 -->
<col width="100" />
</colgroup>
<thead>
<c:choose>
<c:when test="${empty sMemNo}"><!-- 비 로그인 시 -->
<tr>
<th colspan="4">
로그인이 필요한 서비스 입니다.
<div class="cmn_btn login" id="loginBtn">로그인</div>
</th>
</tr>
</c:when>
<c:otherwise><!-- 로그인 시 -->
<tr>
<th>${sMemNm}</th>
<td colspan="2" class="con_td">
<form action="#" id="actionForm">
<input type="hidden" name="no" id="no"/>
<input type="hidden" name="memNo" value="${sMemNo}"/>
<textarea class="con" name="con" id="con"></textarea>
</form>
</td>
<th>
<div class="insert">
<div class="cmn_btn" id="insertBtn">등록</div>
</div>
<div class="update">
<div class="cmn_btn mtb" id="updateBtn">수정</div><br/>
<div class="cmn_btn mtb" id="cancelBtn">취소</div>
</div>
</th>
</tr>
</c:otherwise>
</c:choose>
<tr>
<th>작성자</th>
<th>내용</th>
<th>작성일</th>
<th> </th>
</tr>
</thead>
<tbody>
<!-- reloadList로 그릴 거임 -->
<!-- <tr>
<td>홍길동</td>
<td>테스트</td>
<td>10:16</td>
<td>
<div class="cmn_btn mtb">수정</div><br/>
<div class="cmn_btn mtb">삭제</div>
</td>
</tr> -->
</tbody>
</table>
<!-- 페이징 -->
<div class="paging_area"></div>
</div>
<div class="search_area">
<!-- 검색어 유지용 -->
<input type="hidden" id="oldGbn" value="0" />
<input type="hidden" id="oldText" />
<form action="#" id="searchForm">
<input type="hidden" name="page" id="page" value="1" />
<select name="searchGbn" id="searchGbn">
<option value="0">작성자</option>
<option value="1">내용</option>
</select>
<input type="text" name="searchText" id="searchText" />
<div class="cmn_btn_ml" id="searchBtn">검색</div>
</form>
</div>
</div>
</body>
</html>
testa - login - login.jsp
<%@ 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>
<!-- cmn CSS -->
<link rel="stylesheet" type="text/css" href="resources/css/common/cmn.css" />
<!-- Popup CSS -->
<link rel="stylesheet" type="text/css" href="resources/css/common/popup.css" />
<style type="text/css">
#actionForm {
font-size: 11pt;
}
</style>
<!-- JQuery -->
<script type="text/javascript"
src="resources/script/jquery/jquery-1.12.4.min.js"></script>
<!-- popup -->
<script type="text/javascript"
src="resources/script/common/popup.js"></script>
<!-- cmn JQuery -->
<script type="text/javascript"
src="resources/script/common/common.js"></script>
<script type="text/javascript">
$(document).ready(function(){
$("#actionForm").on("keypress", "input", function(event) {
if(event.keyCode == 13) {
$("#loginBtn").click();
return false;
}
});
$("#loginBtn").on("click", function(){
if($.trim($("#id").val()) == "") {
makeAlert("알림", "아이디를 입력하세요.", function(){
$("#id").focus();
});
} else if($.trim($("#pw").val()) == "") {
makeAlert("알림", "비밀번호를 입력하세요.", function(){
$("#pw").focus();
});
} else {
var params = $("#actionForm").serialize();
$.ajax({
url: "testALoginAjax",
type: "POST",
dataType: "json",
data : params,
success: function(res) {
if(res.msg == "success") {
location.href = "testAMain";
} else {
makeAlert("알림", "아이디나 비밀번호가 틀립니다.");
}
},
error : function(request, status, error) {
console.log(request.responseText);
}
});
}
});
});
</script>
</head>
<body>
<form action="#" id="actionForm">
아이디 <input type="text" name="id" id="id" /><br/>
비밀번호 <input type="password" name="pw" id="pw" /><br/>
</form>
<input type="button" value="로그인" id="loginBtn" />
</body>
</html>
testa - login - header.jsp
<%@ page language="java" contentType="text/html; charset=UTF-8"
pageEncoding="UTF-8"%>
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
<script type="text/javascript">
$(document).ready(function() { //제이쿼리 없지만 동작할 거임
$("#logoutBtn").on("click", function() {
location.href = "testALogout";
});
$("#loginBtn").on("click", function() {
location.href = "testALogin";
});
});
</script>
<c:choose>
<c:when test="${empty sMemNm}"> <!-- 세션이 비어 있다면 -->
<input type="button" value="로그인" id="loginBtn" />
</c:when>
<c:otherwise>
${sMemNm}님 어서오세요. <input type="button" value="로그아웃" id="logoutBtn" />
</c:otherwise>
</c:choose>
testa - login - main.jsp
<%@ page language="java" contentType="text/html; charset=UTF-8"
pageEncoding="UTF-8"%>
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Insert title here</title>
<script type="text/javascript"
src="resources/script/jquery/jquery-1.12.4.min.js"></script>
<script type="text/javascript">
$(document).ready(function() {
});
</script>
</head>
<body>
<c:import url="/testAHeader"></c:import>
<br/>
<a href="ATList">게시글 목록</a>
<br/>
<a href="AOB">댓글</a>
</body>
</html>
OB_SQL.xml
<?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="ob">
<insert id="insertOb" parameterType="hashmap">
INSERT INTO OB(NO, MEM_NO, CON)
VALUES (OB_SEQ.NEXTVAL, #{memNo}, #{con})
</insert>
<update id="updateOb" parameterType="hashmap">
UPDATE OB SET CON = #{con}
WHERE NO = #{no}
</update>
<update id="deleteOb" parameterType="hashmap">
UPDATE OB SET DEL = 0
WHERE NO = #{no}
</update>
<select id="getObCnt" parameterType="hashmap" resultType="Integer">
SELECT COUNT(*) AS CNT
FROM OB O INNER JOIN MEM M
ON O.MEM_NO = M.MEM_NO
AND M.DEL = 1
WHERE O.DEL = 1
<if test="searchText != null and searchText != ''">
<choose>
<when test="searchGbn eq 0">
AND M.MEM_NM LIKE '%' || #{searchText} || '%'
</when>
<when test="searchGbn eq 1">
AND O.CON LIKE '%' || #{searchText} || '%'
</when>
</choose>
</if>
</select>
<select id="getObList" parameterType="hashmap" resultType="hashmap">
SELECT O.NO, O.MEM_NO, O.MEM_NM, O.CON, O.DT
FROM (SELECT O.NO, O.MEM_NO, M.MEM_NM, O.CON,
CASE WHEN TO_CHAR(O.DT, 'YY.MM.DD') = TO_CHAR(SYSDATE, 'YY.MM.DD')
THEN TO_CHAR(O.DT, 'HH24:MI')
ELSE TO_CHAR(O.DT, 'YY.MM.DD')
END AS DT,
ROW_NUMBER() OVER(ORDER BY O.NO DESC) AS RNUM
FROM OB O INNER JOIN MEM M
ON O.MEM_NO = M.MEM_NO
AND M.DEL = 1
WHERE O.DEL = 1
<if test="searchText != null and searchText != ''">
<choose>
<when test="searchGbn eq 0">
AND M.MEM_NM LIKE '%' || #{searchText} || '%'
</when>
<when test="searchGbn eq 1">
AND O.CON LIKE '%' || #{searchText} || '%'
</when>
</choose>
</if>) O
WHERE O.RNUM BETWEEN #{start} AND #{end}
</select>
</mapper>
'학원 > 수업 기록' 카테고리의 다른 글
| TBOARD 수정, CATE 생성(카테고리) 실습 (0) | 2022.08.25 |
|---|---|
| AOP 관점 지향 프로그래밍 (0) | 2022.08.25 |
| 데이터베이스 어려운 문제.. 집계 함수에 대해 이해하기.. - OT (0) | 2022.07.15 |
| 생성자, main, 인자, 메소드, void, 반환값, 상속, Object 클래스, 오버라이딩 (0) | 2022.06.22 |
| switch, 반복문(while, do~while, for), 다중 반복문, 마름모 만들기, 별 찍기 (0) | 2022.06.17 |