본문 바로가기
내일배움캠프_게임서버(202410)/분반 수업 Basic-A

베이직 강의 24.12.12 - 5주차 Database와 ORM(Prisma)

by GREEN나무 2024. 12. 12.
728x90

베이직 목(24.12.12) 5주차 DatabaseORM(Prisma)

https://teamsparta.notion.site/5-Database-ORM-Prisma-55844e67285d45ccbc8b2566826773d2#d526b92621a149ae9485015a9da69816

허승우 튜터님

 

Database란 데이터를 체계적으로 저장하고 관리하기 위한 시스템.

 

 

 

Relational Database(RDB, 관계형 데이터베이스)

데이터를 테이블 형식으로 저장하며, 각 테이블은 행(Row)과 열(Column)로 구성됩니다.

테이블 간의 관계를 정의하여 데이터를 효율적으로 관리하고 중복을 최소화합니다.


DB는 도서관

책장이 테이블

데이터가 책

책을 조회 = 데이터를 조회

서로 관련된 데이터(: 도서와 대출 기록)는 도서관 시스템 내에서 연결(테이블 간 관계)되어 있습니다

 


(col)이 컬럼, (row)이 데이터

 

🔹id :

- 중복된 데이터가 있어도 구분할 수 있음

   ex) 동명 이인

-필요한 데이터만 빠르게 조회 가능

 


RDB의 주요 용어

SQL: Structured Query Language의 약자로, 데이터를 저장, 수정, 조회하는 데 사용하는 언어입니다.

 

🔹  Primary Key: 각 행을 고유하게 식별하는 열.

            ex) id

🔹  Foreign Key: 다른 테이블과의 관계를 정의하는 열. 일반적으로 고유한 값을 가집니다.

           다른 테이블에 있는 값을 조회하기 위해 내 테이블과 이어주는 키

 

🔹Join : Foreign Key를 이용해 다른테이블을 이어 사용합니다.

            inner join, left join, right join, full outer join

 

Join을 사용하는 경우

  1. 데이터 중복 제거: 테이블을 분리하여 저장했으므로, 관계를 맺어 조합이 필요.
  2. 데이터 통합: 쪼개진 데이터를 한 번에 조회.
  3. 효율적인 관리: 테이블 간 관계를 명확히 유지하여 데이터 무결성 보장.
  4. 복잡한 쿼리 처리: 하나의 쿼리로 다차원 데이터를 가져올 수 있음.

join 기본 형태 : 

select * 
FROM 테이블a as a join 테이블b as b on a.id = b.aId -- 외래키로 잇기

ex)

SELECT orders_table.order_name, user_table.name -- 컬럼 선택
FROM orders_table JOIN user_table ON orders_table.user_id =user_table.id; -- FK 이용하여 JOIN

 

inner join 교집합 : 

테이블a inner join 테이블b on 조건;

 

left join 집합A : 앞에 있는 테이블에 연결된 값이 있는 것 들만 가져옴. 테이블b의 값이 있을수도 없을 수도 있음

테이블a left join 테이블b on 조건;

테이블a 가 한국의 도시 테이블이고 테이블b가 화석이 발견된 장소 테이블이라 할 때

 

- 화석이 발견되지 않은 지역만 추출 (b.fossil 값이 NULL)

SELECT *
FROM A left join B on A.postId = B.postId
WHERE B.fossil IS NULL;

 

- 화석이 발견된 지역만 추출 (b.fossil 값이 NOT NULL)

SELECT *
FROM A left join B on A.postId = B.postId
WHERE B.fossil IS NOT NULL;

 

 

right join 집합B : 뒤에 있는 테이블에 연결된 값이 있는 것 들만 가져옴. 테이블a의 값이 있을수도 없을 수도 있음

테이블a right join 테이블b on 조건;

 

full outer join : RIGHT JOIN LEFT JOIN의 결과를 합

MySQL에서는 full outer join을 지원하지 않아 right join left join을 합한것으로 쿼리를 짜야한다.

SELECT *
FROM A FULL OUTER JOIN B on A.postId = B.postId

 

 

그 외에 join

더보기

Join의 종류와 간단한 설명, 사용법

1. Equi Join (등가 조인)

  • 설명: 두 테이블의 컬럼 값이 같은 데이터만 반환.
  • 사용법:
    SELECT Project.project_name, Employee.full_name, Employee.department 
    FROM Project, Employee 
    WHERE Project.employee_id = Employee.employee_id;
    
  • 사용 이유: PK와 FK 관계를 기반으로 데이터를 연결할 때.

2. Non-Equi Join (비등가 조인)

  • 설명: 컬럼 값이 아닌 다른 조건으로 조인.
  • 사용법:
    SELECT Employee.full_name, Project.project_name 
    FROM Employee, Project 
    WHERE Employee.salary > 2500000;
    
  • 사용 이유: 숫자, 범위 등 다른 조건으로 관계를 정의할 때.

3. Natural Join (자연 조인)

  • 설명: 공통 컬럼 이름을 기준으로 조인하며, 중복 컬럼 제거.
  • 사용법:
    SELECT *
    FROM Address NATURAL JOIN Employee;
    
  • 사용 이유: 중복 컬럼 없이 깔끔한 결과가 필요할 때.

4. Outer Join (외부 조인)

4.1. Left Outer Join

  • 설명: 왼쪽 테이블의 모든 데이터와 오른쪽 테이블의 매칭 데이터를 가져오며, 매칭이 없으면 NULL로 반환.
  • 사용법:
    SELECT *
    FROM Employee E 
    LEFT JOIN Address A
    ON E.employee_id = A.employee_id;
    

4.2. Right Outer Join

  • 설명: 오른쪽 테이블의 모든 데이터와 왼쪽 테이블의 매칭 데이터를 가져오며, 매칭이 없으면 NULL로 반환.
  • 사용법:
    SELECT *
    FROM Address A 
    RIGHT JOIN Employee E
    ON A.employee_id = E.employee_id;

4.3. Full Outer Join (완전 외부 조인)

  • 설명: 두 테이블의 모든 데이터를 가져오며, 매칭이 없으면 NULL로 반환.
  • 사용법:
SELECT * 
FROM Employee E LEFT JOIN Address A ON E.employee_id = A.employee_id
UNION
SELECT * 
FROM Address A RIGHT JOIN Employee E ON A.employee_id = E.employee_id;
  • 사용 이유: 양쪽 데이터를 모두 포함해야 할 때.

5. Self Join (셀프 조인)

  • 설명: 같은 테이블을 두 번 조인하여 참조.
  • 사용법:
    SELECT E.employee_id, E.full_name, M.full_name AS manager_name
    FROM Employee E 
    LEFT JOIN Employee M 
    ON E.manager = M.employee_id;
    
  • 사용 이유: 계층적 데이터나 관계를 표현할 때.

6. Semi Join (세미 조인)

  • 설명: 조건을 만족하는 데이터가 다른 테이블에 존재하는지 여부만 확인.
  • 사용법:
    SELECT E.employee_id, E.full_name
    FROM Employee E
    WHERE EXISTS (
      SELECT 1 
      FROM Project P 
      WHERE P.employee_id = E.employee_id
    );
    
  • 사용 이유: 연결된 데이터 여부만 확인할 때.

Object-Relational Mapping (ORM)

ORM은 프로그래밍 언어에서 데이터베이스를 조작할 수 있도록 돕는 도구로 SQL 쿼리를 직접 작성하지 않고도 데이터베이스와 상호작용이 가능합니다.

비유: ORM은 통역사. 사용자가 말하는 내용을 데이터베이스가 이해할 수 있도록 번역해주는 역할

         언어별로 다양함

 

 

Prisma : Nodejs에서 사용하는 대표적인 ORM

🔹Prisma CLI 설치: 프로젝트 초기화 및 설정.

# 설치
npm install prisma @prisma/client
npx prisma init

 

내 프로젝트 폴더 이름

├── prisma

   └── schema.prisma

├── .env

├── .gitignore

├── package.json

└── pakcage_lock.json

 

 

🔹 기본 틀

 

Prisma의 데이터베이스 URL 구현하기

데이터베이스 엔진: mysql

마스터 사용자 이름: root

마스터 암호: aaaa4321

RDS 엔드포인트: express-database.clx5rpjtu59t.ap-northeast-2.rds.amazonaws.com

Port 번호: 3306

사용할 DB 이름: prisma_crud

 

 

🔹 기본 설정

schema.prisma 파일에서 데이터베이스의 유형과 연결 정보를 설정합니다.

generator client {

provider = "prisma-client-js"

}

datasource db {

provider = "mysql" // 사용하는 DB종류로 수정하세요

url = env("DATABASE_URL")  // .env 파일의 DATABASE_URL값에 연결합니다.

}

DATABASE_URL.env 파일에 정의합니다

// "mysql://<계정>:<비밀번호>@<DB주소>:<포트>/<db이름>
DATABASE_URL="mysql://root:1234@localhost:3306/test?schema=public“

 

 

🔹 스키마 파일 사용하기

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

datasource db {
  provider = "mysql"
  url      = env("DATABASE_URL")
}

model User {
  id        Int      @id @default(autoincrement()) // Primary Key
  name      String
  gender    String
  createdAt DateTime @default(now())
  orders    Order[] // 관계 정의
}

model Order {
  orderId   Int    @id @default(autoincrement())
  orderName String
  userId    Int
  author    User   @relation(fields: [userId], references: [id])
}

 

🔹 DB에 적용하기

npx prisma db push  // 이건 지양

// 이걸로 쓰기. 마이그레이션 폴더애 생성,수정 기록이 남음
npx prisma migrate dev --name <마이그레이션 이름>

 

마이그레이션 결과

마이그레이션을 하면 이 모델을 만들 수 있는 실제 sql 파인을 만들어줌

변경사항에 대해 히스토리가 남아서 관리 용이

 

📦prisma

📂migrations

┃ ┣ 📂<생성일자>_<마이그레이션 이름>

┃ ┃ ┗ 📜migration.sql

┃ ┗ 📜migration_lock.toml

📜schema.prismaon

더보기
-- migration.sql

-- CreateTable
CREATE TABLE `User` (
    `id` INTEGER NOT NULL AUTO_INCREMENT,
    `name` VARCHAR(191) NOT NULL,
    `gender` VARCHAR(191) NOT NULL,
    `createdAt` DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3),

    PRIMARY KEY (`id`)
) DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;

-- CreateTable
CREATE TABLE `Order` (
    `orderId` INTEGER NOT NULL AUTO_INCREMENT,
    `orderName` VARCHAR(191) NOT NULL,
    `userId` INTEGER NOT NULL,

    PRIMARY KEY (`orderId`)
) DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;

-- AddForeignKey
ALTER TABLE `Order` ADD CONSTRAINT `Order_userId_fkey` FOREIGN KEY (`userId`) REFERENCES `User`(`id`) ON DELETE RESTRICT ON UPDATE CASCADE;

Prisma Method 사용: 애플리케이션에서 데이터베이스와 상호작용.

 

🔹create : 데이터 생성

import { PrismaClient } from '@prisma/client';
const prisma = new PrismaClient();

const newUser = await prisma.user.create({
  data: {
    name: "김지웅",
    gender: "남자"
  }
});

 

🔹 nested write :

- 데이터 생성 시 연관된 데이터를 같이 생성

- 자동으로 트렌젝션 처리

const newUser = await prisma.user.create({
  data: {
    name: "김지웅",
    gender: "남자",
    orders: {
      create: {
        orderName: "아메리카노",
      }
    }
  }
});

 

🔹 findUnique:

- unique로 설정된 column값을 가지고 조회하는 경우 사용.

- 속도가 빠름

- findFerst는 컬럼이 unique아닌 경우에만 사용하기

const users = await prisma.user.findUnique({
  where: {
    id: 1,
  },
})

 

🔹 findMany:

- 여러가지 column을 가지고 여러개의 데이터를 조회할때 사용

- 전체 조회

const users = await prisma.user.findMany();

const users = await prisma.user.findMany();

-  조건 조회

const users = await prisma.user.findMany({
  where: {
    gender: "남자"
  }
});

include 조회 (join)

 - 테이블a as a join 테이블b as b on a.id = b.aId  include로 실행됨. 무거우니 필요할 때만 사용

const orders = await prisma.order.findMany({
  include: {
    user: true
  },
});

// 

const orders = await prisma.order.findMany({

include: {

user: true,

},

});

🔹 findFirst :

- findMany와 같지만 맨 처음 조회 된 1개만 조회

- 태이블.findManylimit이 붙은 형태. 무거움. 유니크 있는건 .findUnique로 사용!

const users = await prisma.user.findFirst({
  where: {
    gender: "남자"
  }
});

SQL문을 이용한 로우쿼리(프리즈마에서 제공)

🔹 SQL문법을 이용한 Raw쿼리 사용법

// prisma
const newUser = await prisma.user.create({
  data: {
    name: "김지웅",
    gender: "남자"
  }
});

// raw query
await prisma.$executeRaw`
  INSERT INTO "User" ("name", "gender")
  VALUES ('김지웅', '남자');
`;

 

🔹 Transaction을 사용한 원자성 보장방법

// nested write 사용
const newUser = await prisma.user.create({
  data: {
    name: "김지웅",
    gender: "남자",
    orders: {
      create: {
        orderName: "아메리카노",
      }
    }
  }
});

// transaction 사용
await prisma.$transaction(async (tx) => {
  // 첫 번째 작업: 사용자 생성
  const newUser = await tx.user.create({
    data: {
      name: "김지웅",
      gender: "남자"
    },
  });

  // 두 번째 작업: 새로운 사용자의 주문 생성
  await tx.order.create({
    data: {
      orderName: "아메리카노",
      userId: newUser.id,
    },
  });

  console.log("모든 작업이 성공적으로 완료되었습니다.");
});

join이랑 트렌젝션 중에 뭐가 더 빠르고 메모리 덜쓸까?

 

1. JOIN

  • 사용 목적: 여러 테이블의 데이터를 한 번에 조회.
  • 장점: 빠르고 네트워크 요청 최소화. 메모리 효율적.
  • 단점: 복잡한 JOIN은 성능 저하 및 메모리 사용 증가 가능.
  • 추천 상황: 데이터 조회.

2. TRANSACTION

  • 사용 목적: 여러 작업(조회, 수정, 생성, 삭제)을 묶어서 원자성을 보장.
  • 장점: 데이터 무결성 및 롤백 가능.
  • 단점: 락(lock)으로 인한 성능 저하, 메모리 사용량 증가.
  • 추천 상황: 종속적인 작업이 필요한 데이터 변경.

결론

  • 데이터 조회: JOIN이 더 빠르고 메모리 효율적.
  • 데이터 수정/생성/삭제: 종속성이 있으면 TRANSACTION필수.
  • 성능 최적화를 위해 JOIN은 필요한 필드만 선택적으로 가져오고, TRANSACTION은 꼭 필요한 경우에만 사용.

숙제

저번주 express과제에서 했던 메모리 저장방식을 DB저장방식으로 변경하기

https://teamsparta.notion.site/5-Database-ORM-Prisma-55844e67285d45ccbc8b2566826773d2


※ 요약

1. Database 개념

  • Database: 데이터를 체계적으로 저장하고 관리하는 시스템.
  • Relational Database (RDB):
    • 데이터를 테이블(행과 열) 형식으로 저장.
    • 테이블 간 관계(Foreign Key)를 통해 효율적 관리 및 중복 최소화.

2. RDB 주요 용어

  • SQL: 데이터베이스를 조작하는 표준 언어.
  • Primary Key: 각 행을 고유하게 식별하는 열.
  • Foreign Key: 다른 테이블과의 관계를 정의하는 열.
  • Join: 두 테이블을 연결하여 데이터를 조회.

Join 종류:

  1. Inner Join: 두 테이블의 교집합 데이터 조회.
  2. Left Join: 왼쪽 테이블의 모든 데이터와 조건이 맞는 오른쪽 테이블 데이터 조회.
  3. Right Join: 오른쪽 테이블의 모든 데이터와 조건이 맞는 왼쪽 테이블 데이터 조회.
  4. Full Outer Join: 두 테이블의 모든 데이터를 합침. (MySQL은 지원 X)

3. ORM (Object-Relational Mapping)

  • ORM: SQL 없이 프로그래밍 언어로 DB 조작.
  • Prisma (Node.js에서 자주 사용):
    • 설정: npm install prisma @prisma/client, npx prisma init.
    • 데이터베이스 연결 정보는 .env 파일에서 설정.
    • Schema 정의: schema.prisma에서 데이터 모델 정의.

4. Prisma 주요 기능

  1. Create: 데이터 생성.
    const newUser = await prisma.user.create({
      data: { name: "김지웅", gender: "남자" },
    });
    
  2. Nested Write: 데이터 생성과 관련 데이터 생성.
  3. findUnique: Primary Key나 Unique Column을 기반으로 데이터 조회.
  4. findMany: 다중 조건 데이터를 조회하거나 전체 조회.
  5. findFirst: 조건에 맞는 첫 번째 데이터 조회.

5. Raw Query & Transaction

  • Raw Query: SQL 문법을 그대로 사용하는 방법.
    await prisma.$executeRaw`
      INSERT INTO "User" ("name", "gender") VALUES ('김지웅', '남자');
    `;
    
  • Transaction: 작업 묶음의 원자성을 보장.
    await prisma.$transaction(async (tx) => {
      const newUser = await tx.user.create({ data: { name: "김지웅", gender: "남자" } });
      await tx.order.create({ data: { orderName: "아메리카노", userId: newUser.id } });
    });
    

6. Join vs Transaction

  • Join:
    • 목적: 여러 테이블 데이터 조회.
    • 장점: 빠르고 네트워크 요청 감소.
    • 단점: 복잡한 쿼리 시 성능 저하 가능.
    • 추천: 데이터 조회 작업.
  • Transaction:
    • 목적: 여러 작업(조회, 수정, 생성)을 묶음으로 처리.
    • 장점: 원자성 보장 및 데이터 무결성 확보.
    • 단점: 락(lock)으로 인한 성능 저하 가능.
    • 추천: 데이터 변경 작업.

 

※Tip

색인 = 인덱싱

 


참고 

Prisma에서 트랜잭션과 다양한 부가기능 : https://dodote10.tistory.com/625

join1: https://velog.io/@pixelstudio/RDBMSMysql-JOIN%EC%97%90-%EB%8C%80%ED%95%B4%EC%84%9C

join2: https://velog.io/@leesomyoung/MySQL-Join%EC%9D%98-%EC%A2%85%EB%A5%98