1. mariaDB 설치 (WSL2)
mariaDB는 아래 명령어로 설치할 수 있습니다.
apt update && apt-get -y upgrade apt-get install -y mariadb-server |
설치가 완료되면 DB서비스를 시작하고
service mysql start |
초기설정을 진행합니다.
mysql_secure_installation = unix_socket athorization (시스템과 mysql의 root계정을 동일시 할것인가?) 사용여부 (y) = root 패스워드 설정여부 (n) = anonymous users 삭제여부 (y) = 원격지 root 로그인 비허용여부 (n) = test db 삭제 여부 (y) = 위 설정내용 적용 여부 (y) |
WLS2에서 mariaDB를 설치한 경우 외부에서 접속을 하기 위해서는 아래 파일을 열고
vi /etc/mysql/mariadb.conf.d/50-server.cnf |
bind-address를 0.0.0.0으로 수정합니다.
그리고 root에 권한을 부여합니다.
grant all privileges on *.* to root@'%' identified by '[패스워드]'; |
WSL2는 기본적으로 모든 포트가 열림상태인듯 합니다. 따라서 외부에서 DB접속을 위한 별도의 포트설정은 하지 않아도 됩니다.
2. node.js 패키지 설치
node.js에서 시퀄라이즈를 사용하기 위해 아래 모듈을 설치합니다.
npm i sequelize sequelize-cli mysql2 |
설치가 완료되었으면 다음 명령으로 시퀄라이즈를 초기화 합니다.
npx sequelize init |
3. 모델 설정
초기화에 성공하면 프로젝트 폴더에 models, migrations, seeders등 몇개의 폴더가 자등으로 생성됩니다.
그리고 models 디렉토리에 있는 index.js파일을 보면 여러가지 내용이 있을텐데 다 지우고 아래와 같이 간소하게 만들어 둡니다.
const Sequelize = require('sequelize');
const env = process.env.NODE_ENV || 'development';
const config = require('../config/config')[env];
const db = {};
const sequelize = new Sequelize(config.database, config.username, config.password, config);
db.sequelize = sequelize;
db.Sequelize = Sequelize;
module.exports = db;
다음으로 자동으로 만들어진 디렉토리중 config폴더안에서 config.json파일을 열어 아래와 같이 수정합니다. username과 password, database, host값은 상황에 따라 조금씩 다를 수 있으니 확인하고 설정해야 합니다.
{
"development": {
"username": "root",
"password": "!cliel!",
"database": "test",
"host": "192.168.137.1",
"dialect": "mysql"
},
"test": {
"username": "root",
"password": null,
"database": "database_test",
"host": "127.0.0.1",
"dialect": "mysql"
},
"production": {
"username": "root",
"password": null,
"database": "database_production",
"host": "127.0.0.1",
"dialect": "mysql"
}
}
node.js 프로젝트의 js파일에서는 models디렉토리의 index.js파일에서 DB의 연결객체에 해당하는 sequelize를 requre로 불러와 sync함수를 통해 DB와 연결을 시도합니다.
const express = require('express');
const fs = require('fs');
const { sequelize } = require('./models');
const app = express();
app.set('port', 80);
sequelize.sync({ force : false})
.then(() => {
console.log('연결성공');
})
.catch((err) => {
console.error(`연결실패 - ${err}`);
});
...생략
만약 연결에 성공했다면 다음과 같이 나올것입니다.
이제 모델을 생성해 보겠습니다. models디렉토리에서 우선 User.js라는 파일하나를 생성해 다음과 같이 모델을 생성합니다.
const Sequelize = require('sequelize');
module.exports = class User extends Sequelize.Model {
static init(sequelize) {
return super.init({
user_id: {
type: Sequelize.STRING(20),
allowNull: false,
unique: true
},
user_name: {
type: Sequelize.STRING(50),
allowNull: true,
unique: false
},
user_age: {
type: Sequelize.INTEGER,
allowNull: true,
unique: false
},
},
{
sequelize,
timestamps: false,
underscored: false,
modelName: 'User',
tableName: 'tbl_user',
paranoid: false,
charset: 'utf8mb4',
collate: 'utf8mb4_general_ci'
});
}
};
super.init의 첫번째 객체로 테이블의 컬럼과 대응되는 객체를 전달합니다. Sequelize.STRING(100)은 varchar(100)과 같은데 일반 SQL서버에서의 타입과 시퀄라이즈에서의 타입을 비교해서 설정해야 합니다.
varchar | STRING |
int | INTEGER |
tinyint / bit | BOOLEAN |
datetime | DATE |
long / int unsigned | INTEGER.UNSIGNED |
'valuue1', 'value2' | ENUM('value1', 'value2') |
데이터 타입에서 차이가 나는 이유는 시퀄라이즈자체가 특정 DB만을 위한것이 아닌 mariaDB, mysql, mssql등 다양한 DB를 지원하고 있으므로 특정 DB전용에 해당하는 데이터타입명을 사용할 수 없었기 때문입니다.
allowNull은 해당 컬럼이 NULL값을 가질 수 있는지의 여부이며 unique는 해당 컬럼이 중복되는 값을 가지는지의 여부입니다. 예제에서는 사용되지 않았지만 defaultValue를 통해 기본값을 defaultValue: Sequelize.NOW 처럼 설정해 줄 수도 있습니다.
두번째 객체로 설정값을 넣어주는데 timestamps는 테이블에 createdAt과 updateedAt컬럼을 만들어 생성일자와 변경(업데이트)날짜를관리할 수 있도브록 할것인지의 여부입니다. underscored는 위 컬럼의 이름에 created_at 처럼 _문자를 붙일지의 여부이며 paraoid는 테이블에 데이터 삭제시 실제 데이터를 삭제하는 대신 삭제날짜를 기입하여 삭제데이터를 관리할 것인지의 여부를 나타냅ㄴ다.
modelName은 웬만하면 class이름과 같도록 설정하는 것이 좋으며 테이블은 모델로서 관리할 테이블명을 설정해주면 되고 나머지 charset과 collate는 테이블을 생성할때의 설정과 동일하게 맞춰주면 됩니다.
시퀄라이즈는 테이블이 존재하지 않으면 자동으로 모델에 대응하는 테이블을 생성해주는데 모델을 만들때 timestamps나 underscored, paranoid등의 설정은 모두 시퀄라이즈가 테이블을 만들때 사용되는 옵션들입니다. 임의로 컬럼을 생성하거나 이미 DB상에 테이블이 만들어져 있다면 이 설정은 적용되지 않습니다. 이미 만들어져 있는 테이블에 대해서는 시퀄라이즈가 손을 대지 않습니다.
여기서 테이블을 하나더 생성해 보겠습니다. 이전에는 사용자의 정보를 담는 User라는 모델을 만들었는데 게시판인 경우를 상상해서 Board라는 모델을 추가해 보겠습니다.
const Sequelize = require('sequelize');
module.exports = class Board extends Sequelize.Model {
static init(sequelize) {
return super.init({
user: {
type: Sequelize.INTEGER
},
title: {
type: Sequelize.STRING(50),
allowNull: true,
unique: false
},
body: {
type: Sequelize.STRING(200),
allowNull: true,
unique: false
},
reg_date: {
type: Sequelize.DATE,
allowNull: true,
unique: false
},
},
{
sequelize,
timestamps: false,
underscored: false,
modelName: 'Board',
tableName: 'tbl_board',
paranoid: false,
charset: 'utf8mb4',
collate: 'utf8mb4_general_ci'
});
}
};
위와 같이 추가했으면 User..js로 돌아가 다음과 같이 associate를 추가해 줍니다.
const Sequelize = require('sequelize');
module.exports = class User extends Sequelize.Model {
static init(sequelize) {
return super.init({
user_id: {
type: Sequelize.STRING(20),
allowNull: false,
unique: true
},
user_name: {
type: Sequelize.STRING(50),
allowNull: true,
unique: false
},
user_age: {
type: Sequelize.INTEGER,
allowNull: true,
unique: false
},
},
{
sequelize,
timestamps: false,
underscored: false,
modelName: 'User',
tableName: 'tbl_user',
paranoid: false,
charset: 'utf8mb4',
collate: 'utf8mb4_general_ci'
});
}
static associate(db) {
db.User.hasMany(db.Board, { foreignKey : 'user', sourceKey : 'id' });
}
};
어떤 테이블이 다른 테이블과 1:N 관계를 형성하고 있다면 HasMany를 통해 그 관계를 설정해 줍니다. 사용자(User)는 다수의 글을 작성할 수 있고(Board) 결국 User와 Board는 1:N관계이므로 hasMany에서 N에 해당하는 모델과 forgignKey와 sourceKey를 통해 N관계테이블과 주테이블간에 어떤 컬럼으로 연결되는지를 지정해 줍니다.
위 예제에서는 sourceKey를 id로 했는데 실제 User모델에는 id가 없습니다. 그러나 이게 가능한 이유는 시퀄라이즈가 테이블을 생성할때 키와 자동증가가 설정된 id컬럼을 자등으로 만들어 주기 때문입니다.
Board모델에서도 아래와 같이 associate를 추가합니다.
static associate(db) {
db.Board.belongsTo(db.User, { foreignkEY : 'user', targetKey : 'id' });
}
belongsTo는 어떤 테이블이 외래키를 통해 다른 테이블과 연결되는 경우 설정합니다. 현재 Board는 User몯레서 user라는 외래키와 id라는 컬럼으로 연결되어 있음을 의미합니다.
만약 테이블이 1:1로 연결되어 있다면 hasMany대신 One을 붙여 hasOne을 사용해야 합니다. 예를 들어 UserDetail이라는 모델이 존재하고 User에 존재하는 사용자의 상세정보를 보는 모델이 있다고 가정했을때
const Sequelize = require('sequelize');
module.exports = class UserDetail extends Sequelize.Model {
static init(sequelize) {
return super.init({
user: {
type: Sequelize.INTEGER
},
user_address: {
type: Sequelize.STRING(50),
allowNull: true,
unique: false
},
user_phone: {
type: Sequelize.STRING(20),
allowNull: true,
unique: false
}
},
{
sequelize,
timestamps: false,
underscored: false,
modelName: 'UserDetail',
tableName: 'tbl_userDetail',
paranoid: false,
charset: 'utf8mb4',
collate: 'utf8mb4_general_ci'
});
}
};
User는 아래와 같이 hanOne을 추가하고
static associate(db) {
db.User.hasMany(db.Board, { foreignKey : 'user', sourceKey : 'id' });
db.User.hasOne(db.UserDetail, { foreignKey : 'user', sourceKey : 'id' });
}
UserDetail모델은 Board와 마찬가지로 외래키로 연결하는 belongsTo를 사용합니다.
static associate(db) {
db.UserDetail.belongsTo(db.User, { foreignKey : 'user', targetKey : 'id' });
}
흔하지는 않지만 N:M과 같은 다대다관계에서는 관계되는 테이블 모두 belongsToMany로 연결되는 경우도 있습니다. (중간테이블이 존재하는 경우 through로 중간테이블을 지정해야 합니다.)
db.table1.belongsToMany(db.table2, { through : 'table3' });
db.table2.belongsToMany(db.table1, { through : 'table3' });
위와 같이 모델을 추가했으면 모델이 시퀄라이즈와 DB에 적용될 수 있도록 config.js파일을 아래와 같이 수정합니다.
const Sequelize = require('sequelize');
const User = require('./User');
const Board = require('./Board');
const UserDetail = require('./UserDetail');
const env = process.env.NODE_ENV || 'development';
const config = require('../config/config')[env];
const db = {};
const sequelize = new Sequelize(config.database, config.username, config.password, config);
db.sequelize = sequelize;
db.Sequelize = Sequelize;
db.User = User;
db.Board = Board;
db.UserDetail = UserDetail;
User.init(sequelize);
Board.init(sequelize);
UserDetail.init(sequelize);
User.associate(db);
Board.associate(db);
UserDetail.associate(db);
module.exports = db;
이제 프로젝트를 실행하면 mariaDB에 다음과 같이 Table이 생성되어 있을 것입니다.
4. 테이블 데이터 다루기
insert는 모델에서 create() 함수로 대체됩니다.
User.create({
user_id : 'tmpid',
user_name : '홍길동',
user_age : 30
});
Select는 findAll()이나 findOne()을 사용할 수 있습니다. 이 둘의 차이점은 All은 모든 데이터를 가져오고 One은 하나만 가져온다는 것입니다.
const result = await User.findAll()
.then(
users => {
console.log(users[0].dataValues.user_name);
}
);
order by 와 같은 순서는 order를 사용해 지정합니다.
const result = await User.findAll({
order: [['user_id', 'asc']]
});
모든 컬럼이 아닌 특정컬럼의 값만 필요하다면 attributes로 가져올 컬럼을 직접 설정할 수 있습니다. 실제값은 dataVlues안에 객체로 존재합니다.
const result = await User.findAll({
attributes: ['user_id', 'user_name']
});
예제에서는 result로 값을 받아오는 부분과 then으로 값을 받아오는 부분 2가지를 사용하고 있는데 둘중 편한쪽으로 하나만 사용하면 됩니다. 참고로 시퀄라이즈는 promise이므로 await나 then을 사용해야 값을 받아올 수 있습니다.
데이터를 가져오는데 where조건이 필요하다면 아래와 같이 where조건을 설정할 수 있습니다.
const result = await User.findAll({
where : {
user_id : 'aaa'
}
});
이때 만약 비교연산자를 사용해야 한다면 시퀄라이즈의 Op를 사용해서 연선자를 적용합니다.
const result = await User.findAll({
where : {
user_age: { [Op.gt] : 10 }
}
});
Op를 사용하면 조건문 자체에 and나 or절을 추가할 수도 있습니다. 따라서 아래 예제는 user_age가 10이상이고 user_id가 'bbb'인 사용자정보를 가져옵니다.
const result = await User.findAll({
where : {
[Op.and] : [
{ user_age: { [Op.gt] : 10 } },
{ user_id : 'bbb'}
]
}
});
참고로 Op.and는 기본값이라서 따로 설정하지 않으면 where안에 모든 조건은 and가 됩니다.
Op에서 사용가능한 연산자로는 대략 다음과 같은 것들이 있습니다.
gt | > |
lt | < |
gte | >= |
lte | <= |
in | in |
ne | != |
const result = await User.findAll({
where : {
[Op.and] : [
{ user_age: { [Op.gt] : 10 } },
{ user_id : { [Op.in] : ['aaa', 'bbb'] }}
]
}
});
또한 결과중에서 제한된 수의 Row만 가져오려 한다면 limit를 사용하면 됩니다.
const result = await User.findAll({
limit: 2
});
데이터를 가져오는 것 이외에 특정 데이터를 수정하는건 update를
User.update({
user_age : 100
}, {
where : {
user_id : 'bbb'
}
});
삭제하는건 destory를 사용합니다.
User.destroy({
where : {
user_id : 'bbb'
}
});
위 에제는 모두 Model을 통해 데이터를 조작했는데 필요하다면 특정한 쿼리를 바로 전송해 필요한 결과를 가져오는 것도 가능합니다.
const query = 'select * from tbl_user where user_id = :user_id;';
const result = await Board.sequelize.query(query, {
replacements: { user_id: 'aaa' }
});
query를 Board모델에서 사용하고 있는데 이처럼 다른 모델에서 임의로 raw를 사용할 수 있습니다. 또한 쿼리에서 파라메터변수는 ':[변수명]'으로 지정하며 replacements에서 본래설정값을 넣어주면 쿼리문자체를 깔끔하게 유지할 수 있습니다.
마지막으로 테이블끼리의 join은 include를 통해서 구현할 수 있습니다.
const result = await User.findAll({
include : {
model: UserDetail
}
});
res.send(result[0].dataValues.UserDetail.user_address);
'Server > node.js' 카테고리의 다른 글
[node.js] express-rate-limit(DOS공격 방어하기) (2) | 2021.03.09 |
---|---|
[node.js] JWT 인증 (0) | 2021.03.09 |
[node.js] Express (0) | 2021.03.05 |
[node.js] 패키지 관리 (0) | 2021.03.04 |
[node.js] http (0) | 2021.03.04 |