models/index.js 파일이 공개하는 db 객체를 한번 사용해보자. sequelize_sample 디렉토리 안에 app.js라는 파일을 만들고 아래와 같은 코드를 써보자.
// app.js
const db = require('./models/index.js');
const sequelize = db.sequelize;
(async () => {
await sequelize.sync();
})();
지금 db 객체의 sequelize 객체는 각 모델 클래스를 생성할 때 옵션에 들어갔던 그 sequelize 객체이다. 바로 이 sequelize 객체의 sync라는 메소드를 호출하면 각 모델에 대응되는 테이블이 실제로 데이터베이스에도 생성이 된다. 말그대로 데이터베이스에도 싱크를 맞춘다는 뜻이다. 그리고 sync 메소드는 Promise 객체를 리턴하는 비동기 메소드이기 때문에 그 앞에 await을 붙여주었다. sequelize를 사용할 때는 거의 대부분의 메소드들이 이렇게 Promise 객체를 리턴하는 메소드들이기 때문에 그 앞에 await을 붙여줘야한다는 점을 잘 기억해야 한다. 이 코드를 실행해보면
node app.js
아래와 같이 생긴 SQL 문들이 출력되는 것을 알 수 있다.
자세히 읽어보면 결국 모두 테이블을 생성하는 SQL 문인 것을 알 수 있다. 우리는 단지 모델들을 정의하고 sequelize 객체의 sync 메소드를 호출했을 뿐인데 sequelize 객체가 알아서 실제 데이터베이스에 테이블들을 생성해주는 것이다. Workbench로 확인해보면
테이블들이 잘 생긴 것을 알 수 있다. 음, 그런데 뭔가 이상하다. 지금 테이블 이름들은 모두 복수형이다. 모델 이름들은 모두 단수형인데 테이블 이름들은 모두 끝에 s가 붙어있는 것을 알 수 있다. 이것은 sequelize 객체가 자동으로 이렇게 처리하는 것인데 옵션으로 이 동작을 막을 수는 있다. Student.js 파일을 예로 들면
// student.js
~~~
Student.init(
{
registrationNum: DataTypes.STRING,
name: DataTypes.STRING,
age: DataTypes.INTEGER,
},
{
sequelize,
modelName: 'Student',
freezeTableName: true, //
}
);
return Student;
~~~
freezeTableName이라는 옵션에 true 값을 주면 된다. 말 그대로 sequelize 보고 굳이 단수형 단어를 복수형으로 바꾸지 말고 모델 이름 그대로 사용하라는 뜻이다. 혹시 이렇게 할 사람은 생성된 테이블들을 다 삭제하고 다시 코드를 실행하길 바란다.
그럼 잠깐 Students 테이블을 보자.
그런데 컬럼들을 보니 이것도 좀 이상하다. 모델의 속성으로는 존재하지 않았던
이 존재하는 것을 볼 수 있다. 컬럼 구조만 따로 살펴보니
id 컬럼이 지금 Primary Key에 해당한다는 것을 알 수 있다.
바로 이런 점이 ORM을 사용하는 묘미라고 할 수 있는데, 이렇게 sequelize는 모델에서 특정 컬럼을 별도로 Primary Key라고 나타내주지 않으면 Primary Key 역할을 할 수 있는 id 컬럼을 자동으로 추가한다. Primary Key를 별도로 지정하려면 어떻게 해야할까? Student.js의 코드를 이렇게 바꿔주면 된다.
// student.js
~~~
Student.init(
{
registrationNum: {
primaryKey: true,
type: DataTypes.STRING,
}
name: DataTypes.STRING,
age: DataTypes.INTEGER,
},
{
sequelize,
modelName: 'Student',
freezeTableName: true, //
}
);
return Student;
~~~
이렇게 코드를 수정하면 Student 테이블이 생성될 때 id 컬럼은 없고 registrationNum 컬럼이 Primary Key로 설정된다. 만약 직접 Primary Key를 설정하고 싶으면 이 방법을 쓰면 되겠다.
자, 이제 createdAt, updatedAt 컬럼을 생각해보자. 이 컬럼도 id 컬럼처럼 Sequelize가 자동으로 생성하는 컬럼이다. 어떤 row를 추가했을 때(createdAt), 기존 row를 수정했을 때(updatedAt) 해당 컬럼에 값이 채워지는 것이다. 만약 이런 컬럼을 만들고 싶지 않다면
// student.js
~~~
Student.init(
{
registrationNum: {
primaryKey: true,
type: DataTypes.STRING,
}
name: DataTypes.STRING,
age: DataTypes.INTEGER,
},
{
sequelize,
modelName: 'Student',
timestamps: false //
}
);
return Student;
~~~
timestamps라는 옵션의 값을 false로 줘야 한다. 아무 표시도 없으면 timestamps라는 옵션의 값은 true가 기본값이어서 자동으로 createdAt, updatedAt 컬럼이 생성된다.
모델 객체의 init 메소드의 두 번째 객체의 옵션에는 여러 가지 종류가 있다. 하지만 그것을 지금 모두 다룰 수는 없기에 그 부분은 공식 문서의 내용을 참고하길 바란다. 컬럼에 Not null 속성을 설정하는 방법, Unique 인덱스를 설정하는 방법, 물리 삭제 대신 논리 삭제를 할 수 있는 방법(paranoid 옵션) 등이 모두 잘 나와있다. 혹시 필요로 하는 분들이 있다면 이 부분만 따로 정리를 해서 새 글을 쓰겠다.
자, 그런데 한 가지만 기억하고 넘어가자. 지금 app.js 파일을 보면
// app.js
const db = require('./models/index.js');
const sequelize = db.sequelize;
(async () => {
await sequelize.sync();
})();
sequelize 객체의 sync 메소드를 호출하고 있다. 그런데 이렇게 호출하면 우리가 각 모델 객체의 옵션 등을 바꾸어도 실제 테이블에는 반영되지 않는다. 위의 첫 번째 이미지에서 본 것처럼 'CREATE TABLE IF NOT EXISTS~' 이렇게 기존 테이블이 있으면 건드리지 않기 때문이다. sequelize를 배울 때는 코드를 이렇게 저렇게 바꿔가며 실제 테이블에 반영되는 모습을 보는 것이 중요한데 이것을 가능하게 하려면 sync 메소드에 옵션 객체를 전달하면 된다.
// app.js
const db = require('./models/index.js');
const sequelize = db.sequelize;
(async () => {
await sequelize.sync({ force: true });
})();
이렇게 { force: true } 라는 옵션을 주게 되면 기존 테이블이 있어도 그것을 날리고 다시 새 코드에 맞게 테이블을 생성한다. 물론 실제 서비스에서는 절대 사용하면 안 되는 옵션이지만, 연습을 할 때는 이 옵션을 유용하게 사용하면 좋다.
그리고 참고로 각 모델별로 하나씩 테이블을 별도로 생성하는 것도 가능하다.
// app.js
const db = require('./models/index.js');
const sequelize = db.sequelize;
const Student = db.Student;
const ScholarshipAccount = db.ScholarshipAccount;
const Course = db.Course;
const Professor = db.Professor;
(async () => {
// await sequelize.sync({ force: true });
await Student.sync();
await ScholarshipAccount.sync();
await Course.sync();
await Professor.sync();
})();
이렇게 db 객체에 있던 각각의 모델들을 하나씩 꺼내고, 각 모델의 sync 메소드를 호출하면 해당 테이블이 생성된다. 모든 모델을 한번에 테이블로 만들 때는 sequelize 객체의 sync 메소드를, 특정 모델만 테이블로 반영하려면 해당 모델의 sync 메소드를 호출하면 되는 것이다.