ORM(Object Relational Mapping) 이란?
객체를 통해 간접적으로 데이터베이스를 다루는 방식
직접 SQL 쿼리를 작성하지 않고 프로그래밍 언어를 이용하여 DB에 접근할 수 있음
- 장점
1. 개발 코드와 DB의 종속성 분리
2. 생산성 향상 및 유지보수 용이
3. 특정 DBMS에 종속적이지 않아 Object에만 집중 가능
(= 다른 DBMS간에도 문법이 호환되므로 프로젝트 진행시 다른 종류의 DBMS로 교체할 경우 편리)
- 단점
1. 커스터마이징 및 디버깅이 어려움
2. 실행 속도가 raw 방식보다 느림
3. 복잡한 쿼리 작성이 어려움
- Express에서 MySQL ORM 모듈로는 Sequelize와 TypeORM, Knex 등이 있음
① Sequelize
→ PostgreSQL, MySQL, MariaDB, SQLite, MSSQL 지원
② TypeORM
→ MySQL, MariaDB, PostgreSQL, CockroachDB, SQLite, MSSQL, Oracle, sql.js, MongoDB 지원
③ Knex
→ PostgreSQL, MySQL, SQLite3 지원
Sequelize란?
ORM의 일종으로, 자바스크립트 객체와 데이터베이스의 릴레이션을 매핑해주는 도구
프로그래밍 언어를 사용하여 DB에 접근할 수 있으므로 SQL 문법을 몰라도 된다는 장점을 가짐
Sequelize의 작동 원리
1. Express 내부의 시퀄라이저가 js 파일 실행
2. js 파일 내의 내부 entity 정보를 읽어서 sequelize에 적재
3. Express에서 sequelize를 이용하여 DB에 접근 (동기/비동기)4. 접근한 DB 스키마에 CRUD 실행
Express - Sequelize ORM 연동
1. sequelize 및 sequelize cli 설치
# npm i sequelize
# npm i -g sequelize-cli
2. sequelize 명령어를 이용하여 폴더 생성
# sequelize init
3. 생성된 config 폴더에 있는 config.json을 config.js으로 변경 후 아래와 같이 내용 수정
: dotenv를 사용하기 위해 *.js로 수정해야 함
import dotenv from 'dotenv'
dotenv.config()
const development = {
dialect: 'mysql',
host : process.env.DB_HOST,
port: process.env.DB_PORT,
username : process.env.USER_NAME,
password : process.env.USER_PASSWD,
database : process.env.DB_NAME
}
const production = {
dialect: 'mysql',
host : process.env.DB_HOST,
port: process.env.DB_PORT,
username : process.env.USER_NAME,
password : process.env.USER_PASSWD,
database : process.env.DB_NAME
}
const test = {
dialect: 'mysql',
host : process.env.DB_HOST,
port: process.env.DB_PORT,
username : process.env.USER_NAME,
password : process.env.USER_PASSWD,
database : process.env.DB_NAME
}
export default { development, production, test }
4. models 폴더 내 index.js에서 config.json → config.js로 수정
5. MySQL의 이미 생성된 스키마 목록을 읽어오기 위해 sequelize-auto 설치
: sequelize를 이용하여 직접 테이블을 모델링하는 방법도 있지만, 나는 이미 생성된 테이블을 불러와서 사용하고 싶어서 이 방식을 사용했다.
# npm i sequelize-auto
6. 아래 내용으로 js 파일 생성 후 실행
const SequelizeAuto = require('sequelize-auto')
const auto = new SequelizeAuto('DB 이름', '사용자 이름', '사용자 비밀번호', {
host: '115.85.182.54', // DB Host 주소
port: '3306', // 포트 번호
dialect: 'mysql' // 사용하는 DBMS 종류
})
auto.run()
6번까지 수행하면 자동으로 생성된 sequelize 모델들을 얻을 수 있다!
7. router에서 sequelize를 사용하여 테이블 접근 및 사용
: router 파일을 열고 다음과 같이 사용하면 된다.
const express = require('express')
const router = express.Router()
const model = require('../database/models')
const findAllUsers = async () => await models['tb_user'].findAll()
router.get('/test', async (req, res) => {
res.json(await findAllUsers())
})
module.exports = router
Sequelize 문법 정리
1. SELECT(findOne, findAll)
- findAll은 조건을 만족하는 모든 데이터를 가져옴
- findOne은 조건을 만족하는 첫 번째 데이터만 가져옴(= limit 1)
- where절 조건 지정 가능
: Op 모듈을 import해서 and와 or 연산자를 사용할 수 있음
const { Op } = require('sequelize')
model['table'].findAll({
where: {
column: 123 // select * from table where column = 123
}
})
model['table'].findAll({
where: {
[Op.or]: [
{column: 123},
{column: 456},
] // select * from table where column = 123 or column = 456
}
})
2. 조인
: 기본적으로 left join(required: false)이며, inner join을 사용하려면 required: true 옵션 추가
여러 테이블을 조인하려면 include 안에 {} 형태로 여러 개 넣어주면 된다.
models['table1'].findAll({
include: [{
model: models['table2'],
required: true,
as: 'alias' // alias 지정 가능
attributes: ['title', 'content'] // select할 컬럼 선택
}]
})
3. limit / offset 설정
: limit은 한 번에 가져올 row의 개수, offset은 가져오려는 row의 시작 인덱스 값
페이지네이션에 사용 가능
model.findAll({
where: { ... },
limit: 10,
offset: 0
})
4. 정렬(order by)
: order 속성 이용
model.findAll({
order: [
['age', 'desc'] // 정렬할 컬럼명과 오름차순/내림차순 구분
],
where: { ... }
})
5. 특정 컬럼 선택
: attributes 속성 이용
model.findAll({
attributes: [ 'name' ], // 선택할 컬럼 목록 입력
where: { ... }
})
6. 시퀄라이즈 결과값에서 dataValues 부분만 가져오기
: raw 속성 이용
await models['tb_category'].findAll({
raw: true, // raw 값이 true이면 데이터 속성만 리턴
})
cf) Op 모듈
: Op 모듈을 import해서 and, or, eq, ne 등의 다양한 연산자를 사용할 수 있음
① and / or
: where절에서 [Op.and]: [{ a: 5 }, { b: 6 }] 형태로 사용 가능
[Op.and]: [{ a: 5 }, { b: 6 }] // a = 5 and b = 6
[Op.or]: [{ a: 5 }, { b: 6 }] // a = 5 or b = 6
// 혹은 이렇게도 사용 가능
await models['tb_category'].count({
where: {
a: {
[Op.or]: [5, 6] // a in [5, 6]
}
}
raw: true,
})
② eq / ne
[Op.eq]: 3 // = 3
[Op.ne]: 20 // != 20
③ not
[Op.not]: true // IS NOT true
④ is
[Op.is]: null // IS NULL
⑤ gt / gte
: gt는 초과, gte는 이상을 나타냄
[Op.gt]: 6 // > 6
[Op.gte]: 6 // >= 6
⑥ lt / lte
: lt는 미만, lte는 이하를 나타냄
[Op.lt]: 6 // < 6, 미만
[Op.lte]: 6 // <= 6, 이하
⑦ between / notBetween
[Op.between]: [6, 10] // BETWEEN 6 AND 10
[Op.notBetween]: [11, 15] // NOT BETWEEN 11 AND 15
⑧ in / not in
[Op.in]: [1, 2] // IN [1, 2]
[Op.notIn]: [1, 2] // NOT IN [1, 2]
⑨ startsWith / endsWith
[Op.startsWith]: 'hat' // LIKE 'hat%'
[Op.endsWith]: 'hat' // LIKE '%hat'
⑩ like / notLike
[Op.like]: 'hat%' // LIKE 'hat%'
[Op.notLike]: '%hat' // NOT LIKE '%hat'
⑪ substring(=LIKE)
: like 구문처럼 사용 가능
[Op.substring]: 'hat' // LIKE '%hat%'
⑫ regexp / notRegexp
: MySQL과 PostgreSQL에서만 사용 가능
[Op.regexp]: '^[h|a|t]' // REGEXP/~ '^[h|a|t]'
[Op.notRegexp]: '^[h|a|t]' // NOT REGEXP/~ '^[h|a|t]'
→ https://codingmania.tistory.com/555 블로그를 참고하여 작성했습니다.
cf) fn 모듈
: sequelize의 fn 모듈을 import해서 sum, count, min, max 등의 다양한 집계 함수를 사용할 수 있음
① sum
const { Op, fn, col } = require('sequelize')
model.findAll({
attributes: ['itemId', [sequelize.fn('sum', sequelize.col('amount')), 'total']],
where: { ... }
})
② count, min / max 등 기타
: sum과 같은 방법으로 사용하면 됨
2. INSERT
model.create({
컬럼명1: '값1',
컬럼명2: '값2',
컬럼명3: '값3',
...
})
3. UPDATE
model.update(
{
바꿀 컬럼명: '바꿀 값',
...
}, {
where: { ... }
}
)
4. DELETE
model.destroy({
where: { ... }
})
5. CREATE
model.define('tb_board', { // 첫 번째 파라미터는 테이블명
board_sn: {
type: Sequelize.INTEGER,
primaryKey: true,
autoIncrement: true,
},
title: {
type: Datatypes.TEXT,
allowNull: false,
},
content: {
type: Datatypes.TEXT,
allowNull: true,
},
view_num: {
type: Datatypes.INTEGER.SIGNED,
allowNull: false,
},
{
timestamps: true, // 이 값을 true로 줄 경우, createAt과 updateAt 컬럼 자동 생성
}
})
cf) literal
: sequelize의 fn 모듈을 import해서 SQL raw 쿼리를 날릴 수 있음
where, attributes 등에 사용 가능
await models['tb_item'].findOne({
attributes: [
[literal(`(select AUTO_INCREMENT from information_schema.tables where table_name='tb_item')`), 'item_sn']
],
raw: true,
})
not association 오류 해결
SequelizeEagerLoadingError: [테이블명1] is not associated to [테이블명2]
직접 모델을 생성한 경우에는 말 그대로 association을 선언하지 않아서 발생한 오류이고, sequelize-auto를 통해 시퀄라이즈 모델을 생성한 경우에는 자동 생성된 association을 시퀄라이즈가 인식하지 못해서 발생한다.
→ 해결 방법
1. sequelize-auto 사용 시
init-models.js에 있는 association 코드들을 index.js로 이동하면 됨
2. 직접 만든 경우
sequelize-auto를 사용하지 않고 직접 모델을 생성한 경우에는 index.js에 아래처럼 적절하게 association을 설정해 주면 된다.
Object.keys(db).forEach(modelName => {
if (db[modelName].associate) {
db[modelName].associate(db)
}
})
// 여기에 작성
db['tb_like'].belongsTo(db['tb_user'], { foreignKey: "user_id"})
db['tb_user'].hasMany(db['tb_like'], { foreignKey: "user_id"})
db.sequelize = sequelize
db.Sequelize = Sequelize module.exports = db
'Backend > Node.js' 카테고리의 다른 글
Node.js 23 출시 - 어떤 기능이 추가되었을까요? (1) | 2024.10.18 |
---|---|
Node.js - commonJS vs ES Modules (0) | 2023.02.20 |
Node.js - express 특징 및 사용법 정리(공식 문서 참고) (0) | 2021.10.10 |