Node.js와 MySQL에서 Sequelize 사용하는 방법
저자는 Write for Donations 프로그램의 일환으로 Open Internet/Free Speech Fund에 기부금을 전달하기 위해 선택했습니다.
소개
Sequelize는 Node.js 기반의 객체 관계 매퍼(Object Relational Mapper)로, MySQL, MariaDB, SQLite, PostgreSQL 등 다양한 데이터베이스를 쉽게 다룰 수 있도록 돕습니다. 객체 관계 매퍼는 데이터를 객체로 표현하여 데이터베이스 기록을 처리하는 등의 기능을 수행합니다. Sequelize는 기존 데이터베이스 스키마를 새로운 버전으로 변환할 수 있는 강력한 마이그레이션 기능을 제공합니다. 전반적으로 Sequelize는 데이터베이스 동기화, 지연 로딩, 연관 관계, 트랜잭션, 데이터베이스 마이그레이션을 훌륭하게 지원하며 개발 시간을 줄이고 SQL 인젝션을 방지합니다.
이 튜토리얼에서는 로컬 개발 환경에서 MySQL과 함께 Sequelize를 설치하고 구성하는 방법을 다룹니다. 다음으로, Sequelize를 사용하여 데이터베이스와 모델을 생성하고, insert, select, delete 작업을 수행합니다. 이후에는 1:1, 1:N, N:M 관계에 대한 Sequelize 연관 관계를 생성합니다. 마지막으로 배열과 객체 대체를 위한 Sequelize 원시 쿼리를 작성합니다. 이러한 기본 사항 외에도, 이 튜토리얼에서는 환경 변수를 사용한 안전한 데이터베이스 구성, 마이그레이션을 통한 안전한 스키마 변경 관리, 그리고 문제 해결 및 성능 최적화를 위한 모범 사례도 다룹니다.
Linux-Console.net 앱 플랫폼을 사용하여 GitHub에서 프론트엔드 애플리케이션을 배포하세요. 앱의 확장은 Linux-Console.net에 맡기세요.
핵심 요점:
- Sequelize는 원시 SQL 대신 모델을 사용하여 MySQL과 쉽게 상호작용할 수 있는 방법을 제공하여 데이터베이스 작업을 더 간단하고 체계적으로 만들어줍니다.
- 데이터베이스 자격 증명을 환경 변수에 저장하면 민감한 정보를 안전하게 보호하고 버전 관리에서 제외할 수 있습니다.
- 스키마 업데이트를 위해 마이그레이션을 사용하는 것은 특히 운영 환경에서
sequelize.sync()에 의존하는 것보다 더 안전합니다. - Sequelize는 모델 간에 일대일, 일대다 및 다대다 관계를 쉽게 정의할 수 있게 해줍니다.
- 복잡한 데이터베이스 작업에서 더 많은 제어가 필요하거나 성능을 높여야 할 때는 여전히 원시 SQL 쿼리를 사용할 수 있습니다.
- Sequelize에서 가장 흔히 발생하는 문제들, 예를 들어 연결 실패나 마이그레이션 오류는 환경 변수를 확인하고, 로그를 검토하며,
EXPLAIN와SHOW PROCESSLIST같은 MySQL 도구를 사용하면 해결할 수 있습니다. - 성능은 쿼리 최적화, 적절한 인덱스 추가, 연결 풀 튜닝, 자주 접근하는 데이터 캐싱을 통해 향상시킬 수 있습니다.
- 마이그레이션을 테스트하고, 오류 처리를 추가하며, 데이터베이스 성능을 모니터링하는 것은 Sequelize 애플리케이션을 안정적이고 프로덕션 환경에 적합하게 유지하는 데 도움이 됩니다.
전제 조건
이 튜토리얼을 완료하려면 다음이 필요합니다:
- Node.js가 실행되는 개발 환경입니다. 이를 설정하려면 운영 체제에 맞는 Node.js 설치 및 로컬 개발 환경 생성 방법 튜토리얼을 따라 하세요.
- 작업 환경에 MySQL이 설치되어 있어야 합니다. 사용자 생성 단계를 포함하여 환경에 맞는 MySQL 설치 방법 튜토리얼을 따라주세요.
- JavaScript와 MySQL 쿼리에 익숙하며, 이는 'How To Code in JavaScript' 시리즈와 튜토리얼 'An Introduction to Queries in MySQL'을 통해 개발할 수 있습니다.
- 튜토리얼 '관계형 데이터베이스 이해'에서 찾아볼 수 있는 관계형 데이터베이스 개념에 대한 기본적인 이해.
이 튜토리얼은 Node.js 버전 v24.11.1과 npm 버전 11.6.2에서 테스트되었습니다.
Sequelize 설치 및 구성
이 단계에서는 Sequelize를 설치하고 MySQL 데이터베이스와의 연결을 생성합니다. 이를 위해 먼저 Node.js 애플리케이션을 생성합니다. 그런 다음 Sequelize를 설치하고, MySQL 데이터베이스를 구성하며, 간단한 애플리케이션을 개발합니다.
Sequelize 설치하기
프로젝트 폴더를 만드는 것부터 시작하세요. 이 예제에서는 hello-world를 사용할 수 있습니다. 폴더를 만든 후에는 터미널을 사용하여 해당 폴더로 이동하세요:
mkdir hello-world
cd hello-world그런 다음, 다음 명령어를 사용하여 샘플 Node.js 애플리케이션을 생성하세요:
npm init다음으로, 몇 가지 설정 질문에 답하라는 메시지가 표시됩니다. 구성에는 다음 출력을 사용하세요. 표시된 기본값을 사용하려면 ENTER를 누르고, 메인 진입점을 server.js로 설정해야 합니다. 이렇게 하면 관리하기 쉬운 프로젝트 구조가 생성됩니다.
출력은 다음과 같이 나타나며, 이는 package.json 파일을 채우게 됩니다:
{
"name": "hello-world",
"version": "1.0.0",
"description": "",
"main": "server.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
},
"author": "",
"license": "ISC"
}다음으로, 프로젝트 폴더 안에 빈 server.js 파일을 생성하세요:
touch server.js이전 단계를 따르면, 최종 폴더 구조는 다음과 같이 됩니다:
hello-world/
├─ package.json
├─ server.js
이제 다음 명령어로 Sequelize를 설치할 수 있습니다:
npm i sequelize이 업데이트 후에, package.json 파일은 이제 다음과 같이 보입니다:
{
"name": "hello-world",
"version": "1.0.0",
"description": "",
"main": "server.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1",
"start": "node server.js"
},
"author": "",
"license": "ISC",
"dependencies": {
"sequelize": "^6.37.7"
}
}dependencies 섹션에서 이제 Sequelize 의존성을 볼 수 있습니다.
프로젝트를 설정하고 Sequelize를 설치했습니다. 다음으로 연결할 샘플 데이터베이스를 생성할 것입니다.
샘플 데이터베이스 만들기
사전 요구 사항의 일부로 MySQL을 설치하고 구성했으며, 여기에는 사용자 생성이 포함되었습니다. 이제 빈 데이터베이스를 만들겠습니다.
그렇게 하려면 먼저 MySQL 인스턴스에 로그인해야 합니다. 원격으로 실행 중이라면 선호하는 도구를 사용할 수 있습니다. 로컬에서 실행 중인 MySQL 인스턴스를 사용 중이라면 다음 명령어를 사용할 수 있으며, your_username를 MySQL 사용자 이름으로 바꾸면 됩니다:
mysql -u your_username -p-u는 사용자 이름을 의미하며, 계정이 비밀번호로 보호되어 있는 경우 -p 옵션을 사용합니다.
MySQL 서버가 데이터베이스 비밀번호를 요청할 것입니다. 비밀번호를 입력하고 ENTER를 누르세요.
로그인한 후, 다음 명령어를 사용하여 hello_world_db라는 데이터베이스를 만드세요:
CREATE DATABASE hello_world_db;데이터베이스를 성공적으로 생성했는지 확인하려면 다음 명령어를 사용할 수 있습니다:
SHOW DATABASES;
당신의 출력은 이와 비슷할 것입니다:
+--------------------+
| Database |
+--------------------+
| hello_world_db |
| information_schema |
| mysql |
| performance_schema |
| sys |
+--------------------+샘플 데이터베이스를 만든 후, MySQL 서버와의 연결을 끊습니다:
mysql> QUIT이제 선택한 데이터베이스에 대한 수동 드라이버를 설치해야 합니다. Sequelize는 ORM 기능만 제공하므로 내장 데이터베이스 드라이버를 포함하지 않습니다. 따라서 선호에 따라 드라이버를 설치해야 합니다. 이를 위해 터미널을 사용해 프로젝트 디렉터리로 이동한 후 다음 명령어를 사용하여 MySQL 드라이버를 프로젝트에 설치하세요:
npm install --save mysql2이 경우, 당신은 MySQL용 드라이버를 사용하고 있습니다.
참고: 이 튜토리얼에서는 MySQL을 데이터베이스로 사용하므로, 해당 데이터베이스용 드라이버를 사용하게 됩니다. 사용 중인 데이터베이스에 따라 다음과 같이 드라이버를 수동으로 설치할 수 있습니다:
npm install --save pg pg-hstore # Postgresnpm install --save mysql2npm install --save mariadbnpm install --save sqlite3npm install --save tedious # Microsoft SQL Server
이제 샘플 데이터베이스가 있으므로 데이터베이스 연결이 가능한 첫 번째 Sequelize 애플리케이션을 만들 수 있습니다.
MySQL 데이터베이스에 연결 중
이 섹션에서는 Sequelize를 사용하여 Node.js 애플리케이션을 MySQL 데이터베이스에 연결합니다.
데이터베이스에 연결하려면 nano 또는 선호하는 코드 편집기를 사용하여 server.js 파일을 열어 편집하세요:
nano server.js여기서 애플리케이션에서 Sequelize 인스턴스를 사용하여 데이터베이스 연결을 생성할 것입니다. new Sequelize() 메서드에서는 MySQL 서버 매개변수와 데이터베이스 자격 증명을 다음과 같이 전달하며, DATABASE_USERNAME과 DATABASE_PASSWORD는 MySQL 사용자 자격 증명으로 대체합니다:
const Sequelize = require("sequelize");
const sequelize = new Sequelize(
'hello_world_db',
'DATABASE_USERNAME',
'DATABASE_PASSWORD',
{
host: 'DATABASE_HOST',
dialect: 'mysql'
}
);host는 MySQL 서버가 호스팅되는 위치를 의미하므로, 서버 URL이나 IP 주소를 제공해야 합니다. 로컬에 설치된 MySQL 서버를 사용하고 있다면 DATABASE_HOST를 localhost 또는 127.0.0.1로 값으로 대체할 수 있습니다.
마찬가지로, 원격 서버를 사용하는 경우에는 데이터베이스 연결 값을 적절한 원격 서버 정보로 변경해야 합니다.
참고: 다른 데이터베이스 서버 소프트웨어를 사용 중이라면 해당 방언(dialect) 매개변수를 적절히 변경할 수 있습니다.
다음으로, 약속 기반의 authenticate() 메서드를 호출하여 애플리케이션에 대한 데이터베이스 연결을 생성합니다. 이를 위해 다음 코드 블록을 server.js 파일에 추가하세요:
...
sequelize.authenticate().then(() => {
console.log('Connection has been established successfully.');
}).catch((error) => {
console.error('Unable to connect to the database: ', error);
});authenticate() 메서드는 데이터베이스와 연결하고 주어진 자격 증명이 올바른지 테스트하는 데 사용됩니다. 여기서 데이터베이스 연결은 기본적으로 열려 있으며 동일한 연결을 모든 쿼리에 사용할 수 있습니다. 연결을 닫아야 할 때는 이 authenticate() 호출 후 sequelize.close() 메서드를 호출하면 됩니다. Sequelize에 대해 더 알아보려면, 그들의 시작 가이드를 참조하세요.
Sequelize가 제공하는 대부분의 메서드는 비동기적입니다. 이는 비동기 코드 블록이 실행되는 동안 애플리케이션에서 다른 프로세스를 실행할 수 있다는 것을 의미합니다. 또한, 비동기 코드 블록이 성공적으로 실행된 후에는 프로세스가 끝날 때 반환되는 값인 promise를 반환합니다. 따라서 비동기 코드 블록에서는 then(), catch(), finally()를 사용하여 처리된 데이터를 반환할 수 있습니다.
이 시점에서 server.js 파일은 다음과 같이 보일 것입니다:
const Sequelize = require("sequelize");
const sequelize = new Sequelize(
'hello_world_db',
'DATABASE_USERNAME',
'DATABASE_PASSWORD',
{
host: 'DATABASE_HOST',
dialect: 'mysql'
}
);
sequelize.authenticate().then(() => {
console.log('Connection has been established successfully.');
}).catch((error) => {
console.error('Unable to connect to the database: ', error);
});파일을 저장하고 닫으세요.
프로젝트 디렉토리에서 다음 명령어를 실행하여 server.js 애플리케이션을 실행하세요:
node server.js출력은 이렇게 표시됩니다:
Connection has been established successfully!데이터베이스 연결을 성공적으로 생성했습니다.
이 단계에서는 Sequelize를 설치하고, 샘플 데이터베이스를 생성한 후, Sequelize를 사용하여 데이터베이스에 연결했습니다. 다음으로, Sequelize에서 모델을 다루게 됩니다.
Sequelize를 사용하여 데이터베이스 테이블 만들기
샘플 MySQL 데이터베이스를 생성했으므로 이제 Sequelize를 사용하여 테이블을 만들고 데이터를 채울 수 있습니다. Sequelize에서 데이터베이스 테이블은 모델이라고 합니다. 모델은 데이터베이스의 테이블을 나타내는 추상화 개념입니다. 모델은 Sequelize에 테이블 이름, 열 세부 정보, 데이터 타입과 같은 여러 가지를 정의합니다. 이번 단계에서는 책 데이터를 위한 Sequelize 모델을 생성할 것입니다.
먼저, 프로젝트 디렉토리에 book.model.js라는 새 파일을 만듭니다:
nano book.model.js이전 단계와 유사하게, 파일 상단에 DataTypes를 새로 임포트하고 데이터베이스 초기화를 위한 Sequelize 코드를 추가합니다:
const { Sequelize, DataTypes } = require("sequelize");Sequelize에는 많은 내장 데이터 타입이 포함되어 있습니다. 이러한 데이터 타입에 접근하려면 DataTypes를 import하면 됩니다. 이 튜토리얼에서는 STRING, INTEGER, DATEONLY와 같이 자주 사용되는 데이터 타입을 언급합니다. 다른 지원되는 데이터 타입에 대해 더 알고 싶다면 공식 Sequelize 문서를 참고할 수 있습니다.
그런 다음, 이전에 MySQL 데이터베이스에 연결하기 위해 사용했던 코드를 포함하고, MySQL 자격 증명을 이에 맞게 업데이트하세요:
...
const sequelize = new Sequelize(
'hello_world_db',
'DATABASE_USERNAME',
'DATABASE_PASSWORD',
{
host: 'DATABASE_HOST',
dialect: 'mysql'
}
);
sequelize.authenticate().then(() => {
console.log('Connection has been established successfully.');
}).catch((error) => {
console.error('Unable to connect to the database: ', error);
});다음으로, title, author, release_date, subject ID를 포함하는 books 모델을 생성할 것입니다. 이를 위해 다음과 같이 sequelize.define() 메서드를 사용하세요:
...
const Book = sequelize.define("books", {
title: {
type: DataTypes.STRING,
allowNull: false
},
author: {
type: DataTypes.STRING,
allowNull: false
},
release_date: {
type: DataTypes.DATEONLY,
},
subject: {
type: DataTypes.INTEGER,
}
});sequelize.define() 메서드는 데이터베이스의 테이블을 나타내는 새로운 모델을 정의합니다. 이 코드 블록은 books라는 테이블을 생성하고, title, author, release_date, subject에 따라 책 기록을 저장합니다.
이 코드에서 allowNull은 모델 컬럼 값이 null일 수 없음을 나타냅니다. 마찬가지로, 이러한 값을 설정해야 하는 경우 defaultValue: "value"를 사용할 수 있습니다.
다음으로, 데이터베이스에 book 모델을 추가할 것입니다. 이를 위해 다음과 같이 sync() 메서드를 사용합니다:
...
sequelize.sync().then(() => {
console.log('Book table created successfully!');
}).catch((error) => {
console.error('Unable to create table : ', error);
});sync() 메서드에서는 Sequelize에게 데이터베이스에 몇 가지 작업을 수행하도록 요청하고 있습니다. 이 호출을 통해 Sequelize는 자동으로 데이터베이스에 SQL 쿼리를 실행하고 테이블을 생성하며, Book table created successfully!라는 메시지를 출력합니다.
앞서 언급했듯이, sync() 메서드는 프로미스 기반 메서드이므로 오류 처리도 수행할 수 있습니다. 이 코드 블록에서는 테이블이 성공적으로 생성되었는지 확인합니다. 생성되지 않은 경우, catch 메서드를 통해 오류를 반환하고 출력에 표시합니다.
참고: 모델 동기화를 관리하려면 force 매개변수를 전달하여 테이블이 존재하지 않을 경우 새 테이블을 강제로 생성하거나, 기존 테이블을 사용할 수 있습니다. 다음은 Sequelize를 사용할 때 유용할 수 있는 몇 가지 예제입니다:
- `
model.sync()`: 테이블이 아직 존재하지 않는 경우 생성합니다. model.sync({ force: true }): 동일한 테이블이 이미 존재하면 삭제하고 새로 테이블을 생성합니다.
최종 코드는 이렇게 보일 것입니다:
const {Sequelize, DataTypes} = require("sequelize");
const sequelize = new Sequelize(
'hello_world_db',
'DATABASE_USERNAME',
'DATABASE_PASSWORD',
{
host: 'DATABASE_HOST',
dialect: 'mysql'
}
);
sequelize.authenticate().then(() => {
console.log('Connection has been established successfully.');
}).catch((error) => {
console.error('Unable to connect to the database: ', error);
});
const Book = sequelize.define("books", {
title: {
type: DataTypes.STRING,
allowNull: false
},
author: {
type: DataTypes.STRING,
allowNull: false
},
release_date: {
type: DataTypes.DATEONLY,
},
subject: {
type: DataTypes.INTEGER,
}
});
sequelize.sync().then(() => {
console.log('Book table created successfully!');
}).catch((error) => {
console.error('Unable to create table : ', error);
});파일을 저장하고 닫으세요.
다음 명령어를 사용하여 애플리케이션을 실행하세요:
node book.model.js명령줄에서 다음과 같은 출력이 표시됩니다:
Executing (default): SELECT 1+1 AS result
Executing (default): CREATE TABLE IF NOT EXISTS `books` (`id` INTEGER NOT NULL auto_increment , `title` VARCHAR(255) NOT NULL, `author` VARCHAR(255) NOT NULL, `release_date` DATE, `subject` INTEGER, `createdAt` DATETIME NOT NULL, `updatedAt` DATETIME NOT NULL, PRIMARY KEY (`id`)) ENGINE=InnoDB;
Connection has been established successfully.
Executing (default): SHOW INDEX FROM `books`
Book table created successfully!출력에서 반환 로그에 Book table created successfully! 메시지가 포함되어 있는 것을 볼 수 있습니다. 데이터베이스를 확인하여 hello_world_db 데이터베이스에 새로 생성된 books 테이블을 확인함으로써 이를 검증할 수 있습니다.
새 테이블 생성을 확인하려면 MySQL 인스턴스에 로그인하세요:
mysql -u YOUR_USERNAME -p비밀번호를 입력한 후, 샘플 데이터베이스로 변경하세요:
USE hello_world_db;그런 다음 명령어를 실행하여 테이블을 표시합니다:
SHOW TABLES;당신의 출력은 이와 비슷할 것입니다:
+---------------------------+
| Tables_in_hello_world_db |
+---------------------------+
| books |
+---------------------------+
1 row in set (0.00 sec)마지막으로, MySQL 서버 연결을 끊습니다:
mysql> QUIT당신은 book 모델 생성이 성공했음을 확인했습니다. 이 과정을 사용하면 동일한 절차를 따라 원하는 수만큼 모델을 생성할 수 있습니다.
이 단계에서는 데이터베이스에 모델을 생성하고 내장 메서드를 사용하여 모델 작업을 시작했습니다. 또한 Sequelize에서 지원하는 데이터 유형을 사용하여 모델을 정의했습니다. 다음으로는 기본적인 모델 쿼리를 사용하게 됩니다.
데이터베이스 쿼리를 위해 Sequelize 사용하기
이 단계에서는 Sequelize 내장 쿼리를 사용하여 삽입, 선택, 조건절이 있는 선택 및 삭제를 수행하게 됩니다.
새 기록 삽입
이전 단계에서는 데이터베이스 안에 book 모델을 생성했습니다. 이 섹션에서는 이 모델에 데이터를 삽입할 것입니다.
시작하려면, 이전 단계에서 book.model.js의 내용을 복사하세요. 쿼리 로직을 처리할 새 파일 book.controller.js를 만드세요. book.model.js의 코드를 book.controller.js에 추가하세요.
book.controller.js에서 sync() 메서드를 찾으세요. sync() 메서드 안에 다음 하이라이트된 줄을 추가하세요:
...
sequelize.sync().then(() => {
console.log('Book table created successfully!');
Book.create({
title: "Clean Code",
author: "Robert Cecil Martin",
release_date: "2021-12-14",
subject: 3
}).then(res => {
console.log(res)
}).catch((error) => {
console.error('Failed to create a new record : ', error);
});
}).catch((error) => {
console.error('Unable to create table : ', error);
});여기에서는 이미 생성한 books 모델에 새로운 도서 레코드를 추가합니다. sync() 메서드는 이전에 생성한 모델에 새로운 레코드를 추가하는 것을 지원합니다. sync() 메서드가 성공적으로 실행되면 then() 메서드가 실행됩니다. then() 메서드 내부에서 create() 메서드를 호출하여 모델에 새로운 레코드를 삽입합니다.
데이터베이스에 추가할 데이터를 객체 형태로 전달하려면 create() 메서드를 사용합니다. 코드에서 강조된 부분은 기존의 books 테이블에 새 레코드를 삽입합니다. 이 예제에서는 Robert Cecil Martin의 Clean Code를 추가하며, 해당 책은 subject ID가 3로 분류되어 있습니다. 다른 책의 정보로 업데이트된 동일한 코드를 사용하여 데이터베이스에 새 레코드를 추가할 수 있습니다.
파일을 저장하고 닫으세요.
다음 명령어를 사용하여 애플리케이션을 실행하세요:
node book.controller.js출력 결과는 다음과 유사하게 보일 것입니다:
books {
dataValues:
{ id: 1,
title: 'Clean Code',
author: 'Robert Cecil Martin',
release_date: '2021-12-14',
subject: 3,
updatedAt: 2021-12-14T10:12:16.644Z,
...
}데이터베이스에서 생성한 모델에 새 레코드를 추가했습니다. 동일한 방법으로 여러 레코드를 계속 추가할 수 있습니다.
모든 레코드 선택
이 섹션에서는 findAll() 메서드를 사용하여 데이터베이스에서 모든 도서 기록을 선택하고 가져옵니다. 이를 위해 먼저 book.controller.js를 열고 이전의 Book.create() 메서드를 제거하세요. 그런 다음 sync() 메서드에 다음과 같이 Book.findAll() 메서드를 추가합니다:
...
sequelize.sync().then(() => {
Book.findAll().then(res => {
console.log(res)
}).catch((error) => {
console.error('Failed to retrieve data : ', error);
});
}).catch((error) => {
console.error('Unable to create table : ', error);
});
...파일을 저장하고 닫으세요.
다음으로, 다음 명령어를 사용하여 애플리케이션을 다시 실행하세요:
node book.controller.js출력 결과는 다음과 유사하게 보일 것입니다:
[
books {
dataValues: {
id: 1,
title: 'Clean Code',
author: 'Robert Cecil Martin',
release_date: '2020-01-01',
subject: 3,
createdAt: 2021-02-22T09:13:55.000Z,
updatedAt: 2021-02-22T09:13:55.000Z
},
_previousDataValues: {
id: 1,
title: 'Clean Code',
author: 'Robert Cecil Martin',
release_date: '2020-01-01',
subject: 3,
createdAt: 2021-02-22T09:13:55.000Z,
updatedAt: 2021-02-22T09:13:55.000Z
},
...
]출력에는 모든 책 데이터가 배열 객체로 포함됩니다. 데이터베이스에서 모든 책 데이터를 반환하기 위해 Sequelize findAll() 메서드를 성공적으로 사용했습니다.
where 절을 사용한 선택
이 섹션에서는 where 절을 사용하여 조건에 맞는 값을 선택합니다. where 절은 데이터를 가져올 때 조건을 지정하는 데 사용됩니다. 이 튜토리얼에서는 findOne() 메서드를 사용하여 특정 레코드 ID로 데이터베이스에서 책을 가져오게 됩니다.
그렇게 하려면, book.controller.js 파일을 열어 편집하고, findAll() 메서드를 삭제한 후 다음 줄을 추가하세요:
...
sequelize.sync().then(() => {
Book.findOne({
where: {
id: 1
}
}).then(res => {
console.log(res)
}).catch((error) => {
console.error('Failed to retrieve data : ', error);
});
}).catch((error) => {
console.error('Unable to create table : ', error);
});여기에서는 where 옵션과 함께 findOne() 메서드를 사용하여 데이터베이스에서 특정 도서 기록을 선택합니다. 이 예제에서는 id가 1인 도서 데이터를 가져오고 있습니다.
파일을 저장하고 닫으세요.
다음으로, 애플리케이션을 실행하세요:
node book.controller.js출력 결과는 다음과 유사하게 보일 것입니다:
books {
dataValues: {
id: 1,
title: 'Clean Code',
author: 'Robert Cecil Martin',
release_date: '2020-01-01',
subject: 'Science',
createdAt: 2021-02-22T09:13:55.000Z,
updatedAt: 2021-02-22T09:13:55.000Z
},
...
}Sequelize 모델에서 데이터를 가져오기 위해 where 절을 성공적으로 사용했습니다. 데이터베이스 애플리케이션에서 조건에 맞는 데이터를 가져오기 위해 where 절을 사용할 수 있습니다.
레코드 삭제
데이터베이스 모델에서 특정 레코드를 삭제하려면 where 옵션과 함께 destroy() 메서드를 사용합니다. 이를 위해 book.controller.js를 열고, findOne() 메서드를 제거한 후 다음에 강조된 코드를 추가하세요:
...
sequelize.sync().then(() => {
Book.destroy({
where: {
id: 2
}
}).then(() => {
console.log("Successfully deleted record.")
}).catch((error) => {
console.error('Failed to delete record : ', error);
});
}).catch((error) => {
console.error('Unable to create table : ', error);
});여기서는 destroy() 메서드와 where 옵션을 사용하고 제거할 책의 id를 전달하여 데이터베이스에서 책 기록을 삭제합니다. id가 2인 책 기록을 삭제하려고 합니다.
파일을 저장하고 닫으세요.
다음으로, 애플리케이션을 실행하세요:
node book.controller.js출력은 다음과 같이 보일 것입니다:
Successfully deleted record.기록이 삭제되었습니다.
이 단계에서는 데이터베이스 모델과 모델 쿼리를 실험해 보았습니다. 데이터베이스를 초기화하고, 모델을 생성하고, 레코드를 삽입하고, 레코드를 조회하며, where 절을 사용하여 조건에 맞는 레코드를 조회하고, 선택된 레코드를 삭제했습니다. Sequelize에 대한 이러한 지식을 바탕으로 이제 Sequelize에서 연관 관계를 생성할 수 있습니다. 그 후에는 Sequelize 모델을 사용하여 다양한 관계를 정의하고 다룰 수 있게 됩니다.
Sequelize를 사용하여 연관 관계 만들기
이 단계에서는 Sequelize가 지원하는 표준 연관 유형인 일대일, 일대다, 다대다 연관을 사용합니다. 학생, 과정, 학년과 관련된 샘플 데이터를 사용하게 됩니다.
Sequelize는 다음과 같은 데이터베이스 관계를 기반으로 연관 유형을 사용합니다:
일대일 관계: 일대일 관계는 한 테이블의 레코드가 다른 테이블의 정확히 하나의 레코드와 연결되어 있음을 의미합니다. Sequelize 관점에서, 이러한 유형의 관계를 만들기 위해
belongsTo()와hasOne()연관을 사용할 수 있습니다.일대다 관계: 일대다 관계는 한 테이블의 레코드가 다른 테이블의 여러 레코드와 연결되어 있는 것을 의미합니다. Sequelize를 사용하면
hasMany()연관 메서드를 사용하여 이러한 유형의 관계를 만들 수 있습니다.다대다 관계: 다대다 관계는 한 테이블의 여러 레코드가 다른 테이블의 여러 레코드와 연관되어 있음을 의미합니다. Sequelize에서는
belongsToMany()연관을 사용하여 이러한 유형의 관계를 생성할 수 있습니다.
이러한 연관 관계를 만들기 전에, 먼저 student_db라는 새 데이터베이스를 생성하고 학생, 강좌, 학년을 위한 새 모델과 일부 샘플 데이터를 추가합니다.
데이터베이스를 생성하려면, MySQL에 로그인하고 student_db라는 데이터베이스를 생성하는 과정은 Sequelize 설치 및 구성과 동일한 절차를 따르세요. 새 데이터베이스가 생성되면 MySQL에서 로그아웃하세요. 다음으로, 데이터베이스 연관 관계를 생성하기 시작합니다.
belongsTo()로 일대일 관계 만들기
이 섹션에서는 Sequelize 모델을 사용하여 일대일 관계를 생성합니다. 한 학생의 세부 정보와 해당 학년 정보를 가져오고 싶다고 상상해보세요. 한 학생은 하나의 학년만 가질 수 있기 때문에, 이러한 종류의 연관 관계는 일대일 관계이며, belongsTo() 메서드를 사용할 수 있습니다.
참고: belongsTo()와 hasOne()에는 차이가 있습니다. belongsTo()는 소스 테이블에 foreignKey를 추가하는 반면, hasOne()은 대상 테이블에 추가합니다. 어쨌든 두 관계를 동시에 사용하면 Sequelize 양방향 일대일 관계로 작동합니다.
belongsTo() 메서드를 사용하면 두 Sequelize 모델 간에 일대일 관계를 만들 수 있습니다. 이 예제에서는 Student와 Grade 모델을 사용하고 있습니다.
one_to_one.js라는 새 파일을 만드세요. 이전 섹션인 MySQL 데이터베이스 연결과 마찬가지로, 파일 상단에 데이터베이스에 연결하고 MySQL 사용자를 인증하는 코드를 포함하세요. 필요에 따라 MySQL 자격 증명을 업데이트하는 것을 잊지 마세요:
const { Sequelize, DataTypes } = require("sequelize");
const sequelize = new Sequelize(
'student_db',
'DATABASE_USERNAME',
'DATABASE_PASSWORD',
{
host: 'DATABASE_HOST',
dialect: 'mysql'
}
);
sequelize.authenticate().then(() => {
console.log('Connection has been established successfully.');
}).catch((error) => {
console.error('Unable to connect to the database: ', error);
});이 섹션에서는 새로운 student_db 데이터베이스에 세 개의 모델을 생성합니다: Student, Grade, Course. 먼저 Student와 Grade 모델부터 생성합니다. 이후 이 단계에서 Courses 모델을 생성하게 됩니다.
Student 모델의 경우, 다음 코드 블록을 one_to_one.js에 추가하세요:
...
const Student = sequelize.define("students", {
student_id: {
type: DataTypes.UUID,
defaultValue: DataTypes.UUIDV4,
primaryKey: true,
},
name: {
type: DataTypes.STRING,
allowNull: false
}
});이 학생 모델에는 두 개의 열이 있습니다: student_id와 name.
다음으로, Grade 모델에 대한 코드 블록을 추가하세요:
...
const Grade = sequelize.define("grades", {
grade: {
type: DataTypes.INTEGER,
allowNull: false
}
});Grade 모델에는 grade 열이 포함되어 있습니다.
연관 관계를 보여주기 위해 데이터베이스에 샘플 데이터를 추가해야 합니다. 이를 위해 bulk() 메서드를 사용합니다. 데이터를 한 행씩 삽입하는 대신, bulkCreate() 메서드를 사용하면 여러 행을 한 번에 데이터베이스 모델에 삽입할 수 있습니다.
이제 아래와 같이 데이터베이스의 각 모델에 Grade와 Student 데이터를 가져오세요:
...
const grade_data = [{grade : 9}, {grade : 10}, {grade : 11}]
const student_data = [
{name : "John Baker", gradeId: 2},
{name : "Max Butler", gradeId: 1},
{name : "Ryan Fisher", gradeId: 3},
{name : "Robert Gray", gradeId: 2},
{name : "Sam Lewis", gradeId: 1}
]
sequelize.sync({ force: true }).then(() => {
Grade.bulkCreate(grade_data, { validate: true }).then(() => {
Student.bulkCreate(student_data, { validate: true }).then(() => {
…
}).catch((err) => { console.log(err); });
}).catch((err) => { console.log(err); });
}).catch((error) => {
console.error('Unable to create the table : ', error);
});여기에서 샘플 데이터를 제공하고 데이터를 Student 및 Grade 모델에 삽입합니다. 데이터베이스, 모델 및 샘플 데이터가 준비되면 연관 관계를 생성할 준비가 완료됩니다.
one-to-one.js에서 student_data 블록 아래에 다음 줄을 추가하세요:
...
Student.belongsTo(Grade);다음으로, 연관 관계가 제대로 작동하는지 확인해야 합니다. 이를 위해 findAll() 메서드 안에 include 매개변수를 전달하여 모든 학생의 데이터와 연관된 학년 정보를 가져올 수 있습니다.
학생 학년을 가져와야 하므로 모델로 Grade를 전달합니다. sequelize.sync() 메서드에서는 다음과 같이 표시된 줄을 추가하세요:
...
sequelize.sync({ force: true }).then(() => {
Grade.bulkCreate(grade_data, { validate: true }).then(() => {
Student.bulkCreate(student_data, { validate: true }).then(() => {
Student.findAll({
include: [{
model: Grade
}]
}).then(result => {
console.log(result)
}).catch((error) => {
console.error('Failed to retrieve data : ', error);
});
}).catch((err) => { console.log(err); });
}).catch((err) => { console.log(err); });
}).catch((error) => {
console.error('Unable to create the table : ', error);
});전체 코드는 다음과 같습니다:
const {Sequelize, DataTypes} = require("sequelize");
const sequelize = new Sequelize(
'student_db',
'DATABASE_USERNAME',
'DATABASE_PASSWORD',
{
host: 'DATABASE_HOST',
dialect: 'mysql'
}
);
sequelize.authenticate().then(() => {
console.log('Connection has been established successfully.');
}).catch((error) => {
console.error('Unable to connect to the database: ', error);
});
const Student = sequelize.define("students", {
student_id: {
type: DataTypes.UUID,
defaultValue: DataTypes.UUIDV4,
primaryKey: true,
},
name: {
type: DataTypes.STRING,
allowNull: false
}
});
const Grade = sequelize.define("grades", {
grade: {
type: DataTypes.INTEGER,
allowNull: false
}
});
const grade_data = [{grade : 9}, {grade : 10}, {grade : 11}]
const student_data = [
{name : "John Baker", gradeId: 2},
{name : "Max Butler", gradeId: 1},
{name : "Ryan Fisher", gradeId: 3},
{name : "Robert Gray", gradeId: 2},
{name : "Sam Lewis", gradeId: 1}
]
// One-To-One association
Student.belongsTo(Grade);
sequelize.sync({ force: true }).then(() => {
Grade.bulkCreate(grade_data, { validate: true }).then(() => {
Student.bulkCreate(student_data, { validate: true }).then(() => {
Student.findAll({
include: [{
model: Grade
}]
}).then(result => {
console.log(result)
}).catch((error) => {
console.error('Failed to retrieve data : ', error);
});
}).catch((err) => { console.log(err); });
}).catch((err) => { console.log(err); });
}).catch((error) => {
console.error('Unable to create the table : ', error);
});파일을 저장하고 닫으세요.
다음 명령어를 사용하여 파일을 실행하세요:
node one_to_one.js출력 내용이 길며, 모든 학생들의 학년별 데이터가 표시됩니다. 다음은 학생 데이터 출력 예시입니다:
students {
dataValues:
{ student_id: '3e786a8f-7f27-4c59-8e9c-a8c606892288',
name: 'Sam Lewis',
createdAt: 2021-12-16T08:49:38.000Z,
updatedAt: 2021-12-16T08:49:38.000Z,
gradeId: 1,
grade: [grades] },
_previousDataValues:
...사용하는 명령줄 도구에 따라 출력이 확장된 보기로 표시될 수도 있고 그렇지 않을 수도 있습니다. 확장된 보기인 경우, grade 객체가 확장된 형태로 출력됩니다.
이 섹션에서는 Student.belongsTo(Grade); 메서드 호출을 사용하여 일대일 관계를 생성하고, 생성한 연관 관계에 따라 세부 정보를 가져왔습니다.
hasMany()를 사용하여 일대다 관계 만들기
이 섹션에서는 Sequelize 모델을 사용하여 일대다 관계를 생성합니다. 선택한 학년과 관련된 모든 학생을 가져오고 싶다고 가정해봅니다. 특정 학년에는 여러 명의 학생이 있을 수 있으므로, 이는 일대다 관계입니다.
시작하려면 one_to_one.js의 내용을 one_to_many.js라는 새 파일에 복사하세요. one_to_many.js에서 student_data 블록 이후의 줄을 제거합니다. 그러면 one_to_many.js 파일은 다음과 같이 보일 것입니다:
const {Sequelize, DataTypes} = require("sequelize");
const sequelize = new Sequelize(
'student_db',
'DATABASE_USERNAME',
'DATABASE_PASSWORD',
{
host: 'DATABASE_HOST',
dialect: 'mysql'
}
);
sequelize.authenticate().then(() => {
console.log('Connection has been established successfully.');
}).catch((error) => {
console.error('Unable to connect to the database: ', error);
});
const Student = sequelize.define("students", {
student_id: {
type: DataTypes.UUID,
defaultValue: DataTypes.UUIDV4,
primaryKey: true,
},
name: {
type: DataTypes.STRING,
allowNull: false
}
});
const Grade = sequelize.define("grades", {
grade: {
type: DataTypes.INTEGER,
allowNull: false
}
});
const grade_data = [ {grade : 9}, {grade : 10}, {grade : 11}]
const student_data = [
{name : "John Baker", gradeId: 2},
{name : "Max Butler", gradeId: 1},
{name : "Ryan Fisher", gradeId: 3},
{name : "Robert Gray", gradeId: 2},
{name : "Sam Lewis", gradeId: 1}
]student_data 블록 후에, hasMany() 메서드를 사용하여 새로운 관계를 생성하세요:
...
Grade.hasMany(Student)hasMany() 메서드는 두 Sequelize 모델 간에 일대다 관계를 생성할 수 있게 해줍니다. 여기서, Grade와 Student 모델을 사용하고 있습니다.
다음으로, hasMany() 라인 아래에 findAll() 메서드와 함께 sequelize.sync() 메서드를 추가하세요:
...
sequelize.sync({ force: true }).then(() => {
Grade.bulkCreate(grade_data, { validate: true }).then(() => {
Student.bulkCreate(student_data, { validate: true }).then(() => {
Grade.findAll({
where: {
grade: 9
},
include: [{
model: Student
}]
}).then(result => {
console.dir(result, { depth: 5 });
}).catch((error) => {
console.error('Failed to retrieve data : ', error);
});
}).catch((err) => { console.log(err); });
}).catch((err) => { console.log(err); });
}).catch((error) => {
console.error('Unable to create table : ', error);
});여기서는 특정 학년의 모든 학생, 이 경우에는 9학년 모든 학생에 접근하려고 하고 있습니다. 또한 include 옵션에 Student 모델을 추가했습니다.
전체 코드는 다음과 같습니다:
const {Sequelize, DataTypes} = require("sequelize");
const sequelize = new Sequelize(
'student_db',
'DATABASE_USERNAME',
'DATABASE_PASSWORD',
{
host: 'DATABASE_HOST',
dialect: 'mysql'
}
);
sequelize.authenticate().then(() => {
console.log('Connection has been established successfully.');
}).catch((error) => {
console.error('Unable to connect to the database: ', error);
});
const Student = sequelize.define("students", {
student_id: {
type: DataTypes.UUID,
defaultValue: DataTypes.UUIDV4,
primaryKey: true,
},
name: {
type: DataTypes.STRING,
allowNull: false
}
});
const Grade = sequelize.define("grades", {
grade: {
type: DataTypes.INTEGER,
allowNull: false
}
});
const grade_data = [ {grade : 9}, {grade : 10}, {grade : 11}]
const student_data = [
{name : "John Baker", gradeId: 2},
{name : "Max Butler", gradeId: 1},
{name : "Ryan Fisher", gradeId: 3},
{name : "Robert Gray", gradeId: 2},
{name : "Sam Lewis", gradeId: 1}
]
// One-To-Many relationship
Grade.hasMany(Student);
sequelize.sync({ force: true }).then(() => {
Grade.bulkCreate(grade_data, { validate: true }).then(() => {
Student.bulkCreate(student_data, { validate: true }).then(() => {
Grade.findAll({
where: {
grade: 9
},
include: [{
model: Student
}]
}).then(result => {
console.dir(result, { depth: 5 });
}).catch((error) => {
console.error('Failed to retrieve data : ', error);
});
}).catch((err) => { console.log(err); });
}).catch((err) => { console.log(err); });
}).catch((error) => {
console.error('Unable to create table : ', error);
});파일을 저장하고 닫으세요.
다음 명령어로 파일을 실행하세요:
node one_to_many.js출력은 다음과 유사하게 나타납니다. 꽤 길겠지만, 9학년의 모든 학생이 다음과 같이 반환됩니다:
[ grades {
dataValues:
{ id: 1,
grade: 9,
createdAt: 2021-12-20T05:12:31.000Z,
updatedAt: 2021-12-20T05:12:31.000Z,
students:
[ students {
dataValues:
{ student_id: '8a648756-4e22-4bc0-8227-f590335f9965',
name: 'Sam Lewis',
createdAt: 2021-12-20T05:12:31.000Z,
updatedAt: 2021-12-20T05:12:31.000Z,
gradeId: 1 },
...
students {
dataValues:
{ student_id: 'f0304585-91e5-4efc-bdca-501b3dc77ee5',
name: 'Max Butler',
createdAt: 2021-12-20T05:12:31.000Z,
updatedAt: 2021-12-20T05:12:31.000Z,
gradeId: 1 },
...이 섹션에서는 Grade.hasMany(Student); 메서드 호출을 사용하여 일대다 관계를 생성했습니다. 출력에서는 생성한 연관 관계에 따라 세부 정보를 가져왔습니다.
belongsToMany()로 다대다 관계 만들기
이 섹션에서는 Sequelize 모델을 사용하여 다대다 관계를 생성합니다. 예를 들어, 학생들이 여러 강좌에 등록되는 상황을 상상해보세요. 한 명의 학생이 여러 강좌에 등록할 수 있고, 하나의 강좌에 여러 학생이 있을 수 있습니다. 이것이 다대다 관계입니다. Sequelize를 사용하여 이를 구현하기 위해 Student, Course, StudentCourse 모델과 belongsToMany() 메서드를 사용할 것입니다.
시작하려면 many_to_many.js라는 파일을 만들고 데이터베이스 초기화 및 인증 코드 블록을 다음과 같이 추가하세요. (이전 one_to_many.js 예제의 코드 블록을 재사용할 수 있습니다.) 필요에 따라 강조 표시된 데이터베이스 연결 값을 업데이트해야 합니다.
const {Sequelize, DataTypes} = require("sequelize");
const sequelize = new Sequelize(
'student_db',
'DATABASE_USERNAME',
'DATABASE_PASSWORD',
{
host: 'DATABASE_HOST',
dialect: 'mysql'
}
);
sequelize.authenticate().then(() => {
console.log('Connection has been established successfully.');
}).catch((error) => {
console.error('Unable to connect to the database: ', error);
});다음으로, 다대다 관계를 위한 데이터베이스 모델인 Student와 Course를 생성할 것입니다. 그런 다음 이 모델들에 샘플 데이터를 추가할 것입니다.
...
const Student = sequelize.define("students", {
student_id: {
type: DataTypes.UUID,
defaultValue: DataTypes.UUIDV4,
},
name: {
type: DataTypes.STRING,
allowNull: false
}
});
const Course = sequelize.define("courses", {
course_name: {
type: DataTypes.STRING,
allowNull: false
}
});
const StudentCourse = sequelize.define('StudentCourse', {
id: {
type: DataTypes.INTEGER,
primaryKey: true,
autoIncrement: true,
allowNull: false
}
});
const course_data = [
{course_name : "Science"},
{course_name : "Maths"},
{course_name : "History"}
]
const student_data = [
{name : "John Baker", courseId: 2},
{name : "Max Butler", courseId: 1},
{name : "Ryan Fisher", courseId: 3},
{name : "Robert Gray", courseId: 2},
{name : "Sam Lewis", courseId: 1}
]
const student_course_data = [
{studentId : 1, courseId: 1},
{studentId : 2, courseId: 1},
{studentId : 2, courseId: 3},
{studentId : 3, courseId: 2},
{studentId : 1, courseId: 2},
]여기에서는 Student와 Course 모델을 생성하고 일부 샘플 데이터를 제공합니다. 또한 이 관계 유형에 따라 학생을 검색하는 데 사용할 courseID를 설정합니다.
마지막으로, Student와 Course 간의 관계 데이터를 관리하는 새로운 모델 StudentCourse를 정의했습니다. 이 예제에서는 studentId 1이 courseId 1과 courseId 2에 등록되어 있습니다.
데이터베이스 초기화를 완료하고 샘플 데이터를 데이터베이스에 추가했습니다. 다음으로, 다음과 같이 belongsToMany() 메서드를 사용하여 다대다 관계를 생성하세요:
...
Course.belongsToMany(Student, { through: 'StudentCourse'})
Student.belongsToMany(Course, { through: 'StudentCourse'})belongsToMany() 메서드 내에서, 구성 옵션으로 모델 이름과 함께 through 구성을 전달합니다. 이 경우에는 StudentCourse입니다. 이것이 다대다 관계를 관리하는 테이블입니다.
마지막으로, 연관 관계가 제대로 작동하는지 확인하려면 관련된 학생들과 함께 모든 과정 데이터를 가져와 확인할 수 있습니다. 이는 findAll() 메서드 안에 include 매개변수를 전달하여 수행할 수 있습니다. 다음 줄을 many_to_many.js에 추가하세요:
...
sequelize.sync({ force: true }).then(() => {
Course.bulkCreate(course_data, { validate: true }).then(() => {
Student.bulkCreate(student_data, { validate: true }).then(() => {
StudentCourse.bulkCreate(student_course_data, { validate: true }).then(() => {
Course.findAll({
include: {
model: Student,
},
}).then(result => {
console.log(result);
}).catch((error) => {
console.error('Failed to retrieve data : ', error);
});
}).catch((error) => {
console.log(error);
});
}).catch((error) => {
console.log(error);
});
}).catch((error) => {
console.log(error);
});
}).catch((error) => {
console.error('Unable to create table : ', error);
});전체 코드는 다음과 같습니다:
const {Sequelize, DataTypes} = require("sequelize");
const sequelize = new Sequelize(
'student_db',
'DATABASE_USERNAME',
'DATABASE_PASSWORD',
{
host: 'DATABASE_HOST',
dialect: 'mysql'
}
);
sequelize.authenticate().then(() => {
console.log('Connection has been established successfully.');
}).catch((error) => {
console.error('Unable to connect to the database: ', error);
});
const Student = sequelize.define("students", {
student_id: {
type: DataTypes.UUID,
defaultValue: DataTypes.UUIDV4,
},
name: {
type: DataTypes.STRING,
allowNull: false
}
});
const Course = sequelize.define("courses", {
course_name: {
type: DataTypes.STRING,
allowNull: false
}
});
const StudentCourse = sequelize.define('StudentCourse', {
id: {
type: DataTypes.INTEGER,
primaryKey: true,
autoIncrement: true,
allowNull: false
}
});
const course_data = [
{course_name : "Science"},
{course_name : "Maths"},
{course_name : "History"}
]
const student_data = [
{name : "John Baker", courseId: 2},
{name : "Max Butler", courseId: 1},
{name : "Ryan Fisher", courseId: 3},
{name : "Robert Gray", courseId: 2},
{name : "Sam Lewis", courseId: 1}
]
const student_course_data = [
{studentId : 1, courseId: 1},
{studentId : 2, courseId: 1},
{studentId : 2, courseId: 3},
{studentId : 3, courseId: 2},
{studentId : 1, courseId: 2},
]
Course.belongsToMany(Student, { through: 'StudentCourse'})
Student.belongsToMany(Course, { through: 'StudentCourse'})
sequelize.sync({ force: true }).then(() => {
Course.bulkCreate(course_data, { validate: true }).then(() => {
Student.bulkCreate(student_data, { validate: true }).then(() => {
StudentCourse.bulkCreate(student_course_data, { validate: true }).then(() => {
Course.findAll({
include: {
model: Student,
},
}).then(result => {
console.log(result);
}).catch((error) => {
console.error('Failed to retrieve data : ', error);
});
}).catch((error) => {
console.log(error);
});
}).catch((error) => {
console.log(error);
});
}).catch((error) => {
console.log(error);
});
}).catch((error) => {
console.error('Unable to create table : ', error);
});파일을 저장하고 닫으세요.
다음 명령어를 사용하여 파일을 실행하세요:
node many_to_many.js출력은 길겠지만, 다음과 비슷하게 보일 것입니다:
[ courses {
dataValues:
{ id: 1,
course_name: 'Science',
createdAt: 2022-05-11T04:27:37.000Z,
updatedAt: 2022-05-11T04:27:37.000Z,
students: [Array] },
_previousDataValues:
{ id: 1,
course_name: 'Science',
createdAt: 2022-05-11T04:27:37.000Z,
updatedAt: 2022-05-11T04:27:37.000Z,
students: [Array] },
_changed: Set {},
_options:
{ isNewRecord: false,
_schema: null,
_schemaDelimiter: '',
include: [Array],
includeNames: [Array],
includeMap: [Object],
includeValidated: true,
attributes: [Array],
raw: true },
isNewRecord: false,
students: [ [students], [students] ] },
courses {
dataValues:
{ id: 2,
course_name: 'Maths',
createdAt: 2022-05-11T04:27:37.000Z,
updatedAt: 2022-05-11T04:27:37.000Z,
students: [Array] },
_previousDataValues:
...이 출력에서 볼 수 있듯이, 관련된 학생들이 있는 강좌들이 조회되었습니다. courses 블록 안에서는 각 강좌를 나타내는 개별 id 값을 볼 수 있습니다. 예를 들어, id: 1은 과학 수업의 course_name: Science와 연결되어 있으며, id: 2는 수학 수업이고, 이렇게 계속됩니다.
데이터베이스에서 삽입한 샘플 데이터와 함께 생성된 세 개의 테이블을 확인할 수 있습니다.
이 단계에서는 Sequelize를 사용하여 일대일, 일대다, 다대다 연관 관계를 생성했습니다. 다음으로는 원시 쿼리를 다루게 됩니다.
원시 쿼리 작업하기
이 단계에서는 Sequelize에서 원시 쿼리를 다루게 됩니다. 이전 단계에서는 데이터를 삽입하고 선택하기 위해 insert() 및 findAll()과 같은 Sequelize 내장 메서드를 사용했습니다. 이러한 메서드들이 쿼리를 작성할 때 특정 패턴을 따른다는 것을 눈치챘을 수도 있습니다. 그러나 원시 쿼리를 사용하면 Sequelize 내장 메서드와 패턴을 신경 쓸 필요가 없습니다. SQL 쿼리에 대한 지식을 활용하여, 간단한 쿼리부터 더 고급 쿼리까지 Sequelize에서 다양한 쿼리를 수행할 수 있습니다.
다음은 특정 테이블에서 모든 값을 선택하고, 조건에 따라 선택된 값을 삭제하며, 주어진 값으로 테이블을 업데이트하는 작업을 수행하는 원시 쿼리의 예입니다.
SELECT * FROM table_name;
DELETE FROM table_name WHERE condition;
UPDATE table_name SET y = 42 WHERE x = 12;Sequelize에서 원시 쿼리는 주로 두 가지 방법, 즉 배열 치환과 객체 치환으로 사용할 수 있습니다. SQL 쿼리에 값을 전달할 때는 배열이나 객체 중 하나를 사용하여 치환할 수 있습니다.
원시 쿼리를 작성하기 전에 먼저 샘플 데이터베이스에 학생 데이터를 제공해야 합니다. 이전 섹션인 샘플 데이터베이스 만들기를 따라 MySQL에 로그인한 후, sample_student_db라는 데이터베이스를 생성하고 MySQL에서 로그아웃합니다.
다음으로, 원시 쿼리를 사용하기 위해 일부 원시 데이터를 추가할 것입니다. add_student_records.js라는 새 파일을 만들고, 앞서 논의한 Sequelize 메서드인 authenticate(), sync(), bulkCreate()를 포함한 다음 코드 블록을 추가하세요.
const {Sequelize, DataTypes} = require("sequelize");
const sequelize = new Sequelize(
'sample_student_db',
'DATABASE_USERNAME',
'DATABASE_PASSWORD',
{
host: 'DATABASE_HOST',
dialect: 'mysql'
}
);
sequelize.authenticate().then(() => {
console.log('Connection has been established successfully.');
}).catch((error) => {
console.error('Unable to connect to the database: ', error);
});
const Student = sequelize.define("students", {
student_id: {
type: DataTypes.UUID,
defaultValue: DataTypes.UUIDV4,
primaryKey: true,
},
name: {
type: DataTypes.STRING,
allowNull: false
}
});
const student_data = [
{name : "John Baker"},
{name : "Max Butler"},
{name : "Ryan Fisher"},
{name : "Robert Gray"},
{name : "Sam Lewis"}
]
sequelize.sync({ force: true }).then(() => {
Student.bulkCreate(student_data, { validate: true }).then((result) => {
console.log(result);
}).catch((error) => {
console.log(error);
});
}).catch((error) => {
console.error('Unable to create table : ', error);
});여기에서 데이터베이스 연결을 시작하고, 모델을 생성하며, 새 데이터베이스에 몇 개의 학생 기록을 삽입합니다.
파일을 저장하고 닫으세요.
다음으로, 다음 명령어를 사용하여 이 스크립트를 실행하세요:
node add_student_records.js출력은 다음과 유사한 형태가 될 것입니다. 꽤 길겠지만, 삽입한 모든 학생 기록이 다음과 같이 반환됩니다. student_id는 자동 생성된 UUID(범용 고유 식별자) 값이므로 사용자에 따라 달라질 수 있습니다.
Executing (default): SELECT 1+1 AS result
Executing (default): DROP TABLE IF EXISTS `students`;
Connection has been established successfully.
Executing (default): DROP TABLE IF EXISTS `students`;
Executing (default): CREATE TABLE IF NOT EXISTS `students` (`student_id` CHAR(36) BINARY , `name` VARCHAR(255) NOT NULL, `createdAt` DATETIME NOT NULL, `updatedAt` DATETIME NOT NULL, PRIMARY KEY (`student_id`)) ENGINE=InnoDB;
Executing (default): SHOW INDEX FROM `students`
Executing (default): INSERT INTO `students` (`student_id`,`name`,`createdAt`,`updatedAt`) VALUES ('45d1f26c-ba76-431f-ac5f-f41282351710','John Baker','2022-06-03 07:27:49','2022-06-03 07:27:49'),('1cb4e34d-bfcf-4a97-9624-e400b9a1a5f2','Max Butler','2022-06-03 07:27:49','2022-06-03 07:27:49'),('954c576b-ba1c-4dbc-a5c6-8eaf22bbbb04','Ryan Fisher','2022-06-03 07:27:49','2022-06-03 07:27:49'),('e0f15cd3-0025-4032-bfe8-774e38e14c5f','Robert Gray','2022-06-03 07:27:49','2022-06-03 07:27:49'),('826a0ec9-edd0-443f-bb12-068235806659','Sam Lewis','2022-06-03 07:27:49','2022-06-03 07:27:49');
[
students {
dataValues: {
student_id: '45d1f26c-ba76-431f-ac5f-f41282351710',
name: 'John Baker',
createdAt: 2022-06-03T07:27:49.453Z,
updatedAt: 2022-06-03T07:27:49.453Z
},
_previousDataValues: {
name: 'John Baker',
student_id: '45d1f26c-ba76-431f-ac5f-f41282351710',
createdAt: 2022-06-03T07:27:49.453Z,
updatedAt: 2022-06-03T07:27:49.453Z
},
…다음 섹션에서는 위 코드 블록에 있는 student_id 출력 중 하나를 사용하여 원시 쿼리를 적용하게 됩니다. 다음 섹션에서 query() 메서드를 사용하여 배열과 객체를 대체할 때 사용할 수 있도록 복사해 두세요.
배열 교체
이 섹션에서는 배열 교체를 위해 query() 메서드를 사용할 것입니다. 이 메서드를 사용하면 Sequelize가 원시 SQL 쿼리나 이미 준비된 SQL 쿼리를 실행할 수 있습니다.
시작하려면 1단계에서 server.js 파일의 내용을 복사하세요. 해당 파일에는 Sequelize() 초기화 메서드와 데이터베이스 초기화가 포함되어 있습니다. 내용을 array_raw_query.js라는 새 파일에 붙여넣으세요. 데이터베이스 이름을 sample_student_db로 업데이트하세요:
const {Sequelize, DataTypes} = require("sequelize");
const sequelize = new Sequelize(
'sample_student_db',
'DATABASE_USERNAME',
'DATABASE_PASSWORD',
{
host: 'DATABASE_HOST',
dialect: 'mysql'
}
);
sequelize.authenticate().then(() => {
console.log('Connection has been established successfully.');
}).catch((error) => {
console.error('Unable to connect to the database: ', error);
});파일의 끝에 다음 코드 블록을 배열 교체용으로 추가하되, 이전 섹션에서 복사한 student_id 값을 REPLACE_STUDENT_ID로 교체해야 합니다.
...
sequelize.query(
'SELECT * FROM students WHERE student_id = ?',
{
replacements: ['REPLACE_STUDENT_ID'],
type: sequelize.QueryTypes.SELECT
}
).then(result => {
console.log(result);
}).catch((error) => {
console.error('Failed to insert data : ', error);
});배열 교체를 위해서는 SQL 쿼리와 구성 객체를 query() 메서드에 전달합니다. 구성 객체에는 replacements 값과 유형이 포함됩니다. 교체할 값은 배열로 전달하며, 물음표(?) 기호를 사용하여 해당 값을 가져옵니다.
다음으로, 특정 학생에 대한 데이터를 가져와야 하므로 student_id가 두 번째 매개변수로 전달됩니다. 그 후에는 데이터베이스에서 데이터를 선택하는 데 사용할 수 있는 type: sequelize.QueryTypes.SELECT 키-값 쌍을 전달합니다.
다른 타입들도 있으며, 예를 들어 QueryTypes.UPDATE와 QueryTypes.DELETE가 있습니다. 필요에 따라 목적에 맞는 타입을 선택할 수 있습니다.
다음은 전체 코드 블록을 보여줍니다. 여기서 데이터베이스에 연결하고 원시 쿼리를 사용하여 선택된 학생 데이터를 가져옵니다.
const {Sequelize, DataTypes} = require("sequelize");
const sequelize = new Sequelize(
'sample_student_db',
'DATABASE_USERNAME',
'DATABASE_PASSWORD',
{
host: 'DATABASE_HOST',
dialect: 'mysql'
}
);
sequelize.authenticate().then(() => {
console.log('Connection has been established successfully.');
}).catch((error) => {
console.error('Unable to connect to the database: ', error);
});
sequelize.query(
'SELECT * FROM students WHERE student_id = ?',
{
replacements: ['REPLACE_STUDENT_ID'],
type: sequelize.QueryTypes.SELECT
}
).then(result => {
console.log(result);
}).catch((error) => {
console.error('Failed to insert data : ', error);
});파일을 저장하고 닫으세요.
다음으로, 다음 명령어를 사용하여 이 스크립트를 실행할 수 있습니다:
node array_raw_query.js다음과 유사한 출력 결과를 보게 될 것입니다:
Connection has been established successfully.
[ { student_id: 'STUDENT_ID_YOU_RETRIEVED',
name: 'Robert Gray',
createdAt: 2022-05-06T13:14:50.000Z,
updatedAt: 2022-05-06T13:14:50.000Z } ]선택된 student_id 때문에 출력 값이 다를 수 있습니다.
객체 교체
표면적으로 객체 교체는 배열 교체와 비슷하지만, 원시 쿼리에 데이터를 전달하는 방식에는 차이가 있습니다. 교체 옵션에서는 데이터를 객체로 전달하고, 쿼리 옵션에서는 :key와 같은 값을 사용합니다.
시작하려면 object_raw_query.js라는 새 파일을 만들고, server.js 파일의 전체 코드 블록을 붙여넣은 후, 데이터베이스를 sample_student_db로 업데이트하세요.
const {Sequelize, DataTypes} = require("sequelize");
const sequelize = new Sequelize(
'sample_student_db',
'DATABASE_USERNAME',
'DATABASE_PASSWORD',
{
host: 'DATABASE_HOST',
dialect: 'mysql'
}
);
sequelize.authenticate().then(() => {
console.log('Connection has been established successfully.');
}).catch((error) => {
console.error('Unable to connect to the database: ', error);
});그런 다음 새 object_raw_query.js 파일의 끝에 다음 코드 블록을 추가하세요:
...
sequelize.query(
'SELECT * FROM students WHERE student_id = :id',
{
replacements: { id: 'REPLACE_STUDENT_ID' },
type: sequelize.QueryTypes.SELECT
}
).then(result => {
console.log(result);
}).catch((error) => {
console.error('Failed to insert data : ', error);
});여기서는 객체 교체 방법을 사용하여 선택된 학생 데이터를 가져옵니다. replacement 객체를 생성하고, 가져오려는 학생 정보를 id로 설정합니다: { id: '<mark>REPLACE_STUDENT_ID</mark>' }.
query()에서 다음과 같이 지정합니다: 'SELECT * FROM students WHERE student_id=:id'. query() 메서드를 사용하면서 교체 값을 객체로 전달하기 때문에, 이 방법은 객체 교체(object replacement)라고 불립니다.
전체 코드는 다음과 같습니다:
const {Sequelize, DataTypes} = require("sequelize");
const sequelize = new Sequelize(
'sample_student_db',
'DATABASE_USERNAME',
'DATABASE_PASSWORD',
{
host: 'DATABASE_HOST',
dialect: 'mysql'
}
);
sequelize.authenticate().then(() => {
console.log('Connection has been established successfully.');
}).catch((error) => {
console.error('Unable to connect to the database: ', error);
});
sequelize.query(
'SELECT * FROM students WHERE student_id = :id',
{
replacements: { id: 'REPLACE_STUDENT_ID' },
type: sequelize.QueryTypes.SELECT
}
).then(result => {
console.log(result);
}).catch((error) => {
console.error('Failed to insert data : ', error);
});파일을 저장하고 닫으세요.
다음으로, 다음 명령어를 사용하여 이 스크립트를 실행하세요:
node object_raw_query.js출력은 다음과 유사하게 보일 것입니다:
Connection has been established successfully.
[ { student_id: 'STUDENT_ID_YOU_RETRIEVED',
name: 'Robert Gray',
createdAt: 2022-05-06T13:14:50.000Z,
updatedAt: 2022-05-06T13:14:50.000Z } ]선택된 student_id 때문에 출력 값이 다를 수 있습니다.
이 단계에서는 두 가지 다른 방법론인 배열 치환과 객체 치환을 사용하여 Sequelize 원시 쿼리를 다뤘습니다.
보안 데이터베이스 연결을 위한 환경 변수 구성
지금까지는 데이터베이스 자격 증명을 코드에 직접 저장해 왔습니다. 이러한 방식은 로컬 실험에는 적합하지만, 안전하지는 않습니다. 자격 증명을 하드코딩하면 데이터베이스 비밀번호나 호스트 이름과 같은 민감한 정보가 노출되고, 코드가 공유되거나 배포될 때 침해 위험이 증가합니다. 이 섹션에서는 환경 변수를 사용하여 이러한 자격 증명을 안전하게 저장하는 방법을 배울 것입니다.
환경 변수를 사용하는 이유는 무엇인가요?
환경 변수는 애플리케이션이 실행 시 읽을 수 있는 외부 구성 매개변수 역할을 합니다. 자격 증명을 코드에 직접 포함시키는 대신, 이러한 정보는 시스템이나 버전 관리에 커밋되지 않는 별도의 구성 파일에 저장됩니다. 이러한 접근 방식은 여러 가지 이점을 제공합니다:
- 보안: 귀하의 자격 증명은 공개 저장소 및 버전 관리 시스템에서 숨겨져 있습니다.
- 유연성: 각 환경(개발, 스테이징, 운영)은 소스 코드를 변경하지 않고도 서로 다른 데이터베이스 구성을 사용할 수 있습니다.
- 유지 관리성: 중앙 집중형 구성 덕분에 쉽게 업데이트할 수 있으며, 데이터베이스 비밀번호가 변경되더라도 한 파일이나 변수만 수정하면 됩니다.
- 준수: 이 관행은 확장 가능한 애플리케이션을 위해 환경별 구성 관리를 촉진하는 Twelve-Factor App 방법론과 일치합니다.
환경 변수 설정
Node.js에서 환경 변수를 관리하기 위해 dotenv 패키지를 사용할 것입니다. 이 라이브러리는 .env 파일에서 변수를 읽어 Node.js process.env 객체에 주입하여 애플리케이션 전체에서 접근할 수 있게 합니다.
다음 명령어를 실행하여 dotenv 패키지를 설치하세요:
npm install dotenv설치 후, 프로젝트의 루트 디렉토리에 .env라는 새 파일을 생성하세요. 이 파일에는 데이터베이스 연결에 필요한 모든 민감한 데이터가 저장됩니다.
다음은 Sequelize 설정을 위한 .env 파일 구성 예시입니다:
DB_NAME=hello_world_db
DB_USER=your_username
DB_PASSWORD=your_password
DB_HOST=localhost
DB_DIALECT=mysql각 변수는 Sequelize가 연결을 설정하는 데 사용하는 특정 매개변수에 해당합니다.
.env 파일 보안
.env 파일이 버전 관리에 절대 나타나지 않도록 하는 것이 중요합니다. 이를 방지하려면 .gitignore 파일에 추가하여 Git이 변경 사항을 커밋할 때 이를 건너뛰도록 하세요:
.env운영 환경에서는 환경 변수가 일반적으로 로컬 .env 파일 대신 호스팅 플랫폼의 설정이나 비밀 관리자에서 직접 구성됩니다.
Sequelize 구성 업데이트
이제 환경 변수를 정의했으므로 Sequelize 초기화를 업데이트하여 이를 사용하세요. 먼저 메인 서버 파일(예: server.js) 상단에서 dotenv를 가져와 구성하는 것으로 시작합니다:
require('dotenv').config();
const Sequelize = require('sequelize');
const sequelize = new Sequelize(
process.env.DB_NAME,
process.env.DB_USER,
process.env.DB_PASSWORD,
{
host: process.env.DB_HOST,
dialect: process.env.DB_DIALECT
}
);
sequelize.authenticate().then(() => {
console.log('Connection has been established successfully.');
}).catch((error) => {
console.error('Unable to connect to the database:', error);
});require('dotenv').config()라인은.env파일에 정의된 모든 변수를process.env로 불러옵니다.- Sequelize는 새 인스턴스를 초기화할 때 환경 변수를 읽습니다.
- 이렇게 하면 사용자 이름이나 비밀번호와 같은 민감한 정보가 코드에 나타나지 않습니다.
이 접근 방식은 보안을 강화하고 환경 변수 값만 변경하여 서로 다른 환경에서 고유한 구성을 사용할 수 있게 합니다.
구성 확인
구성을 업데이트한 후에는 Node.js 애플리케이션을 실행하여 연결을 확인할 수 있습니다:
node server.js만약 .env 파일이 올바르게 설정되어 있고 MySQL 서버가 실행 중이라면, 다음과 같은 출력이 표시될 것입니다:
Connection has been established successfully.오류가 나타나면 다음 사항을 다시 확인하세요:
.env파일의 자격 증명이 MySQL 인스턴스의 것과 일치합니다.- MySQL 서버가 실행 중이며 사용자의 호스트에서 접근할 수 있습니다.
.env파일은 프로젝트 루트에 위치하며 올바르게 포맷되어 있습니다.
보안을 위한 구성 모범 사례
환경 변수를 사용하는 것은 안전한 구성 관리를 위한 강력한 첫 번째 단계입니다. 지속적인 보안과 신뢰성을 보장하기 위해 다음과 같은 추가 모범 사례를 고려하십시오:
- 환경별로 별도의 설정 사용:
development,staging,production각각에 대해 다른.env파일을 유지하세요. 파일 이름은.env.development,.env.staging,.env.production으로 지을 수 있습니다. - 비밀 관리자 활용: 운영 환경에서는 비밀을 파일에 저장하지 말고 비밀 관리자를 사용하세요.
- 환경 변수 검증: 앱이 실행되기 전에 필수 변수가 정의되어 있는지 확인하는 구성 유틸리티나 시작 스크립트를 사용하세요. 이는 실행 시간 오류를 방지하는 데 도움이 됩니다.
- 자격 증명을 정기적으로 변경하세요: 잠재적인 유출의 영향을 줄이기 위해 데이터베이스 자격 증명을 주기적으로 업데이트하세요.
- 데이터베이스 접근 제한: 애플리케이션에 필요한 최소 권한만 갖도록 데이터베이스 사용자 역할을 구성하세요.
환경 변수를 구현하고 이러한 관행을 따르면, Sequelize 애플리케이션이 개발 및 운영 환경 전체에서 유연성과 보안을 모두 유지할 수 있습니다.
마이그레이션 및 동기화 최선의 방법
데이터베이스 스키마를 진화시킬 때, Sequelize를 사용하면 두 가지 주요 접근 방식이 있습니다: 명시적인 마이그레이션을 작성하고 실행하는 방법, 또는 sequelize.sync()를 사용하여 Sequelize가 스키마 변경을 자동으로 생성하도록 하는 방법입니다. 두 가지 접근 방식 모두 유효하지만, 용도가 다르며 각각의 장단점이 있습니다. 언제 각 방식을 사용해야 하는지, 안전한 마이그레이션을 작성하는 방법, 그리고 개발 및 운영 환경에서 이를 신뢰성 있게 실행하는 방법을 이해해 봅시다.
마이그레이션 vs sequelize.sync() — 언제 무엇을 사용할까
마이그레이션(프로덕션용 권장): 마이그레이션은 스키마에 대한 명시적이고 버전 관리가 되는 변경 사항입니다. 이는 소스 코드 관리에 저장되고, 코드 리뷰를 거치며, 배포 과정의 일부로 실행됩니다. 데이터 무결성이나 가용성에 영향을 주는 모든 스키마 변경에는 프로덕션 환경에서 마이그레이션을 사용하세요.
sequelize.sync()(개발 및 프로토타입 용도로만 사용):sync()는 테이블을 자동으로 생성하거나 조정할 수 있기 때문에 로컬 개발, 테스트, 빠른 프로토타입에 편리합니다. 그러나sync({ force: true })또는sync({ alter: true })를 운영 환경에서 사용하지 마세요. 이러한 옵션은 데이터를 삭제하거나 안전하지 않은 변경을 하여 되돌릴 수 없는 상황을 초래할 수 있습니다.
도구: sequelize-cli 및 마이그레이션 실행기
공식 CLI인 sequelize-cli는 Sequelize로 마이그레이션을 관리하는 가장 간단한 방법입니다. 더 많은 제어나 맞춤형 워크플로가 필요한 팀에게는 umzug가 인기 있는 대안입니다.
프로젝트에 CLI를 설치하고 초기화하려면 다음을 실행하세요:
npm install --save-dev sequelize-cli
npx sequelize-cli init이것은 다음 폴더를 생성합니다:
config/ # Stores database configuration files
models/ # Stores your Sequelize model definitions
migrations/ # Stores migration files
seeders/ # Stores seeder files for inserting initial data이 폴더들은 버전 관리에 추가해야 합니다(자격 증명이 포함된 파일은 제외). 이렇게 하면 모든 환경에서 동일한 데이터베이스 변경 사항이 일관되게 적용됩니다.
마이그레이션 생성 및 실행
새 마이그레이션 파일을 생성하려면 다음 명령어를 사용하세요:
npx sequelize-cli migration:generate --name add-isbn-to-books이렇게 하면 migrations/ 폴더에 타임스탬프가 있는 파일이 생성됩니다. 파일을 열고 마이그레이션을 적용할 때(up) 어떤 일이 발생할지, 그리고 이를 되돌릴 때(down) 어떻게 할지를 정의하세요:
module.exports = {
async up(queryInterface, Sequelize) {
await queryInterface.addColumn('books', 'isbn', {
type: Sequelize.STRING
});
},
async down(queryInterface) {
await queryInterface.removeColumn('books', 'isbn');
}
};up함수는 마이그레이션을 적용합니다(열을 추가함).down함수는 변경을 되돌립니다(열을 제거합니다).
다음을 사용하여 마이그레이션을 실행하세요:
npx sequelize-cli db:migrate마지막 마이그레이션을 되돌리려면 다음을 사용하세요:
npx sequelize-cli db:migrate:undo개발 환경에서는 다음 명령어를 실행하여 모든 마이그레이션을 되돌릴 수도 있습니다:
npx sequelize-cli db:migrate:undo:all안전하고 멱등적인 마이그레이션 작성
마이그레이션을 작성할 때 다음 규칙을 따르세요:
데이터베이스에서 지원하는 경우 작업을 트랜잭션으로 감싸십시오. 이렇게 하면 마이그레이션이 오류 발생 시 완전히 성공하거나 완전히 롤백되도록 보장할 수 있습니다.
module.exports = { async up(queryInterface, Sequelize) { return queryInterface.sequelize.transaction(async (t) => { await queryInterface.addColumn('books', 'isbn', { type: Sequelize.STRING }, { transaction: t }); }); }, async down(queryInterface) { return queryInterface.sequelize.transaction(async (t) => { await queryInterface.removeColumn('books', 'isbn', { transaction: t }); }); } };마이그레이션은 작고 집중적으로 유지하세요. 각 마이그레이션은 하나의 논리적 변경만 수행해야 합니다(컬럼 추가, 인덱스 생성, 컬럼 이름 변경 등). 작은 마이그레이션일수록 리뷰, 테스트, 롤백이 더 쉽습니다.
방어적이고 멱등적으로 행동하세요. 적절할 때, 드리프트가 있는 환경에서 실패를 피하기 위해 존재 여부를 확인하세요. 작업을 보호하기 위해
queryInterface.describeTable()또는 원시 쿼리를 사용하세요.한 번에 파괴적인 작업을 하지 마세요. 예를 들어, 안전하게 열을 제거하려면 새 열을 추가하고, 값을 채운 후, 애플리케이션 코드에서 읽기를 새 열로 전환한 다음, 나중의 마이그레이션에서 이전 열을 삭제하세요.
장기간 실행되며 중단 없는 마이그레이션
큰 테이블과 인덱스 작업은 잠금을 유발할 수 있습니다. 영향을 최소화하기 위해 다음 전략을 사용하세요:
- 먼저 NULL 값을 허용하는 열을 추가하세요. NULL 값을 허용하는 열을 추가하는 것은 일반적으로 빠릅니다. 전체 테이블 재작성(full-table rewrite)을 유발하는 복잡한 기본값이 있는 열은 추가하지 마세요.
- 작은 배치로 백필하세요. 단일 대규모
UPDATE를 실행하기보다는, 새로운 열을 청크 단위로 채우는 백그라운드 작업을 구현하세요. - 가능한 경우 독점 잠금 없이 인덱스를 생성하세요. 마이그레이션에서 원시 SQL을 사용하여 데이터베이스별 기능(예: Postgres의
CONCURRENTLY와 최신 MySQL 온라인 DDL)을 활용하세요. - 기능 플래그 롤아웃. 기능 플래그 뒤에서 새 컬럼을 읽는 애플리케이션 코드를 배포하세요. 백필이 완료되고 테스트가 통과하면 플래그를 전환한 후 나중에 이전 필드를 제거합니다.
롤백 및 긴급 수정
down마이그레이션을 제공하세요. 모든up에는 변경 내용을 되돌리는 방법을 문서화한 역down이 있어야 합니다. 실제 운영 환경에서down을 거의 실행하지 않더라도 명확한 롤백 계획으로 활용됩니다.- 비상 롤백 계획을 세우세요. 마이그레이션으로 문제가 발생하면 먼저 애플리케이션 코드를 롤백하고 필요에 따라 백업에서 데이터베이스를 복원할 준비를 하세요. 복원 절차를 정기적으로 테스트하세요.
데이터 및 스키마 변경 시 초기 데이터 입력
- 시더는 최소화하고 안정적으로 유지하세요. 필수 조회나 구성 행에 대해서만 시더를 사용하세요. 마이그레이션을 통해 대량의 비즈니스 데이터를 시딩하는 것은 피하세요.
- 씨앗은 구조적 마이그레이션과 분리하세요. 스키마 변경에 따라 데이터를 채워야 한다면, 책임이 명확하게 유지되도록 후속 시더를 사용하는 것이 좋습니다.
CI, 코드 리뷰, 배포 자동화
먼저 CI와 스테이징에서 마이그레이션을 실행하세요. 임시 데이터베이스에서 마이그레이션을 실행하는 CI 단계를 추가하여 오류를 조기에 잡으세요.
마이그레이션을 코드처럼 다루세요. 풀 리퀘스트에서 마이그레이션 파일을 린트하고 검토하세요. 가능한 경우 자동화된 검사를 포함하세요.
배포 시 마이그레이션 실행을 자동화하세요. 애플리케이션 프로세스가 트래픽을 처리하기 전에 마이그레이션이 실행되도록 합니다. 예시
package.json스크립트:"scripts": { "migrate": "npx sequelize-cli db:migrate", "start": "node server.js", "deploy": "npm run migrate && npm run start" }
컨테이너화된 배포에서는 마이그레이션을 초기화 컨테이너로 실행하거나 배포 훅에서 실행하여 웹 프로세스가 마이그레이션 완료 후에만 시작되도록 하세요.
마이그레이션 테스트
- 두 방향 모두 테스트하세요. 로컬과 CI 환경에서
db:migrate를 실행한 후db:migrate:undo:all를 실행하여up과down이 모두 제대로 작동하는지 확인하세요. - 테스트에는 일회용 데이터베이스를 사용하세요. 결정론적 테스트 실행을 위해 일시적인 데이터베이스나 컨테이너를 선호하세요.
이러한 모범 사례를 따르면 Sequelize 마이그레이션이 장기 프로젝트에서도 신뢰할 수 있고 추적 가능하며 안전하게 유지됩니다.
문제 해결 및 성능 향상 팁
탄탄한 설정을 갖추고 있더라도, Sequelize 애플리케이션은 때때로 데이터베이스 연결 오류, 마이그레이션 실패, 느린 쿼리 실행, 교착 상태, 동시성 충돌과 같은 예상치 못한 문제를 겪을 수 있습니다. 이 섹션에서는 이러한 문제를 식별하고 해결하는 단계별 가이드와 애플리케이션을 안정적이고 효율적으로 유지하는데 도움이 되는 성능 모범 사례를 제공합니다.
일반적인 문제 해결
1. 연결 오류
문제: Sequelize가 MySQL 데이터베이스에 연결할 수 없으며 SequelizeConnectionError: connect ECONNREFUSED, 사용자 접근 거부, 또는 SequelizeHostNotFoundError와 같은 오류를 발생시킵니다.
수정 방법:
먼저 연결 정보가 올바른지 확인하세요. 데이터베이스 이름, 사용자 이름, 비밀번호, 호스트, 포트를 포함하여 .env 파일의 값을 다시 한 번 확인하세요. 작은 오타나 불필요한 공백도 Sequelize 연결 실패를 유발할 수 있습니다.
다음으로, MySQL 서버가 실행 중이며 기본 포트(3306)에서 수신 대기 중인지 확인하세요. 수동으로 로그인하여 연결을 테스트할 수 있습니다:
mysql -u your_username -p -h your_host -P 3306이 명령이 실패하면 문제는 대부분 MySQL 서비스나 자격 증명에 있을 가능성이 높습니다.
만약 dotenv 패키지를 사용하고 있다면, require('dotenv').config()가 Sequelize가 초기화되기 전에 나오는지 확인하세요. 이 단계는 모든 환경 변수가 런타임에 사용 가능하도록 보장합니다.
마지막으로, 원격 연결의 경우 방화벽 설정이나 보안 그룹 규칙을 확인하세요. MySQL 서버는 애플리케이션 호스트로부터 들어오는 연결을 허용해야 합니다.
2. 마이그레이션 실패
문제: npx sequelize-cli db:migrate 또는 sequelize.sync()를 실행할 때 누락된 테이블, 중복된 열, 스키마 불일치와 같은 오류가 발생합니다.
수정 방법:
마이그레이션 문제는 일반적으로 파일이 잘못된 순서로 실행되거나 SQL 작업에 오류가 있을 때 발생합니다. Sequelize는 파일 이름의 타임스탬프를 기준으로 마이그레이션을 처리하므로 파일 이름이 올바른지 확인하세요.
마이그레이션이 실패하면 오류 메시지를 자세히 확인하세요. 그런 다음 다음을 사용하여 마지막 마이그레이션을 안전하게 되돌리세요:
npx sequelize-cli db:migrate:undo마이그레이션 파일을 수정한 후, 다시 실행하세요.
항상 마이그레이션 로직을 트랜잭션 안에 감싸서 원자성을 보장하세요. 즉, 모든 변경이 성공하거나 전혀 적용되지 않아야 합니다. 프로덕션에서 마이그레이션을 실행하기 전에 스테이징 또는 로컬 데이터베이스에서 철저히 테스트하여 정확성을 확인하세요.
3. 모델 동기화 문제
문제: Sequelize 모델과 데이터베이스 스키마가 일치하지 않아 누락된 열, 잘못된 데이터 유형 또는 오래된 테이블이 발생합니다.
수정 방법:
프로덕션 환경에서는 sequelize.sync({ force: true }) 사용을 피하세요. 이 명령은 테이블을 삭제하고 다시 생성하므로 데이터가 복구 불가능하게 손실될 수 있습니다. 마찬가지로 sequelize.sync({ alter: true }) 도 신중하게 사용하세요. 이 경우 스키마가 예측 불가능하게 수정될 수 있습니다.
대신, 모든 스키마 변경을 명확히 정의하는 명시적 마이그레이션을 사용하는 것이 좋습니다. 모델을 실제 데이터베이스 구조와 비교하려면 다음을 사용하세요:
await queryInterface.describeTable('tableName');이는 문제가 실제 생산에 영향을 미치기 전에 불일치를 식별하는 데 도움이 됩니다.
4. 쿼리 및 시간 초과 오류
문제: Connection timeout 또는 Too many connections와 같은 타임아웃 관련 오류가 발생합니다.
수정 방법:
타임아웃 문제는 종종 소진된 연결 풀이나 닫히지 않은 연결 때문에 발생합니다. 모든 Sequelize 쿼리가 올바르게 await되도록 하여 사용 후 연결이 풀로 반환되도록 하세요.
Sequelize의 풀 구성을 애플리케이션의 작업 부하에 맞게 조정할 수도 있습니다:
const sequelize = new Sequelize(DB_NAME, DB_USER, DB_PASSWORD, {
host: DB_HOST,
dialect: 'mysql',
pool: {
max: 10,
min: 0,
acquire: 30000,
idle: 10000
}
});애플리케이션이 많은 동시 사용자를 처리한다면, 데이터베이스 설정에서 MySQL max_connections 매개변수를 늘리는 것을 고려하세요. MySQL에서 SHOW PROCESSLIST;를 사용하여 활성 연결을 모니터링하고 누수나 사용되지 않는 세션을 감지하세요.
5. 느린 쿼리 성능
문제: 특정 쿼리가 예상보다 훨씬 오래 걸려 API 응답이 느려지거나 지연이 발생합니다.
진단 방법:
먼저 어떤 쿼리가 느린지 확인하는 것부터 시작하세요. Sequelize가 실행하는 모든 쿼리를 일시적으로 로깅하도록 설정할 수 있습니다:
const sequelize = new Sequelize(..., { logging: console.log });그런 다음 MySQL의 EXPLAIN 문을 사용하여 데이터베이스가 쿼리를 어떻게 실행하는지 이해합니다:
EXPLAIN SELECT * FROM users WHERE email = 'user@example.com';출력에 '전체 테이블 스캔'이 표시되면 쿼리에 인덱스가 없습니다.
MySQL 느린 쿼리 로그를 활성화하여 느린 작업을 자동으로 기록할 수도 있습니다:
SET GLOBAL slow_query_log = 'ON';
SET GLOBAL long_query_time = 1;수정 방법:
자주 조회되는 열(특히 WHERE, JOIN, ORDER BY 절에서 사용되는 열)에 인덱스를 추가하세요. SELECT *는 피하고, 대신 attributes 옵션을 사용하여 필요한 필드만 명시적으로 가져오세요.
Sequelize가 불필요한 조인을 포함한 비효율적인 쿼리를 생성한다면, 쿼리를 raw SQL로 다시 작성하세요. 대규모 보고서나 분석 쿼리의 경우, 쿼리를 더 작은 단계로 나누거나 반복되는 결과를 저장하기 위해 Redis와 같은 캐싱 도구를 사용하세요.
6. 교착 상태와 동시성 오류
문제: 특히 부하가 높은 상황에서 잠금을 얻으려고 할 때 교착 상태가 발생했습니다; 트랜잭션을 재시작해 보세요와 같은 오류가 발생할 수 있습니다.
진단 방법:
MySQL에서 InnoDB 상태를 확인하여 어떤 쿼리나 테이블이 관련되어 있는지 확인하세요:
SHOW ENGINE INNODB STATUS\G이 출력에는 최근 교착 상태와 잠금 대기와 관련된 자세한 정보가 포함되어 있습니다. 문제 발생 전 쿼리 순서를 검토하려면 일반 로그 또는 트랜잭션 로그를 활성화할 수도 있습니다.
수정 방법:
교착 상태는 두 개 이상의 트랜잭션이 서로의 락을 기다릴 때 발생합니다. 이를 최소화하려면:
거래는 가능한 한 짧게 유지하세요. 락을 잡고 있는 동안 외부 서비스를 호출하거나 무거운 계산을 수행하지 마세요.
순환 대기를 피하기 위해 트랜잭션 간에 항상 동일한 순서로 테이블과 행에 접근하세요.
반복적인 교착 상태의 경우, 지수적 대기 시간을 가진 간단한 재시도 메커니즘을 구현하세요:
async function runWithRetry(fn, retries = 3) { for (let i = 0; i < retries; i++) { try { return await fn(); } catch (err) { if (isDeadlockError(err) && i < retries - 1) { await sleep(100 * (i + 1)); continue; } throw err; } } }행 수준 잠금(
SELECT ... FOR UPDATE)은 필요할 때만 사용하고, 즉시 해제하세요.큰 거래를 더 작은 단위로 나누어 경쟁을 줄이세요.
적절하다면 트랜잭션 격리 수준을 조정하되, 데이터 일관성에 영향을 미치지 않는다는 것을 확인한 후에만 수행하세요.
성능 최적화 팁
성능 문제는 종종 비효율적인 쿼리, 누락된 인덱스, 또는 부적절한 연결 처리에서 발생합니다. 명확성과 유지보수성을 해치지 않으면서 Sequelize 성능을 향상시키기 위한 몇 가지 실용적인 팁은 다음과 같습니다.
1. 쿼리 최적화
필요한 데이터만 요청하는 쿼리를 작성하세요. 불필요한 데이터를 가져오면 메모리 사용량이 증가하고 응답 속도가 느려집니다. 필드를 제한하려면 attributes를 사용하세요:
const users = await User.findAll({ attributes: ['id', 'name'] });가능하면 항상 인덱스된 열을 사용하여 결과를 필터링하고 LIKE '%term%' 같은 광범위한 검색은 피하세요.
2. 인덱스를 효과적으로 사용하기
인덱스는 조회 속도를 빠르게 하지만 쓰기 속도를 느리게 할 수 있습니다. 실질적인 이점을 제공하는 곳에만 인덱스를 추가하세요. 인덱스를 추가하기 전과 후에 MySQL의 EXPLAIN을 사용하여 쿼리 성능을 분석하고 개선 여부를 확인하세요.
await queryInterface.addIndex('users', ['email']);3. 연결 풀 조정
연결 풀은 Sequelize가 유지하는 데이터베이스 연결 수를 결정합니다.
- 작은 앱의 경우 보통 5~10개의 연결이면 충분합니다.
- 고부하 시스템에서는 연결 병목 현상을 방지하기 위해 풀 크기를 늘리고 타임아웃을 조정하세요. MySQL의
SHOW PROCESSLIST;를 사용하여 사용량을 정기적으로 모니터링하세요.
4. 로깅 및 모니터링 활성화
SQL 쿼리를 검토하기 위해 Sequelize 로깅을 일시적으로 켭니다. 성능 기준을 초과하는 쿼리를 식별하려면 MySQL 느린 쿼리 로그를 사용하세요. 실시간 추적을 위해 PM2, New Relic, Datadog과 같은 모니터링 도구를 통합하여 지연 시간과 자원 사용량을 측정하세요.
5. 자주 조회되는 쿼리 캐시
자주 변경되지 않는 데이터는 캐시 결과를 사용하세요. Redis나 Memcached와 같은 도구는 데이터베이스 요청을 줄이고 응답 시간을 개선하며, 특히 읽기 작업이 많은 워크로드에서 효과적입니다.
6. 결과 페이지 매김
대규모 결과 세트는 상당한 메모리를 사용합니다. API에서 쿼리를 항상 페이지로 나누세요:
const users = await User.findAll({ limit: 50, offset: 100 });페이지 구분은 부하가 걸릴 때 응답을 가볍고 일관되게 유지합니다.
7. 복잡한 작업에는 원시 쿼리 사용
Sequelize의 추상화가 특정 사용 사례에 대해 비효율적인 SQL을 생성한다면, 직접 원시 SQL 쿼리를 실행하세요:
const [results] = await sequelize.query('SELECT name FROM users WHERE active = 1');원시 쿼리는 최소한으로 사용하고, 향후 유지 관리를 위해 명확하게 문서화하세요.
자주 묻는 질문
1. Node.js에서 Sequelize란 무엇인가요?
Sequelize는 Node.js용 객체 관계 매퍼(ORM)로, MySQL, PostgreSQL, SQLite, MariaDB와 같은 SQL 데이터베이스를 다루기 위한 고급 JavaScript 기반 API를 제공합니다. 모든 작업마다 직접 SQL을 작성하는 대신, 데이터베이스 테이블에 매핑되는 모델을 정의하고 모델 메서드를 사용하여 레코드를 삽입, 조회, 업데이트, 삭제할 수 있습니다. Sequelize는 또한 모델 간의 연관 관계, 트랜잭션, 마이그레이션, 직접 SQL이 필요할 때 사용하는 원시 쿼리도 지원합니다.
2. Sequelize를 MySQL에 어떻게 연결하나요?
Sequelize를 MySQL에 연결하려면 데이터베이스 자격 증명과 옵션을 사용하여 Sequelize 인스턴스를 생성하면 됩니다. Node.js 파일에서 Sequelize를 require하고, 필요에 따라 환경 변수를 불러온 후, 데이터베이스 이름, 사용자 이름, 비밀번호, 호스트/다이얼렉트 옵션을 사용하여 인스턴스를 생성합니다. 예를 들어:
require('dotenv').config();
const { Sequelize } = require('sequelize');
const sequelize = new Sequelize(
process.env.DB_NAME,
process.env.DB_USER,
process.env.DB_PASSWORD,
{
host: process.env.DB_HOST || 'localhost',
dialect: 'mysql'
}
);
await sequelize.authenticate();
console.log('Connected to the database successfully!');자격 증명은 항상 소스 관리 외부에 보관하세요(예: .env 파일이나 비밀 관리자에서).
3. Sequelize에서 모델을 생성하고 동기화하려면 어떻게 해야 하나요?
Sequelize에서 모델은 테이블을 나타냅니다. 모델은 sequelize.define()을 사용하거나 Model 클래스를 확장하여 정의할 수 있습니다. 그 후 데이터베이스와 동기화하면 Sequelize가 테이블을 자동으로 생성합니다(개발 시에 유용합니다). 작은 예시는 다음과 같습니다:
const { DataTypes } = require('sequelize');
const Book = sequelize.define('Book', {
title: { type: DataTypes.STRING, allowNull: false },
author: { type: DataTypes.STRING, allowNull: false },
releaseDate: DataTypes.DATEONLY
});
// Create the table if it does not exist
await sequelize.sync();프로덕션 환경에서는 스키마 변경에 대한 버전 관리를 위해 마이그레이션을 사용하는 것이 좋습니다.
4. Express에서 Sequelize를 어떻게 사용하나요?
Express 앱에서 Sequelize를 별도의 파일(예: db.js)에 초기화하고, 연결된 인스턴스와 모델을 내보낸 후, 라우트 핸들러나 컨트롤러에서 필요할 때 가져와 사용합니다. 라우트 핸들러에서는 async/await를 사용해 Sequelize 모델 메서드를 호출하고, try/catch나 에러 미들웨어로 오류를 처리합니다. 실제 사용 예시는 다음과 같습니다:
// db.js
const sequelize = new Sequelize(...);
const User = sequelize.define(...);
module.exports = { sequelize, User };
// app.js
const { User } = require('./db');
app.get('/users', async (req, res) => {
const users = await User.findAll();
res.json(users);
});Express 서버를 시작하기 전에 마이그레이션을 실행하거나 sequelize.sync()를 호출하여 요청이 들어올 때 데이터베이스 테이블이 존재하도록 하세요.
5. Sequelize와 순수 SQL 쿼리의 차이점은 무엇인가요?
Sequelize는 데이터베이스 로직을 자바스크립트로 작성할 수 있게 해주는 반면, 원시 SQL은 SQL 문을 직접 작성하는 것을 의미합니다. Sequelize를 사용하면 모델과 관계를 한 번 정의하고 앱 전체에서 사용할 수 있기 때문에 코드가 더 깔끔하고 유지 관리가 쉬워집니다. 원시 SQL은 더 많은 제어권을 제공하며, 매우 복잡한 쿼리에서는 때때로 더 빠를 수 있습니다.
요약하면, 일상적인 작업에는 대부분 Sequelize를 사용하고, 성능을 세밀하게 조정해야 하거나 Sequelize로 쉽게 표현할 수 없는 작업을 수행할 때는 원시 SQL을 사용하세요.
6. Sequelize에서 연관 관계를 어떻게 정의할 수 있나요?
Sequelize는 테이블 간의 관계를 쉽게 설명할 수 있게 해줍니다. hasOne, hasMany, belongsToMany와 같은 간단한 메서드를 사용하여 일대일, 일대다, 다대다 관계를 설정할 수 있습니다. 예를 들어:
// One to one
User.hasOne(Profile);
Profile.belongsTo(User);
// One to many
Author.hasMany(Book);
Book.belongsTo(Author);
// Many to many
Student.belongsToMany(Course, { through: 'StudentCourse' });
Course.belongsToMany(Student, { through: 'StudentCourse' });한 번 정의하면, 쿼리에서 include를 사용하여 관련 데이터를 자동으로 가져올 수 있습니다.
7. 마이그레이션과 스키마 변경은 어떻게 처리하나요?
개발 중에는 sequelize.sync()를 사용하여 테이블을 빠르게 생성할 수 있습니다. 하지만 운영 환경에서는 항상 마이그레이션을 사용해야 합니다. 마이그레이션은 데이터베이스 스키마가 시간이 지남에 따라 어떻게 변화하는지를 설명하는 버전 관리 파일로, Git 커밋이 코드 변경 사항을 추적하는 방식과 유사합니다.
Sequelize CLI를 사용하여 마이그레이션을 생성할 수 있습니다:
npx sequelize-cli migration:generate --name add-users-table각 마이그레이션에는 변경 사항을 적용하는 up 메서드와 변경 사항을 되돌리는 down 메서드가 있습니다. 마이그레이션을 실행하려면 다음을 사용하세요:
npx sequelize-cli db:migrate이렇게 하면 팀의 모든 사람(및 모든 배포)이 동일한 데이터베이스 구조를 유지할 수 있습니다.
빠른 실험에는 sequelize.sync()를 사용하지만, 앱이 라이브 상태가 되면 마이그레이션을 사용하는 것이 좋습니다. 마이그레이션은 더 안전하고, 되돌릴 수 있으며, 추적하기도 쉽습니다.
결론
이 튜토리얼에서는 Node.js와 MySQL을 사용하여 Sequelize를 설정하고, 모델, 연관관계, 마이그레이션을 통해 데이터를 관리하는 방법을 다룹니다. 또한 환경 변수를 사용하여 데이터베이스 자격 증명을 안전하게 보호하고, 문제 해결 및 성능 튜닝을 위한 모범 사례를 살펴봅니다.
이 단계들을 통해 이제 Sequelize와 MySQL을 사용하여 데이터를 관리하는 안전하고 효율적인 Node.js 애플리케이션을 구축하고 유지하기 위한 탄탄한 기반을 갖추게 되었습니다.
Sequelize에 대해 더 알아보려면 제품 문서를 확인하세요. 더 많은 Node.js 튜토리얼은 다음 글들을 참고하세요:
- Apollo Server와 Sequelize를 사용하여 Node.js에서 GraphQL 서버 설정하는 방법
- Redis를 사용하여 Node.js에서 캐싱 구현하는 방법
- HTTP 모듈을 사용하여 Node.js에서 웹 서버 만드는 방법
Linux-Console.net 커뮤니티와 함께 학습해 주셔서 감사합니다.
