학원/수업 기록

9월 2일 통합 구현 시험 (비동기 가계부)

60cod 2022. 9. 2. 15:40

 

 

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(/&lt;/gi, "<");
		price = price.replace(/&gt;/gi, ">");
		info = info.replace(/&lt;/gi, "<");
		info = info.replace(/&gt;/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, "&lt;"));
	$("#price").val($("#price").val().replace(/>/gi, "&gt;"));
	$("#info").val($("#info").val().replace(/</gi, "&lt;"));
	$("#info").val($("#info").val().replace(/>/gi, "&gt;"));
	
   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>&nbsp;</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 사용법