1. MYSQL workbench 다운로드

https://dev.mysql.com/downloads/workbench/

 

MySQL :: Download MySQL Workbench

Select Operating System: Select Operating System… Microsoft Windows Ubuntu Linux Red Hat Enterprise Linux / Oracle Linux Fedora macOS Source Code Select OS Version: All Windows (x86, 64-bit) Recommended Download: Other Downloads: Windows (x86, 64-bit), M

dev.mysql.com

 

2. docker desktop 설치

 

3. docker 올리기

  • docker-compose.yml 작성 (.env와 같은 폴더 안)
services:
  mysql:
    platform: linux/amd64
    image: mysql:8.0.29-debian
    ports:
      - "${MYSQL_PORT}:3306"
    command: --default-authentication-plugin=mysql_native_password
    volumes:
      - "${MYSQL_DATA_DIR}:/var/lib/mysql"
    environment:
      MYSQL_ROOT_PASSWORD: "${MYSQL_ROOT_PASSWORD}"

 

  • .env 작성 (같은 폴더 안)
MYSQL_PORT=3306
MYSQL_DATA_DIR=/d/dev/mysql
MYSQL_ROOT_PASSWORD=hello123!

 

  • 로컬에 여러개 포트의 mysql을 띄우고 싶을 때
    • .env 파일안의 MYSQL_PORT를 달리해주면 된다
    • docker run -p 5000:3306 → 로컬에서 5000 보고 mysql용 3306 연결
    • docker run -p 7000:3306 → 로컬에 7000 포트에 또 mysql 띄울 수 있음
  • MYSQL_DATA_DIR = /d/dev/mysql
    • 도커를 올리면 mysql 폴더가 생성된다
    • 로컬에 여러개의 mysql을 올리고 싶을 때는 포트를 바꾸기
    • access denied 관련 에러가 나면 mysql 폴더를 지우고 도커를 다시 올려본다

  • docker-compose.yml 있는 폴더로 이동
docker compose up

 

  • MYSQL workbench 연결
    • 127.0.0.1
    • DB 이름 정하기
    • 비밀번호(.env 파일 작성 password)

 

  • CREATE SCHEMA

 

Node-Sequelize-Mysql (4) Model 생성 & migrations 파일 생성

 

 

 

1. mysql, sequelize, sequelize-cli 모듈 설치

$ npm i --save sequelize mysql2  // 모듈 설치
$ npm i -g sequelize-cli  // 콘솔에서 sequelize 명령 가능하게 하는 모듈
$ sequelize init  // 필요한 파일과 디렉토리를 자동 설치
  • 초기 세팅 폴더 및 파일 생성됨
    • 폴더 및 파일들의 간략한 설명만 하고 실제 세팅은 뒤에서 진행

  • config.json
    • sequelize 모듈을 DB 환경과 연결 시키는 용도
    • 편의상 js로 변경하여 사용한다
    export default {
      development: {
        username: "root",
        password: "hello1234!",
        database: "DEV-HELLO-WEB",
        host: "127.0.0.1",
        dialect: "mysql",
        port: 3307,
      },
      test: {
        username: "root",
        password: "hello1234!",
        database: "TEST-HELLO-WEB",
        host: "127.0.0.1",
        dialect: "mysql",
        port: 3307,
      },
      production: {
        username: "root",
        password: null,
        database: "HELLO-WEB",
        host: "127.0.0.1",
        dialect: "mysql",
        port: 3307,
      },
    };
  • models/~
    • ORM 객체로 사용할 Model을 정의하는 곳
    • RDB에서 member, post, qna 테이블들이 있다고 가정하면, 각각 테이블 별로 Member, Post, Qna 모델을 만들어 주고, 컬럼 속성들이나 제약조건들을 정의해 주면 된다
  • index.js
    • Sequelize 인스턴스를 생성
    • config에서 정의한 환경변수에 따라서 mysql DB와 연동된 sequelize 객체를 export
    • default로 만들어주는 내용을 아래와 같이 필요한 내용만 두고 수정한다
    "use strict";
    
    import { Sequelize } from "sequelize";
    import config from "./config/config.js";
    
    export default new Sequelize(config[process.env.NODE_ENV]);
    
  • migrations
    • RDB DDL 정의하는 파일들
    • 실제로 ORM 객체로 작성한 테이블 정의 파일을 Mysql DB에 테이블로 반영시킬 수 있다.
    • 이 migrations 파일들만 있으면, 어떤 RDBMS 에도 동일한 테이블 세팅을 구성할 수 있다

Node-Sequelize-Mysql (3) Mysql 로컬 db docker 띄우기

 

 

 

orm 사용해 봅시다

 

ORM 개념

  • ORM 이란
    • Object Relational Mapping : 객체-관계 매핑
    • 객체와 관계형 데이터베이스(RDB) 데이터를 자동으로 매핑해주는 툴
    • mysql로 tb_member이라는 테이블을 만들어서 데이터 관리를 한다
    • orm으로 Member 객체를 만들어서 sql 쿼리 없이 테이블을 객체처럼 관리한다
    • 객체지향 프로그래밍은 클래스(Class) 개념을 사용하는데, 중간중간 RDB의 테이블 개념이 이질적인 현상을 해소할 수 있다. → 전체적으로 객체지향(Class)로 프로그래밍이 가능
  • 장점
    • 객체 지향적인 코드로 전체적으로 직관적이고, 비즈니스 로직에 집중할 수 있게 해준다
    • 재사용 및 유지보수의 편리성이 증가한다
    • DBMS에 대한 종속성이 줄어든다 (DB 솔루션과 상관없다)
  • 단점
    • 프로그램의 복잡서이 커질 수록 ORM 난이도가 오히려 더 복잡해진다
    • 잘못 구현된 경우 속도 저하, 성능 저하, 일관성 무너짐 발생 가능

 

Node-Sequelize-Mysql (2) Sequelize 설치

 

 

 

HMAC : Hash-based Message Authentication Code

해쉬 기반 메시지 신원확인 코드

 

클라이언트 👉 API 서버에 보내는 "요청자의 신원과 메시지의 무결성을 검증하기 위한 해쉬 문자열"

 

HMAC 만드는 방법

  • HMAC는 인증을 위한 Secret Key와 임의의 길이를 가진 Message를 해시함수를 이용해서 생성
  • 해시함수로 MD5, SHA-256등 일반적인 함수 사용가능
  • 각 알고리즘에 따라 다른 고정 길이의 MAC(=해시문자열 = 코드)이 생성된다

 

만약 중간에 해커가 코드를 가로채서 동일한 요청을 계속 보낸다면 (Reply attack), 이를 방지하기 위해서 MAC을 생성할 때 timestamp를 추가해서 사용하는 방법이 있다

API 서버는 해당 메시지가 생성된 시간을 알 수 있고, 생성된 시간부터 일정 시간 이내의 호출만 정상적인 호출로 인식 가능

serial, nonce 등 다른 값들도 룰에 따라 추가 가능

 

HMAC 생성과 API 통신에서 사용방법

1. (Client) 해시 생성

  • 클라이언트는 SHA-256(SecretKey, Message) = MAC 생성

2. 데이터 전송

  • 생성된 MAC + Message 를 API 서버에 전송, MAC은 HTTP Header 또는 쿼리로 (URL)에  포함된다

3. (Server) 해시 생성

  • API 서버는 클라이언트로부터 전달받은 Message 와 가지고 있던 SecretKey를 이용해 SHA-256(SecretKey, Message) = MAC 생성

4. 해시 비교 

  • API 서버에서 생성된 MAC과 클라이언트로부터 전달받은 MAC의 값이 같은지 비교 👉 무결성판단

 

구현 복잡도

Client_id & Client_secret < HMAC < PKI

 

 

예시

  • AWS S3 REST API 기준 HMAC 서명 생성과검증
  • 서명을 생성하는데 필요한 것은 API 서버가 정의한 룰 + 요청 자체에 담긴 여러 데이터(Message) + SecretKey 이다
  • 다양한 룰이 있는데, DomainTools의 경우 Client_id + timestamp + request_uri 조합한 문자열 + client_secret을 이용한 HMAC 생성
  • 서버도 동일한 방법으로 HMAC을 생성하여 클라이언트로 부터 받은 코드가 유효한지 검사

 

 

  • AWS S3 REST API 기준 인증방법
  • S3 REST API는 서버와 클라이언트 만이 공유하는 SecretKey를 사용하여 생성된 서명(HMAC)으로 매번 클라이언트로부터의 요청이 올바른지 매번 인증한다
  • 서버 - 클라이언트 사이의 요청 - 응답 메시지에 대한 보안은 HTTPS 프로토콜을 사용하는 것으로 자연스럽게 수행된다
  • 서명(Signature)은 단지 클라이언트가 진짜인지, 가짜인지 증명하는 역할만 수행
  • HMAC 생성을 위한 해쉬함수로는 SHA256이 사용된다
  • 생성된 Signature가 포함된 요청의 유효시간은 15분이다. 15분이 경과하면 유효하지 않은 요청으로 간주한다. timestamp의 포맷은 ISO8601에 타임존을 추가하여 정의한다

 

 

 

  • [코드예시-JAVA]
String sharedSecret = "5pKRnC5MGNuqEdKkzYy4MA";
String algorithm = "HmacSHA256";
String message = "2017-11-07T18:00:00+09:00 GET http://jsonobject.com/posts/2017";

// HMAC 생성기 초기화, 공유키, 알고리즘, 원본 메시지 지정
Mac mac = Mac.getInstance(algorithm);
mac.init(new SecretKeySpec(sharedSecret.getBytes(), algorithm));

// Signature 바이트 배열 생성
byte[] signatureBytes = mac.doFinal(message.getBytes()));

// 생성된 Signature를 Base64 문자열로 변환, UMuelgDclhzNZPiNqF6NYkZtJnOFqlgu4i4t+4M1fJs=
String signature = Base64.getEncoder().encodeToString(signatureBytes);

 

  • [코드예시 - Binance API]

Request Body에 담아서 전송할 경우

 

앞에 여러가지 key-value 쌍들은 특정 API 메소드에 필요한 params 값들 + SecretKey -> SHA512로 암호화 -> 나온 결과값이 HMAC => 서명

    $ echo -n "symbol=LTCBTC&side=BUY&type=LIMIT&timeInForce=GTC&quantity=1&price=0.1&recvWindow=5000&timestamp=1499827319559" | openssl dgst -sha256 -hmac "NhqPtmdSJYdKjVHjA7PZj4Mge3R5YNiP1e3UZjInClVN65XAbvqqM6A7H5fATj0j"
    (stdin)= c8db56825ae71d6d79447849e617115f4a920fa2acdcab2b053c4b2838bd6b71

API 요청 메시지 (필요한 params = message + HMAC 서명)

    (HMAC SHA256)
    $ curl -H "X-MBX-APIKEY: vmPUZE6mv9SD5VNHk4HlWFsOr6aKE2zvsw0MuIgwCIPy6utIco14y7Ju91duEh8A" -X POST 'https://api.binance.com/api/v3/order' -d 'symbol=LTCBTC&side=BUY&type=LIMIT&timeInForce=GTC&quantity=1&price=0.1&recvWindow=5000&timestamp=1499827319559&signature=c8db56825ae71d6d79447849e617115f4a920fa2acdcab2b053c4b2838bd6b71'

 

 

'programming > Web' 카테고리의 다른 글

[Express] Post로 JSON 데이터 보내기 (미들웨어 설정)  (0) 2022.08.20
Restfull 웹 API 디자인(1)  (0) 2022.08.20
AWS - EC2(EBS/AZ/ELB)  (0) 2022.04.06
[AWS] IAM 이란? (개념과 실습)  (0) 2022.04.05
API, SDK, Library, Framework  (0) 2022.03.10

비동기 프로그래밍에 대해서 정확하게 알지 못하고 늘 주변만 뱅뱅 돌면서 의미도 모르는 채로 기계처럼 사용해왔다.

 

회사에서 코드리뷰도 하고, 다른사람이 하던 작업을 넘겨 받아 리팩토링도 하면서 내가 그동안 얼마나 빈약한 개념을 가지고 있었는지 새삼 깨닫게 되었다.

 

당장 움직이지 않는 손가락이 너무 답답해서 이번에야 말로 비동기 프로그래밍을 이해해보자는 생각으로 정리를 해보았다.

 

Sync & Async

  • Sync
    • JS 는 Sync → 동기적으로 작동
    • hoisting이 된 이후부터 코드가 작성된 순서대로 실행된다
  • async
    • 비동기, 언제 코드가 끝날 지 모른다
    • 끝날 때까지 기다리지 않음
    • ex) setTimeout - callback 함수

 

Callback

  • Sync callback
console.log('1')

function printNow(print) {
	print()
)
printNow(() => console.log('hello')	

console.log('2')
  •  결과
1
hello
2
  • Async callback
console.log('1')
function printLater(print, delay) {
	setTimeout(print(), delay)
}
printLater(() => console.log('hello', 2)
console.log('2')
  •  결과
1
2
hello

⇒ 어쨌든 JS는 모든 코드에 똑같이 작동한다

  • 한 줄 한 줄 동기적으로
  • 실행할 함수 안에서 동기적으로 작동하게 하느냐, 비동기적으로 작동하게 하느냐를 구현하는 것
  • setTimeout 함수 자체가 시간이 걸리는 작업이라고 생각 → 사실상 JS는 동기적으로 실행을 순서에 맞게 시작했지만, 늦게 끝나서 도착한 결과만 놓고 봤을 때 코드 순서와 다를 뿐 (비동기적)

 

setTimeout 함수가 가지고 있는 콜백함수처럼 함수를 정의할 때 콜백함수를 정의 할 수 있다.

A함수 처리 하고 나서 B 함수 처리해 그리고 C 함수 처리해 그리고 D....

 

예제

1. 로그인 검사

2. user 정보를 가지고 권한 return

 

이런 일련의 과정들을 콜백지옥 체험 코드로 구현 해보자

class UserStorage {
  loginUser(id, password, onSuccess, onError) {
    setTimeout(() => {
      if (id == "apple" && password === "banana") {
        onSuccess(id);
      } else {
        onError(new Error("wrong user info"));
      }
    }, 2000);
  }

  getRoles(user, onSuccess, onError) {
    setTimeout(() => {
      if (user === "apple") {
        onSuccess({ id: "apple", role: "admin" });
      } else {
        onError(new Error("no role"));
      }
    }, 1000);
  }
}

const userStorage = new UserStorage();
const id = "apple";
const password = "banana";

userStorage.loginUser(
  id,
  password,
  (res) => {
    console.log("Welcom", res);
    userStorage.getRoles(res, (res) => {
      console.log(`hello ${res.id}, user Role is ${res.role}`);
    }),
      (error) => console.log("error", error);
  },
  (error) => console.log("error", error)
);
정의해둔 class와 메소드를 사용하여 로직을 구현하는 부분을 보면 가독성이 떨어진다.
콜백함수 안에 또 콜백함수가 들어가 있는 방식으로, 에러처리도 힘들고, 무엇을 진행하려고 하는 지 모르겠다

 

Promise

  • 프로미스는 JS 에서 제공하는 Class (Object)이다
  • 프로미스 Object 안에는 state라는 속성이 있다
    • pending : 진행중
    • fulfilled : 완료
    • rejected : 실패
  • Producer & Consumer
    • 필요한 정보를 만들어주는 Producer → Promise 객체
    • 그 정보를 소비하는 Consumer
  • Promise 생성자
    • executer 콜백 함수를 전달해 주어야 한다
      • executer 함수는 resolve와 reject 2가지의 콜백함수를 가지고 있다

  • Promise를 만드는 순간 내부의 콜백함수 (executer) 가 바로 실행된다
  • 사용자가 액션을 한 순간에 작업이 진행되어야 하면, Promise를 생성하는 방식으로 사용 불가 → 생성과 동시에 바로 실행되기 때문에 (유저의 액션을 체크 불가 & 불필요한 네트워크 통신)
const promise = new Promise((resolve, reject) => {
  console.log("Network connection");
});
  • Producer(resolve/reject) & Consumer(then/catch/finally)
  • Promise 에서 성공시 객체를 resolve에 담고, 실패시 에러를 reject에 담는다
  • 생성한 promise 객체에서는 then을 통해 성공에 대한 결과를 받아내고, catch를 통해 에러를 받아낸다
// Producer
const promise = new Promise((resolve, reject) => {
  console.log("Network connection");
  setTimeout(() => {
    result = "apple";
    if (result === "apple") resolve(result);
    else reject(new Error("no network"));
  }, 2000);
});

//Consumer
promise
  .then((res) => {
    console.log(res);
  })
  .catch((error) => {
    console.log(error);
  })
  .finally(() => {
    console.log("finally");
  });
  • then/catch/finally 메소드는 각각 모두 Promise를 반환 → Chaining 적용하여 처리
// Promise Chaining
const fetchNumber = new Promise((resolve, reject) => {
  setTimeout(() => resolve(1), 1000);
});

fetchNumber
  .then((num) => num * 2)
  .then((num) => num * 3)
  .then((num) => {
    return new Promise((resolve, reject) => {
      setTimeout(() => resolve(num - 1), 1000);
    });
  })
  .then((num) => console.log(num));
  • then을 통해서 값을 return 하거나, 새로운 Promise를 return 하거나
  • Promise Object 에 매개변수를 넘겨줄 수 있다
const getHen = () =>
  new Promise((resolve, reject) => {
    setTimeout(() => resolve("hen"), 1000);
  });

const getEgg = (hen) =>
  new Promise((resolve, reject) => {
    setTimeout(() => reject(new Error("no Egg!")), 1000);
  });

const cook = (egg) =>
  new Promise((resolve, reject) => {
    setTimeout(() => resolve(`${egg} -> fry`), 1000);
  });

getHen()
  .then((res) => getEgg(res))
  .catch((error) => {
    console.log(error);
    return "bread";
  })
  .then((res) => cook(res))
  .then((res) => console.log(res));

// 한가지만 받아서 그대로 넘겨줄 때 축약표현 가능
// getHen()
// .then(getEgg)
// .then(cook)
// .then(console.log);
  • 결과
// CASE1
hen -> egg -> fry

// CASE2
Error: no Egg!
    at Timeout._onTimeout (D:\dev\yakbang_front\promise.js:45:29)
    at listOnTimeout (node:internal/timers:559:17)
    at processTimers (node:internal/timers:502:7)
bread -> fry

 

예제

위에서 작성했던 콜백지옥을 Promise를 사용하여 레벨업을 시켜보자

class UserStorage {
  loginUser(id, password) {
    return new Promise((resolve, reject) => {
      setTimeout(() => {
        if (id === "apple" && password === "banana") {
          resolve(id);
        } else {
          reject(new Error("wrong user info"));
        }
      });
    });
  }
  getRoles(user) {
    return new Promise((resolve, reject) => {
      setTimeout(() => {
        if (user === "apple") {
          resolve({ id: "apple", role: "admin" });
        } else {
          reject(new Error("no role"));
        }
      });
    });
  }
}

const userStorage = new UserStorage();
const id = "apple";
const password = "banana";

userStorage
  .loginUser(id, password)
  .then((id) => userStorage.getRoles(id))
  .then((user) => console.log(`Welcom ${user.id} Role : ${user.role}`))
  .catch((error) => console.log(error));
이제 구현부의 내용을 보면, 순차적으로 실행되는 로직이 눈에 들어온다

async-await

  • Promise 를 간결하게 표현, 동기적으로 실행되는 것 처럼 표현
  • Promise도 좋은데, 좀 더 좋은 방법 없을까? -> 갑자기 new Promise 등장하는게 읽기 불편해
  • syntactic sugar : 코드상 슈가 → 기존에 있는 기술을 좀 더 편하게 사용할 수 있도록 하는 것
  • 모든 Promise를 대체하는 건 아니다
  • 동기적인 실행
    • apple을 받아오는데 2초가 걸리는데, 다 기다렸다가 Next Content 실행
function sleep(ms) {
  const wakeUpTime = Date.now() + ms;
  while (Date.now() < wakeUpTime) {}
}

function fetchUser() {
  sleep(2000);
  return "apple";
}

const user = fetchUser();
console.log("user", user);

console.log("Next Content");

-> 2초 뒤 콘솔로그

  • Promise 로 비동기적 실행 구현 (뒤에 관련 없는 Next Content를 먼저 표현한다)
function sleep(ms) {
  const wakeUpTime = Date.now() + ms;
  while (Date.now() < wakeUpTime) {}
}

function fetchUser() {
  return new Promise((resolve, reject) => {
    sleep(2000);
    resolve("apple");
  });
}

const user = fetchUser();
user.then(console.log);

console.log("Next Content");

 

  • 함수 앞에 “async”를 붙여주면 자동으로 Promise로 return 해준다
    • Producer (Promise를 리턴하는 부분) 에서 Promise를 간결하게 표현하는 방법
function fetchUser() {
  return new Promise((resolve, reject) => {
    sleep(2000);
    resolve("apple");
  });
}

// 위와 아래는 똑같은 결과를 준다
async function fetchUser() {
  sleep(2000);
  return "apple";
}
  • “await”은 “async”가 붙은 함수 안에서만 사용 가능
    • “async” (Promise) 안에서 동기적으로(다 할때까지 기다려) 코드가 실행되도록 하는 장치
  • await 없이 delay 함수 실행하면? 
function delay(ms) {
  return new Promise((resolve) => setTimeout(resolve, ms));
}
async function fetchUser() {
  delay(2000);
  return "apple";
}

const user = fetchUser();
user.then(console.log);

console.log("Next Content");

  • 바로 실행된다
  • why? fetchUser() 함수 안에서 delay를 기다리지 않고, 바로 apple을 return 해줌

 

  • awiat delay 주면 ? 
function delay(ms) {
  return new Promise((resolve) => setTimeout(resolve, ms));
}
async function fetchUser() {
  await delay(2000);
  return "apple";
}

const user = fetchUser();
user.then(console.log);

console.log("Next Content")

-> 2초 뒤에 

  • 2초 동안 기다렸다가 apple을 리턴해줌
  • Next content가 먼저 표현되고, apple을 2초 뒤에 표현

 

 

  • async-await을 통해서 코드를 동기적으로 표현 
  • 가독성이 좋아지고 에러처리도 간편하다 try-catch
function delay(ms) {
  return new Promise((resolve) => setTimeout(resolve, ms));
}
async function fetchUser() {
  await delay(2000);
  console.log("2초 뒤");
  return "apple";
}

async function fetchRole() {
  await delay(1000);
  console.log("1초 뒤");
  return "admin";
}

function delay(ms) {
  return new Promise((resolve) => setTimeout(resolve, ms));
}
async function fetchUser() {
  await delay(2000);
  console.log("2초 뒤");
  return "apple";
}

async function fetchRole() {
  await delay(1000);
  console.log("1초 뒤");
  return "admin";
}

async function fetchUserAndRole() {
  try {
    const user = await fetchUser();
    const role = await fetchRole();
    return `${user} & ${role}`;
  } catch (error) {
    console.log(error);
  }
}

fetchUserAndRole().then(console.log);
console.log("Next Content");
  • 단점) 만약 user & role이 서로 연관 없는 데이터를 가져오는 행동이라면 불필요한 시간 소모가 된다
  • -> 2초 user 가져오고 난 후에 1초 role을 가져와서 총 3초가 걸린다
  • -> user 가져옴과 동시에 role을 병렬적으로 가져오면 총 2초로 단축 가능

 

 

  • 해결책
    • Promise는 생성과 동시에 실행되는 성격을 이용하자
async function fetchUserAndRole() {
  const userPromise = fetchUser(); // promise 만들자 마자 실행
  const rolePromise = fetchRole(); // promise 만들자 마자 실행
  const user = await userPromise;
  const role = await rolePromise;
  return `${user} & ${role}`;
}
  • Better Expression
    • 모든 promise를 한 줄 씩 각각 쓰지말고 -> Promise.all로 전체 실행 가능\
return Promise.all([fetchUser(), fetchRole()]).then((res) => res.join(" & "));
  • Another api
    • 실행하는 Promise 중에서 먼저 완료되는 것만 출력 -> Promise.race
async function fetchOne() {
  return Promise.race([fetchUser(), fetchRole()]);
}
fetchOne().then(console.log);
console.log("Next Content");
  • 단, 아예 실행이 안되는것이아니라, 내부적으로 콘솔로그나 로직은 모두 실행이 되는데, 최종 return 값만 가장 빨리 되는 놈으로 가져오는 것임!

 

 

예제

위에서 작성했던 콜백지옥 -> Promise를 최종적으로 Async-await을 이용해 작성해보자

class UserStorage {
  delay(ms) {
    return new Promise((resolve) => setTimeout(resolve, ms));
  }

  async loginUser(id, password) {
    await this.delay(2000);
    if (id === "apple" && password === "banana") {
      return id;
    } else {
      throw "wrong user Info";
    }
  }
  async getRoles(user) {
    await this.delay(1000);
    if (user === "apple") {
      return { id: "apple", role: "admin" };
    } else {
      throw "no role";
    }
  }
}

const userStorage = new UserStorage();
const id = "apple";
const password = "banana";

async function fetchUserAndRole(id, password) {
  const user = await userStorage.loginUser(id, password);
  const userWithRole = await userStorage.getRoles(user);
  return `Welcome ${userWithRole.id}, Role : ${userWithRole.role}`;
}

fetchUserAndRole(id, password).then(console.log);
가장 가독성이 좋다
따로 try-catch로 에러처리는 하지 않았지만, Class 내부 method에서 예외발생시 throw로 던져주고, 최종 구현부(fetchUserAndRole)에서 try - catch로 감싸면 메소드들에서 던진 예외상황들이 catch로 다 모여서 걸러진다

기존에 수영장 정보 테이블을 만들었었다

링크참조: https://tacit.tistory.com/147

 

[DB] CREATE 로 테이블 만들고 INSERT 해보기

create database playground; use playground; CREATE TABLE swimming_pool ( idx int NOT NULL AUTO_INCREMENT COMMENT '수영장 고유번호', title varchar(20) NOT NULL COMMENT '수영장 이름', type tinyint..

tacit.tistory.com

 

 

이제 수영장 강사, 강습, 직원 등 추가적인 정보들을 위한 테이블을 만들고, 서로 관계성을 연결시켜보자

 

먼저 강사정보 테이블

CREATE TABLE trainer (
  `idx` int NOT NULL AUTO_INCREMENT COMMENT '강사 고유번호',
  `name` varchar(5) NOT NULL COMMENT '강사 이름',
  `gender` char(1) NOT NULL comment '성별 남:M, 여:W',
  `birth_date` date NOT NULL comment '생년월일',
  created_at timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '등록일시',
  updated_at timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '업데이트일시', 
  PRIMARY KEY (idx)
) ENGINE=InnoDB AUTO_INCREMENT=0 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci

 

강습정보 테이블

CREATE TABLE lesson_schedule (
	idx int NOT NULL AUTO_INCREMENT COMMENT '강습 고유번호',
    lesson_code int(5) NOT NULL COMMENT '강습 코드',
    title varchar(20) NOT NULL COMMENT '강습 이름',
    trainer_idx int NOT NULL COMMENT '담당 강사 idx',
    open_time time DEFAULT NULL COMMENT '시작시간',
    close_time time DEFAULT NULL COMMENT '종료시간',
    student_count int DEFAULT NULL COMMENT '등록회원 수',
    created_at timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '등록일시',
	updated_at timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '업데이트일시', 
	PRIMARY KEY (idx),
    UNIQUE KEY (`lesson_code`),
    KEY idx_lesson1 (title),
    KEY idx_lesson2 (lesson_code),
    KEY idx_lesson3 (open_time, close_time),
    FOREIGN KEY (`trainer_idx`) REFERENCES `trainer` (`idx`)
    ) ENGINE=InnoDB AUTO_INCREMENT=0 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci

 

1. Index 란

색인, 빨리 찾기 위한 자료구조, 추가적인 작업공간과 저장공간을 할당한다.

데이터와 데이터의 위치를 저장

 

2. 장점

select

update

delete

성능이 향상된다

인덱스를 사용하지 않는 컬럼을 조건절에 검색시, 해당 테이블을 full scan 한다

시스템 부하를 줄일 수 있다

 

3. 단점

인덱스를 관리하기 위한 추가적인 공간 (약 10%) 이 필요하다

추가 작업이 필요하다

인덱스를 잘못 사용하는 경우 오히려 성능이 저하된다

-> 테이블 rows가 적은 경우는 인덱스를 타고 찾는게 오히려 느려짐

-> update, delete의 경우 데이터를 삭제해도 기존의 인덱스는 삭제하지 않고, '사용하지 않음' 처리를 해주기 때문에, 만약 update, delete가 빈번하게 발생하는 테이블의 경우, 실제데이터는 10만건이지만 인덱스는 100만건이 넘어가서 성능이 저하될 수 있음

 

 

4. 예시

title 컬럼에 인덱스를 걸어둔 상황

 

  • 전체 문자열을 =로 그대로 검색하는 경우 -> 인덱스 탐

  • LIKE 검색하는 경우 -> 인덱스 못탐

 

  • title 컬럼자체를 연산하여 검색하는 경우 -> 인덱스 못탐

5. 인덱스 사용 권장하는 경우

  • 규모가 큰 테이블
  • insert, update, delete 가 자주 발생하지 않는 컬럼
  • join이나 where 또는 order by에 자주 사용되는 컬럼
  • 데이터의 중복도가 낮은 컬럼

 

6. 인덱스 자료구조

1) 해시테이블

(key, value)로 저장

빠른 데이터 검색에 유용

등호(=) 연산에만 특화되어 있다

부등호 연산(<, >) 불가능

2) B+Tree

DB의 인덱스를 위해 자식노드가 2개 이상인 B-Tree를 개선시킨 자료구조

리프 노드(데이터노드)만 인덱스와 함께 데이터를 가지고 있고, 나머지 노드 (인덱스 노드)들은 데이터를 위한 인덱스(Key)만 갖는다

리프노드들은 linkedList로 연결되어 있다

데이터 노드 크기는 인덱스 노드의 크기와 같지 않아도 된다

좀더 정리 필요 

https://12bme.tistory.com/138

 

[Real MySQL] B-Tree 인덱스

인덱스는 데이터베이스 쿼리의 성능을 언급하면서 빼놓을 수 없는 부분입니다. MySQL에서 사용 가능한 인덱스의 종류 및 특성에서 각 특성의 차이는 상당히 중요하며, 물리 수준의 모델링을 할

12bme.tistory.com

 

1. LIKE 연산자

컬럼에 저장된 문자열 중에서 비슷한거 찾아줘

 

2. 부분 문자열 검색에 사용되는 패턴

  • % : *(아스타) 역할하는 와일드카드, 아무나 길이 제한 없이 다
  • _ : 한자리

모두 같은 결과를 뱉는다.

 

  • ESCAPE : 검색하고 싶은 문자 자체가 '%' 이거나 '_' 일때 특수문자를 스트링 취급하라고 알려주기

 

  • substr 연산을 사용하여 맨 첫글자가 뭐인것, 이런식으로도 검색가능
  • 단, 해당 컬럼에 인덱스를 걸어둔 경우 그 컬럼에 대해서 연산을 한 결과로 검색을하면, 인덱스를 타지 못한다(성능저하)

 

집계함수는 여러행으로부터 하나의 결과값을 반환하는 함수이다.

주로 통계적인 데이터 (총합, 평균, 최대, 최소 등)을 구할 때 사용한다

 

1. COUNT

select count(*) from tb_member where gender = 'female';

남자들 중에서 member_name 중복을 제거한(동명이인 제거) 카운트 수

select count(distinct member_name) from tb_member where gender = 'male';

 

2. MIN/MAX

select MAX(member_age) from tb_member;

string 값이 저장되는 컬럼에도 사용 가능 -> 사용시 알파벳 사전순서

select MIN(country) from tb_member;

 

3. AVG(평균), SUM(총합), STDEV(표준편차), VAR(분산), CHECKSUM_AGG

* 표준편차 : 평균에 대한 오차, 실제 데이터 값이 평균과 얼마나 떨어져 있는가

* 분산 : 표준편차 제곱

 

 

4. GROUP BY

카테고리컬한 데이터에서 각각의 카테고리별로 연산을 하고 싶다면?

직급별 월급평균 등

select job, AVG(salary) from tb_member GROUP BY job;

 

5. HAVING

GROUP BY 절을 통해 만들어진 각각의 그룹들에 대해서 조건을 거는 것

WHERE 는 모든 row에 대해서 조건을 거는 것이고,

HAVING은 만들어진 그룹들에만 조건을 거는 것

 

1) 직급별 월급 평균에서 그 평균값이 4000을 넘는 경우

select job, AVG(salary) from tb_member GROUP BY job HAVING AVG(salary) > 4000;

2) 월급이 300만원 이상인 사람들에 대해서 직급별로 2명 이상인 직급과 그 직급의 월급 평균을 구해라

select job, AVG(job)
from tb_member
where salary >= 300
GROUP BY job
HAVING COUNT(job) >= 2

create database playground;

use playground;

 

CREATE TABLE swimming_pool (
	idx int NOT NULL AUTO_INCREMENT COMMENT '수영장 고유번호',
    title varchar(20) NOT NULL COMMENT '수영장 이름',
    type tinyint unsigned DEFAULT 0 COMMENT '수영장 타입 0:국공립, 1:사설',
    sido varchar(30) DEFAULT NULL COMMENT '주소 시/도',
    gugun varchar(30) DEFAULT NULL COMMENT '주소 구/군',
    road_name varchar(30) DEFAULT NULL COMMENT '주소 도로명',
    deatil_juso varchar(30) DEFAULT NULL COMMENT '주소 디테일',
    phone varchar(20) DEFAULT NULL COMMENT '전화번호',
    url varchar(100) DEFAULT NULL COMMENT '홈페이지주소',
    open_time time DEFAULT NULL COMMENT '오픈시간',
    close_time time DEFAULT NULL COMMENT '마감시간',
    close_yn char(1) DEFAULT 'N' COMMENT '폐장 여부 N:아니오(운영중), Y:예(미운영)', -- N/Y 0/1
    lane_length smallint DEFAULT NULL COMMENT '레인 길이',
    lane_count tinyint DEFAULT NULL COMMENT '레인 개수',
    max_depth float DEFAULT NULL COMMENT '최대 수심',
    min_depth float DEFAULT NULL COMMENT '최저 수심',
    kids_pool_yn tinyint DEFAULT NULL COMMENT '키즈풀유무 0:무, 1:유',
    created_at timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '등록일시',
	updated_at timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '업데이트일시', 
	PRIMARY KEY (idx),
    KEY idx_swimming1 (title),
    KEY idx_swimming2 (sido, gugun, road_name)
    ) ENGINE=InnoDB AUTO_INCREMENT=0 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci
    
    -- Y/N 0/1
    -- int , tinyint, smallint
    -- tinyint 정수형으로 총 1byte 저장공간 차지, -128~127 또는 0~255(unsigned)
    -- smallint 정수형 총 2byte 저장공간 차지, -83


select * from swimming_pool;

 

INSERT INTO playground.swimming_pool
(title, type, sido, gugun, road_name, phone, url, open_time, close_time, close_yn, lane_length, lane_count, max_depth, min_depth, kids_pool_yn)
VALUES ('필립발리휘트니스 정자본점', 1, '경기 성남시', '분당구', '내정로 58', '031-728-7777', 'http://www.sportsclubphillip.com/', '06:00:00', '23:00:00', 'N', 25, 5, 2, 1.2, 1);


INSERT INTO playground.swimming_pool
(title, type, sido, gugun, road_name, phone, url, close_yn, lane_length, lane_count, max_depth, min_depth, kids_pool_yn)
VALUES ('올림픽기념국민생활관', 0, '서울', '종로구', '성균관로 91', '02-745-6701', 'http://www.ijongno.co.kr/front/main/11', 'N', 25, 6, 2.5, 1.5, 0);

 

+ Recent posts