관리 메뉴

SIMPLE & UNIQUE

1-4강 : AWS DB서버 연결, 데이터 조회해 리스트 페이지에 노출 본문

탈잉 강의 자료/2021_비전공자도 가능한 웹 프로젝트

1-4강 : AWS DB서버 연결, 데이터 조회해 리스트 페이지에 노출

착한코딩 2021. 4. 1. 00:00

1-4강_완료코드.zip
0.28MB
1-4강_완료 덮어쓰기 코드.zip
0.01MB

1_4 목표 : node서버에 이미 테이블과 데이터가 세팅되어 있는 튜터의 AWS DB서버(RDS)를 연결한다. SELECT문을 사용해 데이터를 조회하고, 조회 결과를 리스트 페이지에 출력한다.

 

1. 소프트웨어 툴 데이터를 노출할, SoftwareList 컴포넌트를 추가한다.

C:\react200\client\src\components\SoftwareListManage\SoftwareList.js

앞 강에서 공공데이터 api를 호출해, 데이터를 화면에 노출했던 방법과 동일하다. 차이점은 앞 강에서는 외부 사이트에서 데이터를 가져왔고, 이번 강에서는 우리가 만든 node api를 호출해 데이터를 가져온다는 것이다.

axios로 node 서버의 api경로를 호출한다. 그런데 localhost:5000/api/Swtool?type=list가 아닌 /api/Swtool?type=list경로만 호출한다. react package.json파일에 node서버 루트 경로(http://127.0.0.1:5000)를 proxy설정했기 때문에, 루트 경로없이 간단한 상대경로를 사용할 수 있다.

import React, { Component } from 'react';
import {Link} from 'react-router-dom';
import axios from "axios";

class SoftwareList extends Component {
    constructor(props) {
        super(props);

        this.state = {
            responseSwtoolList: '',
            append_SwtoolList: '',
        }
    }

    componentDidMount() {
        this.callSwToolListApi()
    }

    callSwToolListApi = async () => {
        axios.post('/api/Swtool?type=list', {
        })
        .then( response => {
            try {
                this.setState({ responseSwtoolList: response });
                this.setState({ append_SwtoolList: this.SwToolListAppend() });
            } catch (error) {
                alert('작업중 오류가 발생하였습니다.');
            }
        })
        .catch( error => {alert('작업중 오류가 발생하였습니다.');return false;} );
    }

    SwToolListAppend = () => {
        let result = []
        var SwToolList = this.state.responseSwtoolList.data
        
        for(let i=0; i<SwToolList.json.length; i++){
            var data = SwToolList.json[i]

            var date = data.reg_date
            var year = date.substr(0,4)
            var month = date.substr(4,2)
            var day = date.substr(6,2)
            var reg_date = year +'.'+month+'.'+day

            result.push(
                <tr class="hidden_type">
                    <td>{data.swt_toolname}</td>
                    <td>{data.swt_function}</td>
                    <td>{reg_date}</td>
                    <td>
                        <Link to={'/SoftwareView/'+data.swt_code} 
                        className="bt_c1 bt_c2 w50_b">수정</Link>
                        <a href="#n" class="bt_c1 w50_b" id={data.swt_code}
                        onClick={(e) => this.deleteSwtool(e)}>삭제</a>
                    </td>
                </tr>
            )
        }
        return result
    }

    render () {
        return (
            <section class="sub_wrap" >
                <article class="s_cnt mp_pro_li ct1 mp_pro_li_admin">
                    <div class="li_top">
                        <h2 class="s_tit1">Software Tools 목록</h2>
                        <div class="li_top_sch af">
                        <Link to={'/SoftwareView/register'} className="sch_bt2 wi_au">Tool 등록</Link>
                        </div>
                    </div>

                    <div class="list_cont list_cont_admin">
                        <table class="table_ty1 ad_tlist">
                            <tr>
                                <th>툴 이름</th>
                                <th>기능</th>
                                <th>등록일</th>
                                <th>기능</th>
                            </tr>
                        </table>	
                        <table class="table_ty2 ad_tlist">
                            {this.state.append_SwtoolList}
                        </table>
                    </div>
                </article>
            </section>
        );
    }
}

export default SoftwareList;

 

2. 소프트웨어 툴 데이터를 등록&수정할, SoftwareView컴포넌트를 추가한다.

C:\react200\client\src\components\SoftwareListManage\SoftwareView.js

데이터를 처음 등록하는 페이지와, 기존에 등록한 데이터를 수정하는 페이지를 같은 컴포넌트로 구현한다. 같은 폼을 사용하지만, 수정페이지의 경우 기존에 입력한 데이터를 불러와 입력창에 값을 할당한다.

아래 코드는 html코드만 추가된 상태이다. 나머지 로직은 이후 강에서 학습한다.

import React, { Component } from 'react';
import {Link} from 'react-router-dom';

class SoftwareView extends Component {
    render () {
        return (
            <section class="sub_wrap">
                <article class="s_cnt mp_pro_li ct1">
                    <div class="li_top">
                        <h2 class="s_tit1">Software Tools 등록/수정</h2>
                    </div>
                    <div class="bo_w re1_wrap re1_wrap_writer">
                        <form name="frm" id="frm" action="" onsubmit="" method="post" >
                            <input id="is_Swtcode" type="hidden" name="is_Swtcode" />
                            <input id="is_Email" type="hidden" name="is_Email" value="guest" />
                            <input id="is_beforeSwtcode" type="hidden" name="is_beforeSwtcode" value="" />
                            <article class="res_w">
                                <p class="ment" style={{"text-align": "right"}}>
                                    <span class="red">(*)</span>표시는 필수입력사항 입니다.
                                </p>
                                <div class="tb_outline">
                                    <table class="table_ty1">
                                        <tr>
                                            <th>
                                                <label for="is_Swt_toolname">툴 이름<span class="red">(*)</span></label>
                                            </th>
                                            <td>
                                                <input type="text" name="is_Swt_toolname" id="is_Swt_toolname" class="" />
                                            </td>
                                        </tr>
                                        <tr>
                                            <th>
                                                <label for="is_Swt_demo_site">데모 URL<span class="red">(*)</span></label>
                                            </th>
                                            <td>
                                                <input type="text" name="is_Swt_demo_site" id="is_Swt_demo_site" class="" />
                                            </td>
                                        </tr>
                                        <tr>
                                            <th>
                                                <label for="is_Giturl">Github URL<span class="red">(*)</span></label>
                                            </th>
                                            <td>
                                                <input type="text" name="is_Giturl" id="is_Giturl" class="" />
                                            </td>
                                        </tr>
                                        <tr>
                                            <th>
                                                <label for="is_Comments">설명<span class="red">(*)</span></label>
                                            </th>
                                            <td>
                                                <textarea name="is_Comments" id="is_Comments" rows="" cols=""></textarea>
                                            </td>
                                        </tr>
                                        <tr class="div_tb_tr fileb">
                                            <th>
                                                메뉴얼 파일 #1
                                            </th>
                                            <td class="fileBox fileBox_w1">
                                                <label for="uploadBtn1" class="btn_file">파일선택</label>
                                                <input type="text" id="manualfile" class="fileName fileName1" 
                                                readonly="readonly" placeholder="선택된 파일 없음"/>
                                                <input type="file" id="uploadBtn1" class="uploadBtn uploadBtn1"
                                                onChange={e => this.handleFileInput('manual',e)}/>	
                                                <div id="upload_menual">
                                                </div>
                                            </td>
                                        </tr>
                                        <tr>
                                            <th>
                                                메인 이미지
                                            </th>
                                            <td className="fileBox fileBox1">
                                                <label htmlFor='imageSelect' className="btn_file">파일선택</label>
                                                <input type="text" id="imagefile" className="fileName fileName1"
                                                readOnly="readonly" placeholder="선택된 파일 없음"/>
                                                <input type="file" id="imageSelect" className="uploadBtn uploadBtn1"
                                                onChange={e => this.handleFileInput('file',e)}/>
                                                <div id="upload_img">
                                                </div>
                                            </td>
                                        </tr>
                                        <tr>
                                            <th>
                                                라벨 이미지
                                            </th>
                                            <td className="fileBox fileBox2">
                                                <label htmlFor='imageSelect2' className="btn_file">파일선택</label>
                                                <input type="text" id="imagefile2" className="fileName fileName1"
                                                readOnly="readonly" placeholder="선택된 파일 없음"/>
                                                <input type="file" id="imageSelect2" className="uploadBtn uploadBtn1"
                                                onChange={e => this.handleFileInput('file2',e)}/>
                                                <div id="upload_img2">
                                                </div>
                                            </td>
                                        </tr>
                                        <tr>
                                            <th>
                                                <label for="is_Swt_function">상세 기능<span class="red">(*)</span></label>
                                            </th>
                                            <td>
                                                <textarea name="is_Swt_function" id="is_Swt_function" rows="" cols=""></textarea>
                                            </td>
                                        </tr>
                                    </table>
                                    <div class="btn_confirm mt20" style={{"margin-bottom": "44px"}}>
                                        <Link to={'/SoftwareList'} className="bt_ty bt_ty1 cancel_ty1">취소</Link>
                                        <a href="javascript:" className="bt_ty bt_ty2 submit_ty1 saveclass" 
                                        onClick={(e) => this.submitClick('save', e)}>저장</a>
                                        <a href="javascript:" className="bt_ty bt_ty2 submit_ty1 modifyclass" 
                                        onClick={(e) => this.submitClick('modify', e)}>수정</a>
                                    </div>
                                </div>
                            </article>
                        </form>	
                    </div> 
                </article>
            </section>
        );
    }
}

export default SoftwareView;

 

3. App.js 파일에 1번, 2번에 추가한 컴포넌트를 라우팅하는 코드를 추가한다.

SoftwareList와 SoftwareView 컴포넌트를 import하고, Route태그에 추가해 라우팅한다.

import React, { Component } from 'react';
import { Router, Route, Switch } from "react-router";

import Api_test from './Api_test'
// css
import '../css/new.css';
// header
import HeaderAdmin from './Header/Header admin';
// footer
import Footer from './Footer/Footer';
// login
import LoginForm from './LoginForm';
// Vaccination_CenterList
import Vaccination_CenterList from './Vaccination_Center/Vaccination_CenterList';
// admin softwareinfo
import SoftwareList from './SoftwareToolsManage/SoftwareList';
import SoftwareView from './SoftwareToolsManage/SoftwareView';

class App extends Component {
  constructor (props) {
    super(props);
    this.state = {
    }
  }

  componentDidMount() {}

  render () {
    return (
      <div className="App">
          <HeaderAdmin/> 
          <Switch>
            {/* <Route exact path='/' component={Api_test} /> // root 경로일 경우 라우팅 */}
            <Route exact path='/' component={LoginForm} />
            <Route path='/Api_test' component={Api_test} />
            <Route path='/Vaccination_CenterList' component={Vaccination_CenterList} />
            <Route path='/SoftwareList' component={SoftwareList} />
            <Route path='/SoftwareView/:swtcode' component={SoftwareView} />
          </Switch>
          <Footer 
            footer_address={this.props.footer_address} 
            footer_tel={this.props.footer_tel}  
            footer_email={this.props.footer_email} 
            footer_mobile={this.props.footer_mobile} 
          />
      </div>
    );
  }
}

App.defaultProps = {
  // footer value
  footer_address: '[34234] 서울특별시 강남구 삼성동 111-114',
  footer_tel: '02-1234-5678',
  footer_email: 'ljung5@naver.com',
  footer_mobile: '010-3288-3398',
};


export default App;

 

4. node에서 사용할 패키지들을 npm으로 설치한다.

cmd 또는 터미널을 열고 node경로(C:\react200)에서 아래 명령어로 패키지들을 설치해 준다.

mysql : 데이터베이스 서버를 연동하기 위해 사용한다.

mybatis-mapper : mybatis를 사용하기 위해 설치한다.

body-parser : http 통신으로 request, response 데이터를 주고 받을 때, body에 있는 데이터를 파싱하기 위해 사용된다. 

npm install --save mysql
npm install --save mybatis-mapper
npm install --save body-parser

## 참고 ##

  mybatis는 자바 퍼시스턴트 프레임워크이다. persistent란 사용한 프로그램이 종료되어도 데이터는 남는다는 의미로, 쉽게 말해서 데이터베이스를 사용한다는 것이다. 

  자바에서 ORM(Object Relational Mapping)중 가장 많이 사용한다. 사용하는 가장 큰 이유는 비즈니스 소스와 sql 소스를 분리해서 개발할 수 있다는 점이다. 

  node도 마찬가지로 node 코드 사이에 sql을 깨워서 개발하는 방법보다는, 따로 분리시켜 개발하는 것이 편의성이 좋고, 유지보수에도 효율적이다.

 

5. node소스에 소프트웨어 툴 라우터를 추가한다.

node경로(C:\react200)의 server.js에, 소프트웨어 툴 api경로(/api/Swtool)를 라우팅하는 코드를 확인한다.

var express = require('express');

var indexRouter = require('./routes/index');
var usersRouter = require('./routes/users');
var swtoolRouter = require("./routes/SwtoolRout");

var app = express();

app.use('/', indexRouter);
app.use('/users', usersRouter);
app.use("/api/Swtool", swtoolRouter);

const port = process.env.PORT || 5000;
app.listen(port, () => console.log(`Listening on port ${port}`));

node경로(C:\react200\routes)에 소프트웨어 툴 라우터(SwtoolRout.js)를 추가한다.

react에서 파라미터로 넘겨준 type변수로 연결할 mybatis mapper정보를 분기한다. type값이 list인 경우, mapper 파일이 SwToolsMapper.xml이고 mapper id가 selectSwToolsList인 쿼리를 찾아서 실행한다는 코드이다. 이 정보들을 다음 라우터(dbconnect_Module.js)로 보내고, 해당하는 쿼리를 찾아 mysql을 연결해 실행한다.

var express = require('express');

var router = express.Router();
const bodyParser = require('body-parser');

router.use(bodyParser.urlencoded({ extended: true }));
router.use(bodyParser.json());

router.post('/', (req, res, next) => {
    var type = req.query.type;
    if(type == 'list'){
      //Swtool 리스트 조회
      try {
        // Mysql Api 모듈(CRUD)
        var dbconnect_Module = require('./dbconnect_Module');
    
        //Mysql 쿼리 호출 정보 입력
        req.body.mapper = 'SwToolsMapper';//mybatis xml 파일명
        req.body.crud = 'select';//select, insert, update, delete 중에 입력
        req.body.mapper_id = 'selectSwToolsList';
        
        router.use('/', dbconnect_Module);
        next('route')
      } catch (error) {
        console.log("Module > dbconnect error : "+ error);      
      }
    }
});

module.exports = router;

 

6. node소스에 DB연결 설정 라우터를 추가한다.

node경로(C:\react200\routes)에 dbconnect_Module.js 라우터를 추가한다. dbconnect_Module라우터는 공통 db라우터이다. 이전 라우터에서 전달받은 정보로, mybatis mapper을 스캔한다. 그리고 정보와 일치하는 쿼리를 가져온 다음, mysql서버를 연결해 쿼리를 수행한다. 

## 참고 ##

  아래 코드에서 createPool()함수는 node서버와 mysql 서버 사이에 connection pool을 생성한 것이다. cp가 없다면 사용자마다 실행하는 쿼리마다 1개씩 커넥션이 생기고, 데이터 호출 후에 연결이 끊긴다. cp는 생성된 연결을 끊지 않고 유지시켜, 연결에 소모되는 자원을 줄일 수 있게 해준다. 

  connectionLimit : 66은 사용하고 있는 rds 서버 max connection수(동시접속허용수)가 66개 이기 때문에, 최대값으로 세팅했다. 

  서버가 켜지자마자 66개의 연결이 생기는 건 아니고, 커넥션이 생길 때마다 1개씩 증가해 그때까지 생성된 연결을 유지한다.  

var express = require("express");
var router = express.Router();
const mysql = require("mysql");
const bodyParser = require("body-parser");

router.use(bodyParser.json());
router.use(bodyParser.urlencoded({ extended: true }));

// Connection Pool 세팅
const pool  = mysql.createPool({
  connectionLimit: 66,
  waitForConnections: true,
  host: "taling1220.#########.ap-northeast-2.rds.amazonaws.com",
  port: "3306",
  database: 'react',
  user: "admin",
  password: "#########",
});

router.post("/", (req, res) => {
  const mybatisMapper = require("mybatis-mapper");
  var param = req.body;

  //mybatis mapper경로 설정
  mybatisMapper.createMapper(['./models/'+param.mapper+'.xml']);
  var time = new Date();
  console.log('## '+time+ ' ##');
  console.log("\n Called Mapper Name  = "+param.mapper);

  var format = { language: 'sql', indent: '  ' };
  //mysql 쿼리 정보 세팅
  var query = mybatisMapper.getStatement(param.mapper, param.mapper_id, param, format);
  console.log("\n========= Node Mybatis Query Log Start =========");
  console.log("* mapper namespce : "+param.mapper+"."+param.mapper_id+" *\n");
  console.log(query+"\n");
  try {
    pool.getConnection(function(err,connection){
      connection.query(query, function (error, results) {
        if (error) {
          console.log("db error************* : "+error);
        }
        var time2 = new Date();
        console.log('## '+time2+ ' ##');
        console.log('## RESULT DATA LIST ## : \n', results);
        if(results != undefined){
          string = JSON.stringify(results);
          var json = JSON.parse(string);
          if (req.body.crud == "select") {
            res.send({ json });
          }else{
            res.send("succ");
          }
        }else{
          res.send("error");
        }
  
        connection.release();
        console.log("========= Node Mybatis Query Log End =========\n");
      });
    })
  } catch (error) {
    console.log("pool error : "+error);
  }
});

module.exports = router;

 

7. node소스에 mybatis mapper파일을 추가한다.

 

node경로 C:\react200\models\models에 SwToolsMapper.xml 파일을 추가한다.

mybatis의 장점은 비지니스 로직(node)소스와 쿼리(mysql)소스를 분리해서 개발할 수 있다는 것이다.

아래 쿼리에서는 is_Swtcode변수가 있다면 수정페이지, 없다면 리스트 페이지에서 쿼리를 사용한다. xml파일에 한 번 만 정의를 하고, 여러군데에서 사용할 수 있다. 만약 이 쿼리를 node소스 사이에 넣었다면, 거의 동일한 쿼리를 2번 작성해야한다.

<?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="SwToolsMapper">  
  <select id="selectSwToolsList">
      SELECT 
        swt_code
        , swt_toolname
        , swt_function
        , swt_imagepath
        , swt_big_imgpath
        , swt_comments
        , swt_demo_site
        , swt_manual_path
        , swt_github_url
        , reg_date
      FROM react.react_swtool
      <if test="is_Swtcode != null && is_Swtcode != ''">
        WHERE swt_code = #{is_Swtcode}
      </if>
      ORDER BY update_date DESC
  </select>
</mapper>

 

8. 화면에 db에서 가져온 데이터가 정상 노출되는 것을 확인한다.

yarn dev 명령어로 서버를 다시 시작하고, 헤더에서 software tools 관리를 선택하면 아래와 같이 데이터가 노출된다.

https://taling.me/Talent/Detail/19341

 

[비전공자도 가능한 웹 프로젝트] REACTJS NODEJS MYSQL AWS 웹개발 | 탈잉

✔ 수업 목표 & 5주 뒤 결과물 만들고 싶은 서비스를 직접 구현하게 됩니다. 사람들이 방문하고 액션을 할 수 있는 웹 페이지를 제작 & 배포하는 것까지 진행됩니다. 여러분이 개발 경험이 있든

taling.me:443

 

Comments