ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • [Node.js & MySQL] 데이터 처리방식: 파일 -> MySQL 이식
    생활코딩/WEBn 2020. 12. 9. 18:03

    [강의 출처] opentutorials.org/course/3347

     

    Node.js - MySQL - 생활코딩

    수업소개 이 수업은 Node.js와 MySQL을 이용해서 웹애플리케이션을 만드는 방법에 대한 수업입니다.  수업대상 예를들어 1억 개의 페이지로 이루어진 웹사이트에서 필요한 정보가 파일에 하나하나

    opentutorials.org


     

    데이터 리스트 얻기

    기존에는 파일 시스템을 이용했었기 때문에, 데이터 리스트를 얻으려면 아래처럼 파일 시스템을 읽어와야 했다.

    fs.readdir('./data', function(err, dataList){
      /* code */ };

    이제 데이터는 MySQL에 저장되므로 이 부분은 이렇게 대체해야 한다.

    /*
      var db = mysql.createConnection({
        host     : 'localhost',
        user     : 'nodejs',
        password : 'password',
        database : 'data'
      });
      db.connect();
      여기까지 MySQL 연동을 위한 객체 설정 */
    
    db.query('SELECT title FROM topic', function(err, result){
        callback()
    };

    데이터가 가져와지긴 하는데 아래처럼 RowDataPacket 형태로 저장된다.

    [ RowDataPacket { title: 'Amazon' },
      RowDataPacket { title: 'Apple' } ]

    객체와 똑같이 for문으로 가져오면 된다.

    db.query('SELECT title FROM topic', function(err, result){
      var dataList = [];
      for (var data of result){
        dataList.push(data.title);
      };
      console.log(dataList);
    }

     

     

    CRUD 연산

    Read

    데이터를 읽어오는 것이기 때문에 위의 SELECT 구문을 그대로 사용하면 된다.

    file

    fs.readdir('./data', function(err, dataList){
    /* code */
    });

    mysql

    db.query('SELECT * FROM topic', function(err, result){
    /* code */
    });

     

    페이지 출력하는 단의 Read 명령은 다음과 같이 처리했다. 읽어온 데이터는 template.get()으로 html 파일로 만든다.

    file

    var template = templates.get(title, `data/${title}`, dataList);
    response.writeHead(200);
    response.end(template);  // 그리고 리스폰스

    mysql

    db.query('SELECT * FROM topic WHERE id = ', [topicId],
     function(err, result2){   // 구분자가 파일명에서 id로 바뀌면서 title -> topicId
       var template = templates.get(topicId, result2[0], dataList);
                                       // 결과가 result2가 array라서 [0]처럼 인덱싱 필요
        response.writeHead(200);       // 리스폰스쪽은 mysql 쿼리문 전송 이후 살행되는
        response.end(template);        //   callback 함수 안쪽으로 들어갔다
    });

     

    Create

    유저가 데이터를 전송하면, 해당 데이터를 파일리스트에 추가한 다음, 해당 데이터 페이지로 유저를 리다이렉트했다.

    file

    request_fmt.getData(request, function(post){    // request_fmt이 데이터 받아오는 함수
        fs.writeFile(`data/${topicId}`, post, 'utf8', function(err){   // 그대로 write
            response_fmt.redirect(response, encodeURI(`/?id=${topicId}`));
        });
    });

    mysql

    request_fmt.getData(request, function(post){
      var keys = Object.keys(post); // INSERT INTO table (col1, col2) VALUES (val1, val2);
      var values = [];              // 형태로 만들기 위해 키/값을 따로 분리하고 아래에서 쉼표로 조인했다
      for (var idx of keys){
        values.push(post[idx]);
      };
      db.query(`INSERT INTO topic (${keys.join(", ")}) VALUES
       (${"'" + values.join("', '") + "'"})`, function(err, result3){
          response_fmt.redirect(response, encodeURI(`/?id=${result3.insertId}`));
      });
    });

    ?id=에 들어간 result.insertId는 쿼리에 INSERT INTO 구문을 넣어주면 result로 들어오는 OkPacket에서 확인할 수 있다.

    OkPacket {
      fieldCount: 0,
      affectedRows: 1,
      insertId: 4,         // 인덱스 4로 들어간 것
      serverStatus: 2,
      warningCount: 0,
      message: '',
      protocol41: true,
      changedRows: 0 }

     

    Update

    파일로 관리하면 파일명 중복의 문제가 있어서 file에서는 해당 부분을 따로 처리했었다.

    file

    request_fmt.getData(request, function(post){ 
      if (post.id !== post.title){ // 이름이 바뀐 경우
          if (dataList.includes(post.title) >= 0){ // 중복된 이름이면
              response_fmt.denied(response); // 갱신거절
              return 0;
          } else {
              fs.rename(`data/${topicId}`, `data/${topicId}`, function(err){
              post.id = post.title;
          });
      }
    });

    인덱스 id는 유저가 변경할 수 없기 때문에 위의 이슈는 없지만, 각각이 웹페이지 명이 되기 때문에 title의 중복은 없는 편이 좋겠다.

     

    mysql (중복 처리 없는 경우)

    위의 Create 연산에서 데이터 포맷 처리하는 부분과 INSERT INTO 구문 -> UPDATE 구문인 점만 다르다.

    request_fmt.getData(request, function(post){
      var keys = Object.keys(post); 
      var inputData = '';
      for (var idx of keys){              // SET (col1 = 'data1', col2 = 'data2' 
        if (post[idx] !== undefined){     // 형태로 데이터 정리
            inputData = inputData + idx + `='${post[idx]}', `;	
        };
      };  // 여기부터 DB처리 부분
      db.query(`UPDATE topic SET ${inputData.slice(0,-2)} WHERE id = ${post.id}`,
       function(err, result3){
          response_fmt.redirect(response, encodeURI(`/?id=${post.id}`));
      });
    });
    

     

    mysql (중복 처리하는 경우)

    타이틀이 존재하는지 여부를 확인해서, 중복된 타이틀이면 response_fmt.denied() 함수로 처리,

    'Already used title ...' 페이지로 리다이렉트한다.

    같은 로직을 Create 구문에 적용하면 생성시의 중복도 막을 수 있다. Create 시에는 WHERE문의 not id 부분은 필요없겠다.

    request_fmt.getData(request, function(post){
      var keys = Object.keys(post);
      var inputData = '';
      for (var idx of keys){
        if (post[idx] !== undefined){
          inputData = inputData + idx + `='${post[idx]}', `;	
        };
      };
           // 여기부터 DB처리 부분
      db.query(`SELECT EXISTS(SELECT * FROM topic
        WHERE title = '${post.title.toLowerCase()}'
         AND NOT id = ${post.id}) AS dup;`,    // 중복된 타이틀 있는지 확인
         function(err, result4){
           if (result4[0].dup === 0){          // 중복된 타이틀 발견 개수가 0이면
             db.query(`UPDATE topic SET ${inputData.slice(0,-2)} WHERE id = ${post.id}`,
              function(err, result3){
                response_fmt.redirect(response, encodeURI(`/?id=${post.id}`));
              });
           } else {
             response_fmt.denied(response);
           };
      });
    
    });

     

    Delete

    실제로는 데이터 보관, 인덱스 등의 여러 가지 이슈로 데이터 삭제보다는 원문을 비활성화처리하는 경우가 많다고 한다.

    아래에서는 백업용 테이블을 만들어서 데이터를 거기에 옮긴 후 삭제하는 방식으로 바꾸었다.

    삭제 후 Undo? 페이지로 보낸 다음, 버튼 입력에서 YES를 받으면 데이터를 되돌린다.

     

    file

    request_fmt.getData(request, function(post){ 
      fs.unlink(`data/${topicId}`, function(err){    // unlink함수로 data 디렉토리의 파일을 삭제
          response_fmt.redirect(response, `/`);      // 삭제 후 리다이렉트 응답을 보내는 함수
      });
    });

     

    mysql (실행취소 없는 경우)

    request_fmt.getData(request, function(post){
        db.query('DELETE FROM topic WHERE id = ', post.id, function(err, result2){
          response_fmt.redirect(response, `/`);
          return 0;
        });
    });

     

     

    mysql (실행취소 하는 경우)

    백업용 테이블 생성 및 데이터 복사는 이쪽에서 처리했다: hayjo.tistory.com/52

     

    [MySQL] 데이터 삭제 시 백업 테이블로 데이터 옮겨놓기

    mysql> INSERT INTO topic_del SELECT * FROM topic WHERE id = 2; [참조 페이지] extbrain.tistory.com/116 [MySQL] 테이블 구조와 데이터 복사 (Table Structure and Data Copy) ▶MySQL 테이블 구조와 데이터..

    hayjo.tistory.com

    undo page html, location값으로 ?id=아이디 값을 주었다.

    <!doctype html>
      <html>
        <h2>Really Delete?</h2>
        <form action="process_undo" method="post" onsubmit="">
            <input type="hidden" name="id" value="${location}">
            <input type="submit" value="Undo" class="edits">
          </form>
        <input type="button" value="Delete" onclick="location.href='/'">
      </html>

    삭제 작업페이지를 /process_delete, /process_undo 둘로 구분해서,

    if (pathname === "/process_delete") {
        request_fmt.getData(request, function(post){
            db.query('INSERT INTO topic_del SELECT * FROM topic WHERE id = ',[post.id], function(err, result34){
                db.query('DELETE FROM topic WHERE id = ',[post.id], function(err, result2){
    			    response_fmt.redirect(response, `/`);
    				return 0;
    			});   
    		});
    		response_fmt.undo(response, post.id);
    	});
    } else if (pathname === "/process_undo") {
        request_fmt.getData(request, function(post){
    	    db.query('INSERT INTO topic SELECT * FROM topic_del WHERE id =',[post.id], function(err, result2){
    		    response_fmt.redirect(response, `/`);
    			return 0;
    		});
    	});
    }

     

     

    생각해보니 이쪽이 편할 듯 싶다. 삭제+복구 말고 undo 했을 때만 삭제하는 버전. 위 html의 href 부분은 바꿔야되겠다:

    if (pathname === "/process_delete") {
        request_fmt.getData(request, function(post){
    	  response_fmt.undo(response, post.id);
    	});
    } else if (pathname === "/delete") {
    	request_fmt.getData(request, function(post){
    	  db.query('DELETE FROM topic WHERE id = ',[post.id], function(err, result2){
    		response_fmt.redirect(response, `/`);
    		return 0;
    	  });
        });
    }

     

    댓글

Designed by Tistory.