9월 2일 통합 구현 시험 (비동기 가계부)
ACCBOOK 테이블 구조
전체 코드
이름ACCController
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 이름ACCController {
@Autowired
public IACDao dao;
@Autowired
public IPagingService ips;
@RequestMapping(value = "ACC")
public ModelAndView acc(ModelAndView mav) {
mav.setViewName("testa/ACC/이름");
return mav;
}
@RequestMapping(value = "/ACCAction/{gbn}",
method = RequestMethod.POST,
produces = "text/json;charset=UTF-8")
@ResponseBody
public String ACCAction(
@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("acc.insertAcc", params);
break;
case "update" : cnt = dao.update("acc.updateAcc", params);
break;
case "delete" : cnt = dao.update("acc.deleteAcc", 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 = "/ACCList",
method = RequestMethod.POST,
produces = "text/json;charset=UTF-8")
@ResponseBody
public String ACCList(
@RequestParam HashMap<String, String> params) throws Throwable {
ObjectMapper mapper = new ObjectMapper ();
Map<String, Object> model = new HashMap<String, Object>();
int cnt = dao.getInt("acc.getAccCnt", 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("acc.getAccList", params);
List<HashMap<String, String>> list2 = dao.getList("acc.getTotal", params);
model.put("list", list);
model.put("list2", list2);
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);
}
}
testa - ACC - 이름.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">
.board_area {
width : 800px;
margin : 0 auto;
}
.wrap {
width : 800px;
margin : 0 auto;
}
.mtb {
margin : 5px 0px;
}
.update {
display : none;
}
.paging_area {
display: block;
position: relative;
left : 0;
margin-bottom : 10px;
}
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() {
reloadList();
$("#insertBtn").on("click", function() {
if($.trim($("#price").val()) == "") {
makeAlert("알림", "금액을 입력하세요.", function() {
$("#price").focus();
});
} else if($.trim($("#info").val()) == "") {
makeAlert("알림", "내역을 입력하세요.", function() {
$("#info").focus();
});
} else if($("#date").val() == "") {
makeAlert("알림", "일자를 입력하세요.", function() {
$("#date").focus();
});
}else {
action("insert");
}
});
//페이징 클릭 시
$(".paging_area").on("click", "span", function() {
$("#page").val($(this).attr("page"));
reloadList();
});
// 목록의 삭제 버튼 클릭시
$("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 : "취소"
}]
});
});
// 목록의 수정 버튼 클릭 시
$("tbody").on("click", ".update_btn", function() {
var no = $(this).parent().parent().attr("no");
$("#no").val(no);
// 입력 칸에 수정하려고 클릭한 내용이 들어옴
var price = $(this).parent().parent().children().eq(1).html();
var info = $(this).parent().parent().children().eq(3).html();
var date = $(this).parent().parent().children().eq(4).html();
if($(this).parent().parent().children().eq(2).html() == '지출') {
var at = "0";
} else {
at = "1"
}
$("#accType").val(at);
// 수정 내용 넣기 전 <>변환
price = price.replace(/</gi, "<");
price = price.replace(/>/gi, ">");
info = info.replace(/</gi, "<");
info = info.replace(/>/gi, ">");
$("#price").val(price);
$("#info").val(info);
$("#date").val(date);
// 등록 버튼 감추기 + 수정,취소 버튼 나타나게 하기
$(".insert").hide();
$(".update").show();
//작성 영역에 포커스
$("#price").focus();
});
// 수정 영역의 취소 버튼 클릭 시
// 취소 버튼은 없었던 일로 하면 됨.
// 취소 버튼은 thead에 있음
$("thead #cancelBtn").on("click", function() {
// 입력 내용 초기화
$("#no").val("");
$("#accType").val("0");
$("#price").val("");
$("#info").val("");
$("#date").val("");
$(".insert").show();
$(".update").hide();
});
// 수정 영역의 수정 버튼 클릭 시
$("thead #updateBtn").on("click", function() {
action("update");
});
});
var msg = {
"insert" : "등록",
"update" : "수정",
"delete" : "삭제",
}
function action(flag) {
$("#price").val($("#price").val().replace(/</gi, "<"));
$("#price").val($("#price").val().replace(/>/gi, ">"));
$("#info").val($("#info").val().replace(/</gi, "<"));
$("#info").val($("#info").val().replace(/>/gi, ">"));
var params = $("#actionForm").serialize(); // 보낼수있는 형태로 만든 후 보낸다
$.ajax({
url : "ACCAction/" + flag, // 경로
type : "POST", // 전송방식(GET : 주소형태, POST : 주소 헤더형태)
dataType : "json", // 데이터 형태
data : params, // 보낼 데이터
success : function(res) {// 성공했을때 경과를 res에 받고 함수 실행
switch(res.msg) {
case "success" :
// 내용 초기화
$("#accType").val("0");
$("#price").val("");
$("#info").val("");
$("#date").val("");
$("#no").val("");
// 목록 재조회
switch(flag) {
case "insert" :
case "delete" :
// 조회 데이터 초기화
$("#page").val("1");
$("#accType").val("0");
break;
case "update" :
// 수정한 뒤 다시 등록 버튼 활성화 시키기
// 입력 내용 초기화
$("#accType").val("0");
$("#price").val("");
$("#info").val("");
$("#date").val("");
$("#no").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: "ACCList",
type: "POST",
dataType: "json",
data : params,
success: function(res) {
totalList(res.list2);
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.NO + "</td>";
html += "<td>" + data.PRICE + "</td>";
html += "<td>" + data.ACC_TYPE + "</td> ";
html += "<td>" + data.INFO + "</td>";
html += "<td>" + data.DT + "</td>";
html += "<td>";
html += "<div class=\"cmn_btn mtb update_btn\">수정</div><br/>";
html += "<div class=\"cmn_btn mtb delete_btn\">삭제</div>";
html += "</td>";
html += "</tr>";
}
$("#accList tbody").html(html); // 누적된 html을 tbody에 붙여야 함
}
function totalList(list2) {
var html = "";
for(var data of list2) {
html += "<tr>";
html += "<td>" + data.OUTCOME + "</td> ";
html += "<td>" + data.INCOME + "</td>";
html += "<td>" + data.TOTAL + "</td> ";
html += "</tr>";
}
$("#totalList tbody").html(html);
}
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);
}
function accFn() {
}
</script>
</head>
<body>
<div class="wrap">
<div class="board_area">
<!-- 총계 테이블 -->
<table class="board_table" id="totalList">
<thead>
<tr>
<th>지출</th>
<th>수입</th>
<th>총액</th>
</tr>
</thead>
<tbody>
<!-- totalList -->
</tbody>
</table>
<!-- 목록 -->
<table class="board_table" id="accList">
<colgroup>
<!-- 번호 -->
<col width="80" />
<!-- 금액 -->
<col width="100" />
<!-- 지출/수입 -->
<col width="80" />
<!-- 내역 -->
<col width="400" />
<!-- 일자 -->
<col width="100" />
<!-- 버튼 -->
<col width="100" />
</colgroup>
<thead>
<tr>
<th colspan="5">
<form action="#" id="actionForm">
<input type="hidden" name="no" id="no"/>
<select name="accType" id="accType">
<option value="0">지출</option>
<option value="1">수입</option>
</select>
<input type="number" placeholder="금액" name="price" id="price"/>
<input type="text" placeholder="내역" name="info" id="info"/>
<input type="date" name="date" id="date"/>
</form>
</th>
<td>
<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>
</td>
</tr>
<tr>
<th>번호</th>
<th>금액</th>
<th>지출/수입</th>
<th>내역</th>
<th>일자</th>
<th> </th>
</tr>
</thead>
<tbody>
<!-- reloadList로 그릴 거임 -->
</tbody>
</table>
<!-- 페이징 -->
<div class="paging_area"></div>
<form action="#" id="searchForm">
<input type="hidden" name="page" id="page" value="1" />
</form>
</div>
</div>
</body>
</html>
이름ACC_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="acc">
<insert id="insertAcc" parameterType="hashmap">
INSERT INTO ACCBOOK (NO, PRICE, ACC_TYPE, INFO, DT)
VALUES(ACCBOOK_SEQ.NEXTVAL, #{price}, #{accType}, #{info}, #{date})
</insert>
<update id="updateAcc" parameterType="hashmap">
UPDATE ACCBOOK SET PRICE = #{price},
INFO = #{info},
DT = #{date},
ACC_TYPE = #{accType}
WHERE NO = #{no}
</update>
<update id="deleteAcc" parameterType="hashmap">
UPDATE ACCBOOK SET DEL = 0
WHERE NO = #{no}
</update>
<select id="getAccCnt" parameterType="hashmap" resultType="Integer">
SELECT COUNT(*) AS CNT
FROM ACCBOOK
WHERE DEL = 1
</select>
<select id="getAccList" parameterType="hashmap" resultType="hashmap">
SELECT A.NO, A.PRICE, A.ACC_TYPE, A.INFO, A.DT
FROM(SELECT NO, PRICE, INFO,
TO_CHAR(DT, 'YYYY-MM-DD') AS DT,
CASE ACC_TYPE
WHEN 1 THEN '수입'
WHEN 0 THEN '지출'
END AS ACC_TYPE,
ROW_NUMBER() OVER(ORDER BY NO DESC) AS RNUM
FROM ACCBOOK
WHERE DEL = 1) A
WHERE A.RNUM BETWEEN #{start} AND #{end}
</select>
<select id="getTotal" parameterType="hashmap" resultType="hashmap">
SELECT A.INCOME, A.OUTCOME, (A.INCOME - A.OUTCOME) AS TOTAL
FROM (SELECT MAX(DECODE(A.ACC_TYPE, 0, A.SUM)) AS OUTCOME,
MAX(DECODE(A.ACC_TYPE, 1, A.SUM)) AS INCOME
FROM (SELECT ACC_TYPE, SUM(PRICE) AS SUM
FROM ACCBOOK
WHERE DEL = 1
GROUP BY ACC_TYPE)A)A
</select>
</mapper>
헷갈렸던 포인트
1. 목록을 불러오지 않았다.
알고보니 복붙해온 코드에서 검색 영역을 지우면서 페이지에 1을 담아주는 히든 인풋을 보내주는 폼 태그도 같이 지워버린 것..
그리고 reloadList 함수에서 serialize한 서치폼을 변수 params에 담는 코드도 지워버렸었음.. 그냥 서치 글자만 보고 서치 관련인 줄 알고 다 지워버렸나보다.
2. select 박스 선택 항목 받아오기
select 박스, input 입력 칸들을 actionForm 폼 태그로 감싸서 값 보내준다.
3. 숫자 데이터로 저장해둔 select 박스 선택 값을 문자열로 불러와야 할 때.
문제대로 디비에는 지출을 0, 수입을 1로 저장해두었는데 이걸 목록에는 '지출', '수입' 문자열로 보여야 한다.
목록에 띄울 때만 바꿔주면 되니까 목록 만드는 select문 쿼리에 CASE 를 사용했다.
이건 처음부터 푼 거긴 한데 헷갈릴 수도 있을 것 같아서 적어봄.
4. 수정 버튼 눌러서 수정 칸에 기존 내용 불러올 때 select 박스는?
기존 내용 불러오는 건 ajax로 해도 되는데 eq로 해봤다.
select 박스의 경우는 디비에서는 숫자인데 html 테이블 상에서는 문자열로 들어가 있으므로
if문 사용해서 값을 바꿔주었다.
5. 지출, 수입, 총액 구하기
처음에 고민을 좀 하다가 쿼리로 다 구현하기로 하고
서브쿼리, SUM 함수, DECODE를 사용했는데 이제 보니 더 쉽게 할 수 있었다.
SELECT I.SUM AS INCOME, O.SUM AS OUTCOME,
(I.SUM - O.SUM) AS TOTAL
FROM (SELECT NVL(SUM(PRICE),0) AS SUM
FROM ACCBOOK
WHERE ACC_TYPE = 1
AND DEL=1) I INNER JOIN
(SELECT NVL(SUM(PRICE),0) AS SUM
FROM ACCBOOK
WHERE ACC_TYPE = 0
AND DEL=1) O
ON 1 = 1
6. 5번을 불러와서 비동기로 띄우기.
xml 파일에 id 따로 지정해서 select문으로 쿼리 담아주고
컨트롤러에 getACCList 주소에 그 쿼리로 받은 데이터를 담아주는 변수 list2를 만들어주고 model에 넣고
jsp에서 totalList 함수 만들고 list2에 대해 for문 돌려서 변수 html에 누적시키고
그 html을 내가 갈아엎고자 하는 html 자리에 지정해서 넣어준다.
그리고 reloadList 함수 성공 시에 이거도 불러오게 하기.
+ 등록 버튼 누를 때 날짜 지정 안 하면 자동으로 오늘 날짜 들어가게 하고 싶어서 아래처럼 코드 짜봤는데
그랬더니 등록 버튼 한 번에 등록이 안 되고 오늘 날짜부터 불러온 다음 다시 눌러야 등록이 되더라..
그래서 고치려다가 할 거 많아서 일단 닫음..
✔️추가로 더 공부할 것
- serialize 정의
- parent(), children() 사용법
- ajax 사용법