SQL 쿼리문과 NestJS에서의 데이터베이스 접근

hyejin·2024년 5월 17일
0

study-2024

목록 보기
5/16

SQL

  • SQL이란?
    Structured Query Language의 약자로, 데이터베이스에서 데이터를 추출하고 조작하는 데에 사용하는 데이터 처리 언어
    ➡️ 데이터베이스에 저장된 정보를 쉽게 찾고 정리하는 데에 도움을 주는 도구

  • SQL 사용 목적
    빅데이터 시대에는 대량의 데이터를 다루는 능력이 중요하다. SQL을 사용하기 이전에는 엑셀이나 수작업으로 데이터를 다뤘지만, 이는 시간과 정확성에 문제를 일으키는 경우가 많았다. SQL을 도입하면서 데이터를 효율적으로 추출하고 분석하게 되었으며, 빅데이터를 다루기 위한 필수적인 언어로 자리잡았다.

SQL의 기본적인 쿼리문

  • DATABASE

    • CREATE DATABASE: 데이터베이스 생성

      CREATE DATABASE [데이터베이스 명];
    • DROP: 데이터베이스 또는 테이블 삭제

      # 데이터베이스 삭제
      DROP DATABASE [데이터베이스 명];
      
      # 테이블 삭제
      DROP TABLE [테이블 명];
    • ALTER: 테이블에 컬럼 추가 및 삭제

      # 컬럼 추가
      ALTER TABLE [테이블명] ADD [추가할 컬럼이름] 데이터타입;
      
      # 컬럼 삭제
      ALTER TABLE [테이블명] DROP COLUMN [삭제할 컬럼이름];
    • TRUNCATE: 테이블에 존재하는 모든 데이터 삭제

      TRUNCATE TABLE [테이블명];
  • TABLE

    • CREATE TABLE: 테이블 생성

      CREATE TABLE [테이블 명](
      	Column1 int
          Column2 varchar(255),
          ...
      );
    • SELECT: 데이터 조회

      # 조회
      SELECT * FROM [테이블 명];
      SELECT Coumn1 FROM [테이블 명];
      
      # 데이터 필터링: 원하는 조건의 데이터만 조회
      SELECT * FROM [테이블 명] WHERE Column1 >= 3;
      -> 해당 테이블의 Column1 의 값이 3 이상인 데이터만 조회
      
      # 데이터 정렬: 특정 컬럼을 기준으로 정렬
      SELECT * FROM [테이블 명] ORDER BY Column1 ASC;
      -> 해당 테이블의 Column1을 기준으로 오름차순 정렬
      SELECT * FROM [테이블 명] ORDER BY Column1 DESC, Column2 ASC;
      -> 해당 테이블의 Column1을 기준으로 내림차순 정렬, Column1이 같은 데이터끼리는 Column2기준으로 오름차순 정렬
      
      # 별명 지정: column의 별명을 지정
      SELECT
      	Column1 AS first,
          Column2 AS second
      FROM [테이블 명];
      
    • INSERT: 테이블에 새로운 데이터 추가

      # case 1: 테이블의 모든 컬럼을 입력하는 경우
      INSERT INTO [테이블 명]
      VALUES (1, 'column2 data')
      
      # case2: 테이블의 특정 컬럼을 입력하는 경우
      INSERT INTO [테이블 명] (Column2)
      VALUES ('column2 data')
    • UPDATE: 데이터 변경

      # 전체 데이터 변경
      UPDATE [테이블 명] SET Column1 = 0
      
      # 특정 조건 데이터 변경
      UPDATE [테이블 명] SET Column1 = 0 WHERE [조건]
    • DELETE: 데이터 삭제

      # 특정 조건 데이터 삭제
      DELETE FROM [테이블 명]
      WHERE [조건]

TypeORM

  • TypeORM 개요

    • ORM이란?
      Object Relation Mapping 의 약자로, 객체와 관계형 데이터베이스의 데이터를 자동으로 연결(Mapping)해주는 것이다. 객체지향 프로그래밍은 클래스를 사용하고 관계형 데이터베이스는 테이블을 사용하는데 객체 모델과 관계형 데이터베이스 사이에는 모델 간의 불일치가 존재한다. ORM을 통해 객체 간의 관계를 바탕으로 SQL을 자동으로 생성하여 불일치를 해결한다.

    • TypeORM이란?
      NodeJS, Browser 등의 환경에서 실행할 수 있는 ORM(Object-relational mapping)이며, JavaScript나 TypeScript로 사용할 수 있다. TypeORM은 Active RecordData Mapper 패턴을 지원한다.

      • Active Record
        모델 자체 내에서 모든 쿼리 메서드를 정의하고 모델 메서드를 사용하여 개체에 저장, 제거 및 로드를 한다. 간단하게 말해서 Active Record패턴은 모델 내에서 데이터베이스에 액세스하는 접근방식이다.(이후 new 키워드를 사용해서 새로운 인스턴스를 만듦)

        import {BaseEntity, Entity, PrimaryGeneratedColumn, Column} from "typeorm";
        
        @Entity()
        export class User extends BaseEntity {
            @PrimaryGeneratedColumn()
            id: number;
        
            @Column()
            firstName: string;
        
            @Column()
            lastName: string;
        
            @Column()
            isActive: boolean;
        
            static findByName(firstName: string, lastName: string) {
                return this.createQueryBuilder("user")
                    .where("user.firstName = :firstName", { firstName })
                    .andWhere("user.lastName = :lastName", { lastName })
                    .getMany();
            }
        }
      • Data Mapper 패턴
        분리된 클래스에 쿼리 메서드를 정의하는 방식이며, Repository를 이용하여 객체를 save, delete, read 한다. Active Record 패턴과의 차이점을 모델에 접근하는 방식이 아닌 Repository에서 데이터에 접근한다는 것이다.(이후 getRepository()를 사용하여 만들어진 모델을 사용)

        import {Entity, PrimaryGeneratedColumn, Column} from "typeorm";
        
        @Entity()
        export class User {
        
            @PrimaryGeneratedColumn()
            id: number;
        
            @Column()
            firstName: string;
        
            @Column()
            lastName: string;
        
            @Column()
            isActive: boolean;
        
        }
  • NestJS에서 TypeORM 사용의 이점

    1. 강력한 ORM 기능: TypeORM은 데이터베이스와의 상호 작용을 객체 지향적인 방식으로 처리한다. 이는 TypeScript 클래스를 사용하여 데이터베이스 스키마를 표현하고, 해당 클래스를 이용하여 데이터를 쿼리하고 조작할 수 있게 한다.

    2. 타입 안정성: NestJS와 TypeScript를 함께 사용하면 TypeORM을 이용할 때 쿼리 수행 시 타입 안정성을 보장한다. 이는 개발자가 컴파일 시간에 타입 오류를 확인하고 디버그할 수 있도록 도와준다.

    3. 디자인 패턴 지원: NestJS와 TypeORM은 코드를 구조화하고 모듈화하는 데 일관된 디자인 패턴을 제공한다. 예를 들어, Repository 패턴을 사용하여 데이터베이스 로직을 캡슐화하고 컨트롤러에서 분리할 수 있다.

    4. 다양한 데이터베이스 지원: TypeORM은 PostgreSQL, MySQL, SQLite, SQL Server 등 다양한 데이터베이스 시스템을 지원한다. 이를 통해 필요에 따라 데이터베이스를 쉽게 전환할 수 있다.

    5. 관계형 데이터베이스 관리: TypeORM은 테이블 간 관계 설정 및 관리를 용이하게 한다. 또한, 복잡한 쿼리를 지원하여 데이터베이스를 효율적으로 관리할 수 있다.

    6. 편리한 마이그레이션: TypeORM은 데이터베이스 스키마의 변경사항을 관리하는 강력한 마이그레이션 도구를 제공한다. 이를 통해 데이터베이스 스키마 변경을 추적하고, 업데이트를 간편하게 관리할 수 있다.

TypeORM Repository와 QueryBuilder

  • Repository 패턴
    Repository 패턴은 데이터베이스와의 상호작용을 추상화하여 별도의 클래스로 분리하는 디자인 패턴이다. 각 Repository 클래스는 엔티티(Entity) 클래스와 일대일로 매핑되며, 이 엔티티 클래스는 데이터베이스의 테이블과 일대일로 매핑된다. Repository는 entity 객체와 함께 작동하여 데이터베이스에서 개체를 찾거나 삽입, 업데이트, 삭제하는 등의 작업을 처리한다. 따라서 비즈니스 로직을 처리하는 service 파일에서는 데이터베이스 작업은 Repository가 담당하게 된다.

  • QueryBuilder
    QueryBuilder는 TypeORM이 제공하는 강력한 기능 중 하나이며, 명쾌하고 간편한 구문을 사용하여 SQL 쿼리문을 작성하고 실행할 수 있다. 이를 통해 데이터베이스의 내용을 직접 조작할 수 있으며, 복잡한 쿼리문도 간단하게 작성할 수 있다.

TypeORM을 사용한 쿼리 작성

  • 데이터 조회

    • TypeORM의 간편한 메서드

      import { getRepository } from 'typeorm';
      
      async function fetchUsers() {
          const userRepository = getRepository(User);
          const users = await userRepository.find();
          return users;
      }
    • QueryBuilder

      import { getRepository } from 'typeorm';
      
      async function fetchUsers() {
          const userRepository = getRepository(User);
          const users = await userRepository.createQueryBuilder('user').getMany();
          return users;
      }
  • 데이터 추가

    • TypeORM의 간편한 메서드

      import { getRepository } from 'typeorm';
      
      async function createUser(name: string, email: string) {
          const userRepository = getRepository(User);
          const newUser = userRepository.create({ name, email });
          await userRepository.save(newUser);
      }
    • QueryBuilder

      import { getRepository } from 'typeorm';
      
      async function createUser(name: string, email: string) {
          const userRepository = getRepository(User);
          await userRepository.createQueryBuilder()
            	.insert().values({ name, email }).execute();
      }
  • 데이터 수정

    • TypeORM의 간편한 메서드

      // save - 데이터가 존재하면 update, 존재하지 않으면 insert
       import { getRepository } from 'typeorm';
      
       async function updateUser(userId: number, newName: string) {
           const userRepository = getRepository(User);
           const userToUpdate = await userRepository.findOne(userId);
         
           if (userToUpdate) {
               userToUpdate.name = newName;
               await userRepository.save(userToUpdate);
           }
       }
       
       // update
       import { getRepository } from 'typeorm';
      
       async function updateUser(userId: number, newName: string) {
           const userRepository = getRepository(User);
           await userRepository.update(userId, { name: newName });
    • QueryBuilder

       import { getRepository } from 'typeorm';
      
       async function updateUser(userId: number, newName: string) {
           const userRepository = getRepository(User);
           await userRepository.createQueryBuilder()
           	.update().set({ name: newName })
               .where('id = :id', { id: userId })
               .execute();
       }
  • 데이터 삭제

    • TypeORM의는 간편한 메서드

      import { getRepository } from 'typeorm';
      
      async function deleteUser(userId: number) {
          const userRepository = getRepository(User);
          const userToDelete = await userRepository.findOne(userId);
          if (userToDelete) {
              await userRepository.remove(userToDelete);
          }
      }
    • QueryBuilder

      import { getRepository } from 'typeorm';
      
      async function deleteUser(userId: number) {
          const userRepository = getRepository(User);
          await userRepository.createQueryBuilder()
          	.delete()
              .where('id = :id', { id: userId })
              .execute();
      }

네이티브 쿼리 및 파라미터 바인딩

  • 네이티브 쿼리
    네이티브 쿼리 실행은 SQL 쿼리가 데이터베이스에서 직접 실행되는 것을 의미한다.

    import { Injectable } from '@nestjs/common';
    import { InjectRepository } from '@nestjs/typeorm';
    import { Repository } from 'typeorm';
    import { User } from './user.entity';
    
    @Injectable()
    export class UserService {
        constructor(
            @InjectRepository(User)
            private readonly userRepository: UserRepository,
        ) {}
    
        async executeNativeQuery() {
            const result = await this.userRepository.createQueryBuilder()
            	.query('SELECT * FROM users WHERE age > :age', { age: 20 });
            return result;
        }
    }
  • 파라미터 바인딩
    파라미터 바인딩은 쿼리에 사용되는 파라미터를 동적으로 제공하여 SQL인젝션을 방지하고 쿼리의 재사용성을 높이는 기술이다.

    import { getConnection } from 'typeorm';
    
    async function queryWithParameterBinding(age: number) {
        const result = getConnection()
        	.query('SELECT * FROM users WHERE age > :age', { age });
        return result;
    }
    




[참고 자료]
https://www.elancer.co.kr/blog/view?seq=156
https://velog.io/@juunghunz/%EB%8D%B0%EC%9D%B4%ED%84%B0%EB%B2%A0%EC%9D%B4%EC%8A%A4-SQLTIL-2021.08.26
https://gent.tistory.com/498
https://121202.tistory.com/28
https://nayoon030303.tistory.com/m/34
https://choi-records.tistory.com/entry/NestJS-TypeORM-Entity-Repository-%EA%B5%AC%EC%A1%B0NestJS-PostgresSQL-%EC%A0%81%EC%9A%A9
https://dream-and-develop.tistory.com/270

profile
노는게 제일 좋아

0개의 댓글