7주차

김민지·2024년 7월 17일

DBMS (DataBase Management System)

관계형 (Relational) 데이터베이스

  • 테이블(표) 형태로 데이터를 저장함
  • 종류: PostgreSQL, MySQL, SQLite

비관계형 (Non-relational) 데이터베이스

  • 문서형(.json) 형태로 데이터를 저장함
  • 종류: mongoDB

ORM (Object-Relational Mapping)

  • SQL (Structured Query Language)을 배우는 대신 자바스크립트의 ORM 라이브러리를 활용해서 대체할 예정
  • Prisma : 테이블 정의가 편리함, 직관적인 CRUD 문법 사용, 데이터 관리 GUI 툴 제공

Prisma

초기화

  1. npx prisma command로 초기화하기
 npx prisma init --datasource-provider postgresql 
  1. .env 파일의 URL 수정 및 포트 번호 추가하기
  • URL 수정
    johndoe:randompassword > postgres:password로 설정
    mydb > comazon_dev로 설정
  • 포트 번호 추가
    PORT=3000

User Table 모델 만들기

  • Shift + Alt + F 단축키로 코드를 자동 정렬하기
model User {
  id        String   @id @default(uuid())
  email     String
  firstName String
  lastName  String
  address   String
  createdAt DateTime @default(now())
  updatedAt DateTime @updatedAt
}
  • 필드 구성: field name | field type | @attribute(필드에 대한 추가정보)
  • @id는 해당 필드가 데이터를 식별하는 고유 id 역할을 한다는 사실을 명시함
  • @default(uuid())는 디폴트 값으로 uuid(36자로 이루어진 id 형식)을 사용하라는 점을 명시함
  • @unique는 해당 필드이름의 데이터가 중복 없이 고유해야한다는 점을 명시함
  • @default(now())는 데이터가 업데이트된 시점을 디폴트로 사용함

Prisma Schema 추가 기능

  • enum
    필드의 값이 몇 가지 정해진 값 중 하나일 때는 enum(enumerated type)을 사용할 수 있음
model User {
  // ...
  membership  Membership  @default(BASIC)
}

enum Membership {
  BASIC
  PREMIUM
}
  • @@unique
    여러 필드의 조합이 unique해야 하는 경우 @@unique 어트리뷰트를 사용 가능
    특정 필드에 종속된 어트리뷰트가 아니기 때문에 모델 아래 부분에 적음
model User {
  id        String   @id @default(uuid())
  email     String   @unique
  firstName String
  lastName  String
  address   String
  createdAt DateTime @default(now())
  updatedAt DateTime @updatedAt

  @@unique([firstName, lastName])
}

위와 같이 정의하면 firstName과 lastName의 조합이 unique해야 함 (firstName끼리, lastName끼리는 겹칠 수 있지만 firstName과 lastName이 동시에 겹칠 수는 없음. 즉, 성끼리, 이름끼리는 겹칠 수 있지만 동명이인이 있을 수는 없음)

Migration

  • model table을 데이터베이스에 반영하는 과정
  • schema 파일에 변경사항이 발생할 때마다 해줘야 함
  • 진행 방법
  1. npx prisma migrate dev 입력
    1-1. Error: P1001: Can't reach database server at localhost:5432 오류 발생 (Window 11 ver.)
    (1) SQL Shell (psql) 실행
    (2) 아래의 이미지와 같이 Server, Database, Port, Username 란에서는 전부 엔터만 치고 넘어가기
    (3) postgres 사용자의 암호: 본인이 PostgreSQL 설치 과정에서 설정한 암호 입력
    (4) postgres=# 뒤에 CREATE DATABASE comazon_dev; CREATE DATABASE 명령어 입력
    (5) npx prisma migrate dev 재시도

  2. migration name에서는 일반적으로 통용되는 init을 입력

  3. npx prisma studio 입력 > usermode 클릭 > add record 클릭해서 데이터 추가해보기 > save change 눌러서 저장

  • migration.sql 파일에는 마지막 migration 이후의 변경사항을 schema 형태로 저장함

  • 테이블에 이미 데이터가 있는 경우 필드 추가 후 migration할 경우의 오류
    age Int 등의 필드를 생성한 후 migration하려고 한다면 기존 데이터에는 age 값이 없는데 age를 필수 필드로 생성하려고 하는 중이므로 오류가 발생함
    필드 삭제 시에는 문제되지 않음

  • 해결책

  1. 필드를 처음 생성할 때에는 age Int?로 optional하게 생성한 후 이후에 기존 데이터에서 age값을 추가해주고 나서 필수 필드로(?를 지우고 나서) 다시한번 migrate해줘야 함
    ?를 붙이면 필드가 optional해짐
  2. default attribute를 사용해서 값이 없는 row에 값을 넣는 방식도 가능

Prisma Client와 Database CRUD (API 만들기)

  • App.js 파일
import * as dotenv from 'dotenv';
dotenv.config();
import express from 'express';
import { PrismaClient } from '@prisma/client';

const prisma = new PrismaClient();

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

app.get('/users', async (req, res) => {
  // user 목록 조회
  const users = await prisma.user.findMany();
  res.send(users);
});

app.get('/users/:id', async (req, res) => {
  const { id } = req.params;
  // id에 해당하는 user 조회
  const user = await prisma.user.findUnique({
    where: { id },
  });
  res.send(user);
});

app.post('/users', async (req, res) => {
  // 리퀘스트 바디 내용으로 유저 생성
  const user = await prisma.user.create({
    data: req.body,
  });
  res.status(201).send(user);
});

app.patch('/users/:id', async (req, res) => {
  const { id } = req.params;
  // 리퀘스트 바디 내용으로 id에 해당하는 유저 수정
  const user = await prisma.user.update({
    where: { id },
    data: req.body,
  })
  res.send(user);
});

app.delete('/users/:id', async (req, res) => {
  const { id } = req.params;
  // id에 해당하는 유저 삭제
  await prisma.user.delete({
    where: { id },
  })
  res.sendStatus(204);
});

app.listen(process.env.PORT || 3000, () => console.log('Server Started'));
  • users.http 파일
GET http://localhost:3000/users

###

GET http://localhost:3000/users/b8f11e76-0a9e-4b3f-bccf-8d9b4fbf331e

###

POST http://localhost:3000/users
Content-Type: application/json

{
  "email": "yjkim@example.com", 
  "firstName": "유진",
  "lastName": "김",
  "address": "충청북도 청주시 북문로 210번길 5"
}

###

PATCH http://localhost:3000/users/b8f11e76-0a9e-4b3f-bccf-8d9b4fbf331e
Content-Type: application/json

{
  "address": "서울특별시 강남구 무실로 234번길 45-6"
}

###

DELETE http://localhost:3000/users/b8f11e76-0a9e-4b3f-bccf-8d9b4fbf331e

Database seeding

  • 데이터베이스에 초기데이터를 넣는 것
  • 대량의 데이터를 한꺼번에 넣을 때에는 하나하나 수동으로 입력하기보다는 대량의 데이터를 담은 mock.js, 입력을 처리할 코드를 담은seed.js 등을 활용해서 넣음
// mock.js 파일 
export const USERS = [
  {
    id: 'b8f11e76-0a9e-4b3f-bccf-8d9b4fbf331e',
    email: 'honggd@example.com',
    firstName: '길동',
    lastName: '홍',
    address: '서울특별시 강남구 무실로 123번길 45-6',
    createdAt: '2023-07-16T09:00:00Z',
    updatedAt: '2023-07-16T09:00:00Z',
  },
  {
    id: '6c3a18b0-11c5-4d97-9019-9ebe3c4d1317',
    email: 'kimyh@example.com',
    firstName: '영희',
    lastName: '김',
    address: '경기도 고양시 봉명로 789번길 21',
    createdAt: '2023-07-16T09:30:00Z',
    updatedAt: '2023-07-16T09:30:00Z',
  },
  {
    id: 'fd3ae0a5-8dd5-40b6-b8fd-48870f731db1',
    email: 'lee.cs@example.com',
    firstName: '철수',
    lastName: '이',
    address: '인천광역시 남구 향교로 567번길 8-2',
    createdAt: '2023-07-16T10:00:00Z',
    updatedAt: '2023-07-16T10:00:00Z',
  },
  {
    id: '70e1e61d-f2ae-4d7d-bf8f-d65eafdb6a45',
    email: 'parkjy@example.com',
    firstName: '지영',
    lastName: '박',
    address: '대전광역시 중구 성남로 432번길 76',
    createdAt: '2023-07-16T10:30:00Z',
    updatedAt: '2023-07-16T10:30:00Z',
  },
  {
    id: '73cb9639-d8b7-4f11-9a62-53f4187f3f11',
    email: 'jungminsoo@example.com',
    firstName: '민수',
    lastName: '정',
    address: '부산광역시 동래구 수림로 987번길 33-7',
    createdAt: '2023-07-16T11:00:00Z',
    updatedAt: '2023-07-16T11:00:00Z',
  },
];

1.seed.js 파일 작성

import { PrismaClient } from '@prisma/client';
import { USERS } from './mock';

const prisma = new PrismaClient();

async function main() {
  // 기존 데이터 삭제
  await prisma.user.deleteMany();
  // mock.js 데이터 삽입
  await prisma.user.createMany({
    data: USERS,
    skipDuplicates: true,
  });

}

main()
  .then(async () => {
    await prisma.$disconnect();
  })
  .catch(async (e) => {
    console.error(e);
    await prisma.$disconnect();
    process.exit(1);
  });

  1. package.json 파일에 아래 코드 추가
  "prisma":{
    "seed":"node prisma/seed.js"
  }
  1. npx prisma db seed command로 실행
    npx prisma studio로 확인

Query parameter 처리하기

// App.js
import * as dotenv from 'dotenv';
dotenv.config();
import express from 'express';
import { PrismaClient } from '@prisma/client';

const prisma = new PrismaClient();

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

app.get('/users', async (req, res) => {
  const { offset = 0, limit = 10, order = 'newest' } = req.query;
  let orderBy;
  switch (order) {
    case 'oldest':
      orderBy = { createdAt: "asc"};
      break;
    case 'newest':
    case 'default':
      orderBy = { createdAt: "desc"};
  }

  const users = await prisma.user.findMany({
    orderBy,
    skip: parseInt(offset),
    take: parseInt(limit),
  });
  res.send(users);
});
// ...

Prisma Client 추가 기능

https://www.codeit.kr/topics/js-server-with-relational-db/lessons/6798

유효성 검사

  • 데이터에 필요한 필드가 모두 있는지, 모델에 정의되지 않은 필드는 없는지, 각 필드가 올바른 형식인지를 체크하기
  1. superstruct 라이브러리 이용: 터미널에 npm list 입력 > superstruct, is-email, is-uuid가 설치되어있는지 확인하고 만약 설치되지 않은 것들이 있다면 설치해주기
  2. structs.js 파일 생성 >
import * as s from 'superstruct';
import isEmail from 'is-email';

export const CreateUser = s.object({
	email: s.define('Email', isEmail), // 이메일주소 형식인지 검사
  	firstName: s.size(s.string(), 1, 30), // 1~30글자 사이인지 검사
  	lastName: s.size(s.string(), 1, 30),
  	address: s.string(),
})

export const PatchUser = s.partial(CreateUser);
  1. npm run dev로 서버 시작
  2. users.http로 테스트

오류 처리하기

  • 오류가 발생할 때마다 서버가 종료된다면 문제가 발생할 것이므로 오류를 다르게 처리하는 방식 도입
  • try-catch문으로 감싸주기
    Alt + Click을 누르고 커서를 선택하면 커서를 한번에 여러군데에 배치해서 여러곳을 동시에 수정할 수 있음 (같은 내용을 여러번 다른 위치에 입력해야 할 때 유용)

Prisma 내용 정리

https://www.codeit.kr/topics/js-server-with-relational-db/lessons/6802

관계형 데이터베이스란?

Primary key와 Foreign key

  • Primary key: 사용자가 정한 row를 식별할 수 있는 column으로, 사용자가 지정하게 됨 (ORM을 사용하는 경우 자동으로 정해지기도 함)
    예: id값

  • Foreign key: 다른 테이블의 Primary key를 참조하는 column으로, 예를들어 Order table에서 User table의 id column을 참조하는 userid라는 column을 만든다면 이게 바로 Foreign key이다.

데이터 모델링과 ER 모델

  • 데이터 모델링: 서비스에 정확히 어떤 데이터가 필요하고 데이터 간 어떤 관계가 있는지를 파악해서 정교하게 표현하는 것
  • ER(Entity-Relationship) 모델
    (1) 개체 (Entity): 현실세계의 사물 or 객체. 하나의 개체 = 하나의 테이블
    예: 유저, 상품, 주문 ...
    (2) 속성 (Attribute): 개체의 세부 정보. 하나의 속성 = 하나의 컬럼
    예: 유저의 성, 이름, 이메일 ...
    (3) 관계 (Relationship): 개체간의 관계
    예: 유저는 주문을 할 수 있다, 주문은 여러 상품을 포함한다 ...

ER 모델링: 개체, 속성, 관계후보 찾기

  • 사업규칙(Business Rule): 사업이나 서비스가 운영되기 위해 따르는 규칙
    예:
  • 개체의 후보: 하나의 값으로 표현할 수 없는 명사
  • 속성의 후보: 하나의 값으로 표현할 수 있는 명사
  • 관계의 후보: 동사

ER 모델링: 카디널리티(Cardinality)

  • 카디널리티(Cardinality): A개체와 B개체 사이의 관계가 있다고 가정했을 때, A개체 하나가 B개체 몇개와 연결되어있을 수 있는지, B개체 하나가 A개체 몇개와 연결되어있을 수 있는지를 뜻함
    예: 하나의 User는 여러개의 Order를 할 수 있지만, 하나의 Order는 하나의 User에 의해서 이루어짐 ➡️ 일대다 관계
    Product는 여러 User에 의해 찜해질 수 있고, User는 여러 Product를 찜할 수 있음 ➡️ 다대다 관계

    일대일 관계도 존재함

  • Crow's Foot Notation: 일대다에서 |로, 삼지창모양으로 나타내기

ER 모델 -> 데이터베이스 테이블

  • 일대다 관계: 쪽 개체의 테이블에 Foreign key를 추가해야 함
  • 일대일 관계: 아무 쪽에나 Foreign key를 추가하고 UNIQUE하게 만들어야 함
  • 이왕이면 A에 B가 속해있는 관계라면 B에 추가하는 것이 권장
  • 다대다 관계: 새로운 테이블을 하나 생성해서 새로운 테이블에 각 개체의 Private key를 Foreign key로 추가하기

Prisma와 관계

일대다 관계 정의하기: User, Order

  • Prisma schema 파일 작성
  • npx prisma migrate dev 로 migrate하기
  • 일대다 관계에서, 하나의 User는 여러개의 Order를 할 수 있지만, 하나의 Order는 하나의 User에 의해서 이루어짐
model Order {
	user User

라고 입력한 후 ctrl + Alt + F를 누르면 자동으로 @relation(fields: [userId], references: [id])가 생성되고, User 모델에도 Order Order[]가 생성됨

  • 관계필드 (방금 생성한 user UserOrder Order[])는 데이터베이스에는 저장되지 않으며 prisma에서 관계를 형성할 때만 참조됨

일대일 관계 정의하기: UserPreference, User

  • 일대다 관계와 동일하게 진행한 후 model User에 자동 생성된 배열 userPreference UserPreference[]userPreference UserPreference?로 수정하고, model UserPreferenceuserId String@unique 추가하기

다대다 관계 정의하기: User, Product

  • prisma가 자동으로 새로운 테이블을 하나 생성해줌!
    각 모델에 타 모델 배열을 저장하는 필드를 추가하기(User 모델에 savedProducts Product[] 추가, Product 모델에 savedUsers User[] 추가) ➡️ migration 진행

@relation의 onDelete 옵션

  • onDelete: 연결된 데이터(Foreign 키가 가리키는 데이터)가 삭제되었을 때 기존 데이터를 어떻게 처리할 것인지
    (1) onDelete: Cascade: Foreign 키가 가리키는 데이터가 삭제되면 기존 데이터도 삭제
    (2) onDelete: Restrict: 만약 특정 데이터를 참조하는 데이터들이 있으면 데이터를 삭제할 수 없음
    (3) onDelete: setNull: Foreign key가 가리키는 데이터가 삭제되면 Foreign key를 NULL로 변경 (onDelete 옵션의 기본값!!)
    (4) onDelete: SetDefault: Foreign key가 가리키는 데이터가 삭제되면 Foreign key를 디폴트 값으로 설정
model Order {
	// ...
    user User @relation(fields: [userId], reference: [id], onDelete: SetDefault)
    userId String @default("Anonymous") // prisma에서는 반드시 큰따옴표를 사용해야 함!
}

총정리

https://www.codeit.kr/topics/js-server-with-relational-db/lessons/6964

배포하기

https://www.codeit.kr/topics/js-server-with-relational-db/lessons/6965

0개의 댓글