Spring

[Spring / 비동기 #8] RESTful_scoreApp 실습_22.07.29 [13일차]

양빵빵 2022. 7. 29. 10:51
package com.spring.webmvc.springmvc.chap02.repository;

import com.spring.webmvc.springmvc.chap02.domain.Score;
import org.apache.ibatis.annotations.Mapper;

import java.util.List;

@Mapper
public interface ScoreMapper {


    // 점수 저장
    boolean save(Score score);

    // 전체 점수 정보 조회
    List<Score> findAll(String sort);

    // 개별 점수 조회
    Score findOne(int stuNum);

    // 점수 정보 삭제
    boolean remove(int stuNum);

    // 평균 1등 정보 구하기
    List<Score> findFirst();

    // 평균 꼴등 정보 구하기
    List<Score> findlast();


}

 

package com.spring.webmvc.springmvc.chap02.repository;

import com.spring.webmvc.springmvc.chap02.domain.Score;
import org.springframework.jdbc.core.RowMapper;

import java.sql.ResultSet;
import java.sql.SQLException;

public class ScoreRowMapper implements RowMapper<Score> {
    @Override
    public Score mapRow(ResultSet rs, int rowNum) throws SQLException {

        return new Score(rs);

//        Score s = new Score(rs);
//        s.setStuNum(rs.getInt("stu_num"));
//        s.setName(rs.getString("stu_name"));
// 입력할게 많으니 간편하게 처리해보자
        // 생성자에 rs를 넘긴다.
        // 컬럼을 읽어서 스코어 필드 어디에 맵핑할 것인지를 알려줘야 함.

    }
}

 

<?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 태그는 sql을 매핑할때 사용하는 태그
    nmaespace 속성에는 사용할 인터페이스의 풀 패키지경로 + 인터페이스이름
-->
<mapper namespace="com.spring.webmvc.springmvc.chap02.repository.ScoreMapper">


<!--   점수 저장    -->
    <insert id="save">
        INSERT INTO tbl_score
        VALUES (seq_tbl_score.nextval, #{name}, #{kor}, #{eng}, #{math}, #{total}, #{average}, #{grade})
    </insert>

<!--  점수 정보 삭제  -->
    <delete id="remove">
        DELETE FROM tbl_score
        WHERE stu_num = #{stuNum}
    </delete>
    <resultMap id="scoreMap" type="com.spring.webmvc.springmvc.chap02.domain.Score">
        <result property="stuNum" column="stu_num"></result>
        <result property="name" column="stu_name"></result>

    </resultMap>


<!--  동적 SQL 작성법   -->
<!-- // 전체 점수 정보 조회    -->
    <select id="findAll" resultMap="scoreMap">
        SELECT * FROM tbl_score
        <if test="sort == 'num'">
            ORDER BY stu_num
        </if>
        <if test="sort == 'name'">
            ORDER BY stu_name
        </if>
        <if test="sort == 'average'">
            ORDER BY average DESC
        </if>
    </select>


    <!--  개별 점수 정보 조회  -->
    <select id="findOne" resultMap="scoreMap">
        SELECT * FROM tbl_score
        WHERE stu_num = #{stuNum}

    </select>


</mapper>








 

package com.spring.webmvc.springmvc.chap02.api;

import com.spring.webmvc.springmvc.chap02.domain.Score;
import com.spring.webmvc.springmvc.chap02.repository.ScoreMapper;
import lombok.RequiredArgsConstructor;
import lombok.extern.log4j.Log4j2;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.*;

import java.util.HashMap;
import java.util.List;
import java.util.Map;

@RestController
@RequiredArgsConstructor
@Log4j2
@CrossOrigin
@RequestMapping("/api/score")
public class ScoreApiController {

    private final ScoreMapper mapper;

    // 점수 등록 및 조회 화면 열기 요청
    @GetMapping("")
    public List<Score> list(){
        String sort = "name";
        List<Score> all = mapper.findAll(sort);
        log.info("/api/score/ GET!");
        return all;
    }

    // 점수 신규등록 요청
    @PostMapping("")
    public String register(@RequestBody Score score) {
        score.calcTotalAndAvg();
        score.calcGrade();
        log.info("/api/score POST! score: {} -" , score);
        boolean flag = mapper.save(score);
        return flag?"insert-success":"insert-fail";

    }


/*        @RequestMapping("score/detail")
        public ModelAndView detail(int sutNum) {
            log.info("score/detail GET - param1: {}", sutNum);
            Score score = repository.findOne(sutNum);

            ModelAndView mv = new ModelAndView("chap02/score-detail");
            mv.addObject("s",score);

            return mv;
        }*/

    @GetMapping("/{stuNum}")
    public Score detail(@PathVariable int stuNum) {
        log.info("/api/score GET 요청!! - ");
        Score one = mapper.findOne(stuNum);
        return one;
    }



    @DeleteMapping("/{stuNum}")
    public String delete(@PathVariable int stuNum) {
        log.info("/api/score GET 요청!! - stuNum{} ",stuNum);
        boolean flag = mapper.remove(stuNum);

        return flag ? "delete-success" :"delete-fail";
    }



}

 

 

<!DOCTYPE html>
<html lang="ko">
<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
    <style>
        label {
            display: block;
        }

        .del-btn {
            width: 10px;
            height: 10px;
            background: red;
            color: white;
            border-radius: 5px;
            margin-left: 5px;
            text-decoration: none;
            font-size: 0.7em;
            padding: 6px;
        }

        .del-btn:hover {
            background: orangered;
        }

        li {
            margin-bottom: 10px;
        }

        .score-list>li:first-child {
            font-size: 1.2em;
            color: blue;
            font-weight: 700;
            border-bottom: 1px solid skyblue;
            margin-bottom: 10px;
        }

        label span[class$='span'] {
            color: red;
        }
    </style>
</head>
<body>
    <h1>시험 점수 등록</h1>

    <label>
        # 이름: <input type="text" name="name">
    </label>
    <label>
        # 국어: <input type="text" name="kor"> <span class="kor-span"></span>
    </label>
    <label>
        # 영어: <input type="text" name="eng"> <span class="eng-span"></span>
    </label>
    <label>
        # 수학: <input type="text" name="math"> <span class="math-span"></span>
    </label>
    <div>
        <button id="reg-btn">확인</button>
    </div>

    <hr>


    <ul class="score-list">
        <li class="stuNum">총 학생 수 : <span class="stu-count">1</span></li>
    </ul>

    <script>

        const URL = 'http://localhost:8282/api/score';

        //함수 선언부

        //점수 삭제 요청
        function deleteScoreData($target) {

            const xhr = new XMLHttpRequest();
            const stuNum = $target.dataset.stuNum;
            console.log('학번: ' + stuNum);
            xhr.open('DELETE', URL + '/' + stuNum);
            xhr.send();

            xhr.onload = e => {
                if (xhr.status === 200) {
                    removeAll();
                    getList();
                } else {
                    alert('삭제 실패!');
                }
            };
        }

        //점수리스트 렌더링
        function makeScoreListDOM(resp) {
            const scoreList = JSON.parse(resp);
            //총 학생수 숫자처리
            document.querySelector('.stu-count').textContent = scoreList.length;


            const $ul = document.querySelector('.score-list');
            for (let s of scoreList) {
                //객체 디스트럭쳐링
                const {stuNum, name, kor, eng, math, total, average} = s;
                //li태그 생성
                let tag = `
                    <li>
                        # 학번: ${stuNum}, 이름: ${name}, 국어: ${kor}점, 영어: ${eng}점, 수학: ${math}점, 총점: ${total}점, 평균: ${average}
                        <a data-stu-num='${stuNum}' class='del-btn' href='#'>삭제</a>
                    </li>    
                `;
                $ul.innerHTML += tag;

            }//for문 내부

            //삭제버튼 클릭이벤트
            $ul.addEventListener('click', e => {
                e.preventDefault();
                if (!e.target.matches('ul li .del-btn')) return;
                //console.log('삭제버튼 클릭!');

                deleteScoreData(e.target);

            });
        }

        //서버의 전체 성적정보 부르기
        function getList() {
            const xhr = new XMLHttpRequest();
            xhr.open('GET', URL);
            xhr.send();

            xhr.onload = e => {
                if (xhr.status === 200) {
                    makeScoreListDOM(xhr.response);
                } else {
                    alert('통신 실패: ' + xhr.status);
                }
            };
        }

        //등록시 숫자입력창 범위 검사
        const $korInput = document.querySelector('input[name=kor]');
        const $engInput = document.querySelector('input[name=eng]');
        const $mathInput = document.querySelector('input[name=math]');

        function numberValidation() {
           
            if (+$korInput.value < 0 || +$korInput.value > 100) return 1;            
            if (+$engInput.value < 0 || +$engInput.value > 100) return 2;
            if (+$mathInput.value < 0 || +$mathInput.value > 100) return 3;

            return 4;
        }
        //검증 CSS처리
        function validationCSS(flagNum) {
            let className = '';
            if (flagNum === 1) {
                className = '.kor-span';
            } else if (flagNum === 2) {
                className = '.eng-span';
            } else if (flagNum === 3) {
                className = '.math-span';
            }
            if (flagNum !== 4) {
                document.querySelector(className).textContent = '점수는 1~100점 사이로 입력하시오~';
            }
        }

        //기존 ul의 li태그들 전체제거
        function removeAll() {
            const $ul = document.querySelector('.score-list');
            for (let $li of [...$ul.children]) {
                if ($li.classList.contains('stuNum')) continue;
                $ul.removeChild($li);
            }
        }

        //서버로 데이터 등록요청
        function sendScoreData() {
            const xhr = new XMLHttpRequest();
            xhr.open('POST', URL);
            xhr.setRequestHeader('content-type', 'application/json');
            xhr.send(JSON.stringify({
                name: document.querySelector('input[name=name]').value,
                kor: +$korInput.value,
                eng: +$engInput.value,
                math: +$mathInput.value
            }));

            xhr.onload = e => {
                console.log(xhr.status);
                if (xhr.status === 200) {
                    //기존 렌더링 제거
                    removeAll();
                    //새로운 데이터 렌더링
                    getList();
                } else {
                    alert('등록 실패!');
                }
            };
        }


        //메인 실행부
        (() => {

            //서버에 있는 성적정보 리스트 불러오기
            getList();

            //등록버튼 이벤트처리
            document.querySelector('#reg-btn').onclick = e => {

                //입력란 검사(숫자 검증)
                const flagNum = numberValidation();
                validationCSS(flagNum);

                //서버 데이터 전송
                sendScoreData();
            };
        })();

    </script>

</body>
</html>