개발일지

개발일지 4주차 WIL

index.ys 2023. 4. 30. 16:22

ORM

  • ORM이란 Object Relational Maping의 약자이다 즉, 객체와 관계형 데이터베이스(RDBMS)를 자동으로 연결해주는 것
  • 객체 지향 프로그래밍클래스를 사용하고 관계형 DB테이블을 사용하므로 객체모델관계형 DB간에 불일치가 존재함, 이를 해결하기 위해 ORM을 사용하여 SQL을 생성하고 불일치해결함.
  • DB를 변경할때 모든 Raw Query를 ORM으로 빠르게 변경가능
  • DB Table 수정시 빠르게 수정, 변경가능
  • 대표적인 ORM:Type ORM , Prisma, Sequelize 등이 있음

ORM의 장점

  • 객체지향적인 코드, 직관적인 코드, 데이터조작을 편리하게함, 생산성을 높혀줌
  • 코드의 가독성을 올려줌
  • 코드의 재사용 및 유지보수 편리

ORM의 단점

ORM 예시 코드

Type ORM

app.js

  • 라우터 설정, 모델 설정
  • 연결 실행
import { AppDataSource } from "./data-source";
import * as express from "express";
import userRouter from "./routers/UserRouter";

const app = express();
app.use(express.json());

AppDataSource.initialize()
  .then(async () => {})
  .catch((error) => console.log(error));

app.use("/api", userRouter);
app.listen(3000, () => {
  console.log("Server running");
});

data-source.ts

- 데이터 베이스 연결 정보를 설정

import "reflect-metadata";
import { DataSource } from "typeorm";
import { Chat } from "./entity/Chat";
import { User } from "./entity/User";

export const AppDataSource = new DataSource({
  type: "mysql",
  host: "localhost",
  port: 3000,
  username: "ID",
  password: "PW",
  database: "DBname",
  synchronize: true,
  logging: false,
  entities: [User, Chat],
  migrations: [],
  subscribers: [],
});

모델 생성

- 컬럼의 타입, 관계설정 ,제약조건 등을 추가함

import { Entity, PrimaryGeneratedColumn, Column, OneToMany } from "typeorm";
import { Chat } from "./Chat";

@Entity({ name: "users" })
export class User {
  @PrimaryGeneratedColumn("uuid")
  id: string;

  @Column({ type: "varchar", length: 10 })
  firstName: string;

  @Column({ nullable: false })
  lastName: string;

  @Column()
  age: number;

  @OneToMany(() => Chat, (chat) => chat.user)
  chats: Promise<Chat[]>;
}

데이터 삽입 create()

  • firstName, lastName, age 세개의 인자를 body로부터 전달받음
  • userRepo 변수에 User모델을 할당함
  • User테이블에 body로부터 전달받은 정보를 create(info)로 생성
  • 생성이 완료되면 save(user)메서드로 정보를 저장하고 저장한 정보를 응답으로 반환
 addUser = async (req: Request, res: Response) => {
    let info = {
      firstName: req.body.firstName,
      lastName: req.body.lastName,
      age: req.body.age,
    };

    const userRepo = AppDataSource.getRepository(User);
    const user = userRepo.create(info);

    await userRepo
      .save(user)
      .then((data) => {
        res.json(data);
      })
      .catch((err) => console.log(err));
  };

데이터 조회 findOne

  • 파라미터로 id를 전달받음
  • User테이블을 userRepo에 저장
  • userRepo에서 파라미터에 해당하는 id를 가진 데이터를 검색
  • 파라미터에 해당하는 데이터 발견시 찾은 데이터를 응답으로 반환하고 콘솔에도 찍음.
  getUser = async (req: Request, res: Response) => {
    let name = req.params.firstName;
    const userRepo = AppDataSource.getRepository(User);

    await userRepo
      .findOne({ where: { firstName: name } })
      .then((data) => {
        res.json(data);
        console.log("Get User: ", data);
      })
      .catch((err) => console.log(err));
  };

Sequelize

config.json

- 데이터 베이스 연결 정보를 설정

{
  "development": {
    "username": "유저이름",
    "password": "패스워드",
    "database": "데이터베이스이름",
    "host": "localhost",
    "dialect": "mysql"
  }
}

app.js

- 포트 설정 , 라우터 설정

const express = require("express");
const cookieParser = require("cookie-parser");
const usersRouter = require("./routes/users.route");
const postsRouter = require("./routes/posts.route");
const app = express();
const PORT = 3018;

app.use(express.json());
app.use(cookieParser());
app.use('/api', [usersRouter, postsRouter]);

app.listen(PORT, () => {
  console.log(PORT, '포트 번호로 서버가 실행되었습니다.');
})

테이블 생성

- 명령어를 통해 DB에 테이블을 생성함

npx sequelize migration:create --name 테이블이름

생성된 테이블 예시

- 각 컬럼별로 타입설정, 참조할 외래키설정 등 컬럼의 속성을 정의함

'use strict';
/** @type {import('sequelize-cli').Migration} */
module.exports = {
  async up(queryInterface, Sequelize) {
    await queryInterface.createTable('Users', {
      userId: {
        allowNull: false, // NOT NULL
        autoIncrement: true, // AUTO_INCREMENT
        primaryKey: true, // Primary Key (기본키)
        type: Sequelize.INTEGER,
      },
      email: {
        allowNull: false, // NOT NULL
        type: Sequelize.STRING,
        unique: true,
      },
      password: {
        allowNull: false, // NOT NULL
        type: Sequelize.STRING,
      },
      createdAt: {
        allowNull: false, // NOT NULL
        type: Sequelize.DATE,
        defaultValue: Sequelize.fn('now'),
      },
      updatedAt: {
        allowNull: false, // NOT NULL
        type: Sequelize.DATE,
        defaultValue: Sequelize.fn('now'),
      },
    });
  },
  async down(queryInterface, Sequelize) {
    await queryInterface.dropTable('Users');
  },
};

마이그레이트 하여 변경사항을 DB에 적용함

sequelize db:migrate

생성된 모델 예시

- 마이그레이션 파일과 동일하게 속성을 동일하게 작성함.

'use strict';
const { Model } = require('sequelize');
module.exports = (sequelize, DataTypes) => {
  class Users extends Model {
    /**
     * Helper method for defining associations.
     * This method is not a part of Sequelize lifecycle.
     * The `models/index` file will call this method automatically.
     */
    static associate(models) {
      // define association here

      // 1. Users 모델에서
      this.hasOne(models.UserInfos, { // 2. UserInfos 모델에게 1:1 관계 설정을 합니다.
        sourceKey: 'userId', // 3. Users 모델의 userId 컬럼을
        foreignKey: 'UserId', // 4. UserInfos 모델의 UserId 컬럼과 연결합니다.
      });


      // 1. Users 모델에서
      this.hasMany(models.Posts, { // 2. Posts 모델에게 1:N 관계 설정을 합니다.
        sourceKey: 'userId', // 3. Users 모델의 userId 컬럼을
        foreignKey: 'UserId', // 4. Posts 모델의 UserId 컬럼과 연결합니다.
      });

      // 1. Users 모델에서
      this.hasMany(models.Comments, { // 2. Comments 모델에게 1:N 관계 설정을 합니다.
        sourceKey: 'userId', // 3. Users 모델의 userId 컬럼을
        foreignKey: 'UserId', // 4. Comments 모델의 UserId 컬럼과 연결합니다.
      });

      // 1. Users 모델에서
      this.hasMany(models.UserHistories, { // 2. UserHistories 모델에게 1:N 관계 설정을 합니다.
        sourceKey: 'userId', // 3. Users 모델의 userId 컬럼을
        foreignKey: 'UserId', // 4. UserHistories 모델의 UserId 컬럼과 연결합니다.
      });
    }
  }

  Users.init(
    {
      userId: {
        allowNull: false, // NOT NULL
        autoIncrement: true, // AUTO_INCREMENT
        primaryKey: true, // Primary Key (기본키)
        type: DataTypes.INTEGER,
      },
      email: {
        allowNull: false, // NOT NULL
        type: DataTypes.STRING,
        unique: true,
      },
      password: {
        allowNull: false, // NOT NULL
        type: DataTypes.STRING,
      },
      createdAt: {
        allowNull: false, // NOT NULL
        type: DataTypes.DATE,
        defaultValue: DataTypes.NOW,
      },
      updatedAt: {
        allowNull: false, // NOT NULL
        type: DataTypes.DATE,
        defaultValue: DataTypes.NOW,
      },
    },
    {
      sequelize,
      modelName: 'Users',
    }
  );
  return Users;
};

데이터 삽입 create

  • body로 title과, content를 인자로 전달받음
  • Posts테이블에 전달 받은 id와 title, content를 생성함
  • 생성한 데이터를 응답으로 반환
// 게시글 생성
router.post("/posts", authMiddleware, async (req, res) => {
  const { userId } = res.locals.user;
  const { title, content } = req.body;

  const post = await Posts.create({
    UserId: userId,
    title,
    content,
  });

  return res.status(201).json({ data: post });
});

데이터 조회 findAll, findOne, findeById

  • findAll메서드로 Posts테이블에 있는 모든 데이터를 조회
  • 생성된 시간을 기준으로 오름차순으로 데이터를 조회하는 조건을 검
  • 조회한 모든 데이터를 응답으로 반환
router.get("/posts", async (req, res) => {
  const posts = await Posts.findAll({
    attributes: ["postId", "title", "createdAt", "updatedAt"],
    order: [['createdAt', 'DESC']],
  });

  return res.status(200).json({ data: posts });
});

Prisma

Schema 설정

  • Datasource: 연결할 디비를 말한다.
  • Generator: Prisma Client 를 생성하고 싶다는 것을 가리킨다.
  • Data model: 애플리케이션 모델을 정의한다.
datasource db {
  provider = "postgresql"
  url      = env("DATABASE_URL")
}

generator client {
  provider = "prisma-client-js"
}

model Post {
  id        Int     @id @default(autoincrement())
  title     String
  content   String?
  published Boolean @default(false)
  author    User?   @relation(fields: [authorId], references: [id])
  authorId  Int?
}

model User {
  id    Int     @id @default(autoincrement())
  email String  @unique
  name  String?
  posts Post[]
}

데이터 생성

  • user테이블에 데이터를생성
  const newUser = await client.user.create({
    data: {
      name: "kim",
      email: "1234@naver.com",
    },
  });

  res.status(200).json({ newUser });

요약

- 대표적인 ORM인 sequelize, TypeORM, prisma의 전체적인 동작 방식은 모두 흡사하지만, 각각의 ORM의 특성이 조금씩 다르기 때문에 개발환경에 맞게 어떤 ORM을 사용할지 고려하고 ORM을 적용해야함

NoSQL vs SQL

NoSQL ( not only SQL )이란

  • 비관계형 데이터 베이스를 뜻함, 관계형 테이블과 다른 형식으로 데이터를 저장함
  • Schema가 존재하지 않음
  • 대표적인 NoSQL: MongoDB, Redis, Cassandra
  • 사용 서비스: 실시간 웹 애플리케이션, 빅 데이터, 대형 커뮤니티 사이트 등 대량의 데이터를 빠른시간안에 처리 할 수 있는 서비스에 적합.

SQL 이란

  •  SQL은 구조화된 쿼리언어 (Structured Query Language)의 약자이다
  • 데이터는 엄격하게 정해진 데이터 구조를 따라 데이터베이스 테이블에 저장됨
  • 대표적인 SQL: MySQL, Orcle, SQLlite, MariaDB
  • 사용 서비스: 온라인 쇼핑몰, 소셜 네트워크, 블로그 , 뉴스 사이트 등 정해진 테이블에 데이터를 삽입하고 관리할 수 있는 환경에 적합함

요약

- NoSQL과 SQL은 어떤 서비스를 개발할지에 따라 어떤 DB를 사용하는 것이 적합한지 판단해보고 사용해야함.