[Nodejs] Sequelize
Sequelize 연결 방법 및 사용 방법
들어가기 전에..
필자는 Java 환경에서 JPA를 사용해본 경험이 있다. 따라서, Sequelize를 JPA에 빗대어 설명하는 부분이 있을 수 있다. JPA에 관해 공부하고 싶다면 JPA 포스트를 참고하면 좋을 듯 하다.
✏️ Sequelize 란
Sequelize
: Node.js 환경에서 사용하는 ORM(Object-Relational Mapping) 라이브러리
- 데이터베이스의 테이블을 객체처럼 다룰 수 있다.
- SQL 쿼리를 작성하지 않고도 데이터베이스 작업을 수행할 수 있다.
■ 기능
Sequelize는 다음과 같은 기능을 제공한다.
-
모델 정의: 데이터베이스 테이블을 모델로 정의한다. 모델은 자바스크립트 객체처럼 다룰 수 있으며, 각 모델은 테이블의 컬럼과 매핑된다.
-
쿼리 작성: SQL 쿼리를 자바스크립트 코드로 작성할 수 있다. CRUD 작업을 위한 메서드를 제공한다. (JPA의
JpaRepository와 비슷한 기능이다!) -
관계 설정: 테이블 간의 관계(일대일, 일대다, 다대다)를 설정할 수 있다.
-
마이그레이션: 데이터베이스 스키마 변경을 관리할 수 있는 마이그레이션 기능을 제공.
■ JPA와의 차이점
| 차이점 | JPA | Sequelize |
|---|---|---|
| 객체 생성 | @Entity 어노테이션을 사용하여 엔티티 클래스를 정의 |
모델을 정의 |
| 관계 설정 | @OneToMany, @ManyToOne 등의 어노테이션 사용 | .hasMany, .belongsTo 같은 관계형 메소드 사용 |
| CRUD | JpaRepository를 상속받아서 CRUD 작업 수행 | 각 모델이 CURD 메서드를 포함하고 있음 |
이 밖에도 다양한 차이점이 존재한다.
✏️ 사용방법
JPA의 경우, 아래와 같은 순서로 개발을 진행한다.
JPA
- 프로젝트 설정
- Entity 정의
- DTO 정의 (생략 가능)
- Repository 정의
- Service 정의
- Controller 연결
Sequelize는 어떨까? Sequelize는 다음과 같은 순서로 개발이 진행된다.
Sequelize
- DB 연결 설정
- 모델 정의
- Service 정의
- Controller 연결
- 라우팅 설정
- 애플리케이션 설정
각 과정을 더 자세히 알아보도록 하자.
1. DB 연결 설정
Node.js 프로젝트를 설정하고 Sequelize 패키지를 설치한다.
1
2
3
npm init -y
npm install sequelize sequelize-cli mysql2
그런 다음, 데이터베이스 설정 파일(ex: config/database.js)을 생성한다.
1
2
3
4
5
6
7
8
const { Sequelize } = require('sequelize');
const sequelize = new Sequelize('database', 'username', 'password', {
host: 'localhost',
dialect: 'mysql',
});
module.exports = sequelize;
2. 모델 정의
User 모델을 생성한다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
const { DataTypes } = require('sequelize');
const sequelize = require('../config/database');
const User = sequelize.define('User', {
id: {
type: DataTypes.INTEGER,
autoIncrement: true,
primaryKey: true,
},
username: {
type: DataTypes.STRING,
allowNull: false,
},
birthday: {
type: DataTypes.DATE,
allowNull: false,
},
});
module.exports = User;
DTO를 생성하는 것은 일단 생략한다.
3. Service 정의
비즈니스 로직을 처리하는 서비스 계층을 정의한다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
const User = require('../models/User');
class UserService {
async createUser(data) {
const user = await User.create(data);
return user;
}
async getUserById(id) {
const user = await User.findByPk(id);
return user;
}
...
}
module.exports = new UserService();
Sequelize는 CRUD 작업을 위한 메서드를 제공한다.
제공되는 메소드는 다음과 같다.
| 메소드 | 인수 | 반환 값 |
|---|---|---|
| create | 생성할 레코드의 데이터 객체 | 생성된 레코드의 모델 인스턴스 |
| findByPK | 조회할 레코드의 기본 키 값, [options: 조회 시 포함할 옵션 객체] | 조회된 모델의 인스턴스 |
| findAll | [options] | 조회된 레코드의 배열 |
| findOne | [options] | 조회된 레코드 모델의 인스턴스 |
| update | 업데이트할 데이터 객체, [options] | 업데이트된 레코드 수 |
| destroy | [options] | 삭제된 레코드의 수 |
옵션 객체는 다양한 메소드에 인수로 전달되어 쿼리의 동작을 제어하는 데 사용된다. 옵션 객체를 통해 조건, 관계, 페이징 등을 설정할 수 있다.
- 조건 설정
1 2 3 4 5 6
const users = await User.findAll({ where: { username: 'janedoe' } }); // SELECT * FROM Users WHERE username = 'jonedoe';
- 관계 설정 ```javascript // User 테이블과 Post 테이블이 FK로 연결되어 있는 경우 const users = await User.findAll({ include: [Post] });
/* ** SELECT Users., Posts. FROM Users ** LEFT JOIN Posts ON Users.id = Posts.userId; */
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
<br>
### 4. Controller 연결
요청을 처리하고 서비스 계층을 호출하는 컨트롤러 정의
```javascript
const userService = require('../services/userService');
class UserController {
async createUser(req, res) {
try {
const user = await userService.createUser(req.body);
res.status(201).json(user);
} catch (error) {
res.status(500).json({ error: error.message });
}
}
async getUserById(req, res) {
try {
const user = await userService.getUserById(req.params.id);
if (user) {
res.status(200).json(user);
} else {
res.status(404).json({ message: 'User not found' });
}
} catch (error) {
res.status(500).json({ error: error.message });
}
}
...
}
module.exports = new UserController();
5. 라우팅 설정
Express를 사용하여 라우팅 설정
1
2
3
4
5
6
7
8
9
const express = require('express');
const userController = require('../controllers/userController');
const router = express.Router();
router.post('/users', userController.createUser);
router.get('/users/:id', userController.getUserById);
...
module.exports = router;
6. 애플리케이션 설정
app.js 파일을 설정하여 전체 애플리케이션을 초기화하고 실행한다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
const express = require('express');
const { sequelize } = require('./models'); // Sequelize 인스턴스를 가져옵니다.
const userRoutes = require('./routes/userRoutes'); // 사용자 정의 라우터를 가져옵니다.
const app = express();
app.use(express.json()); // JSON 형식의 요청 본문을 파싱합니다.
app.use('/api', userRoutes); // 라우팅 설정
const PORT = process.env.PORT || 3000;
// 데이터베이스 동기화 및 서버 시작
sequelize.sync({ force: false })
.then(() => {
console.log('Database synchronized');
app.listen(PORT, () => {
console.log(`Server is running on port ${PORT}`);
});
})
.catch(error => {
console.error('Unable to connect to the database:', error);
});
여기서, sequelize.sync() 부분을 자세히 살펴보자.
sequelize.sync() 는 DB와 모델간의 동기화를 처리하는 기능을 한다.
JPA의 hibernate.hbm2ddl.auto 설정과 유사하지만, 명시적으로 호출하여 동기화를 수행한다.
sequelize.sync()의 옵션
- force: 기존 테이블 삭제 후 새로 생성
- alter: 테이블을 삭제하지 않고, 기존 테이블의 구조를 변경하여 모델과 일치시킴
- logging: 동기화 과정에서 실행되는 SQL 쿼리 로깅
✏️ 연관 관계 매핑
JPA는 @OneToMany, @ManyToOne 등의 어노테이션 사용해서 연관관계 매핑을 하고,
Sequelize는 .hasMany, .belongsTo 같은 관계형 메소드 사용한다.
예를 들어, User 테이블과 Post 테이블을 1:N 관계로 매핑하려면 다음과 같이 설정하면 된다.
1
2
3
4
5
6
7
8
9
10
const User = require('./User');
const Post = require('./Post');
// User는 여러 개의 Post를 가질 수 있습니다.
User.hasMany(Post, { foreignKey: 'userId' });
// Post는 하나의 User에 속합니다.
Post.belongsTo(User, { foreignKey: 'userId' });
module.exports = { User, Post };
■ 옵션
연관관계를 설정할 때 다양한 옵션을 사용할 수 있다.
-
- foreignKey
- 자식 모델에 설정될 외래 키를 지정한다.
1 2 3 4
User.hasMany(Post, { foreignKey: 'userId' }); // Post 테이블에 존재하는 userId 필드를 FK로 사용하겠다.
-
- sourceKey
- 부모 모델의 어떤 키를 왜래 키로 사용할지 지정한다.
1 2 3 4 5 6
User.hasMany(Post, { foreignKey: 'userId', sourceKey: 'id' }); // Post 테이블에 존재하는 userId 필드를 FK로 사용하겠다. // Post 테이블의 userId 필드는 User 테이블의 id와 연결된다.
-
- as
- 관계에 대한 별칭을 지정한다.
1 2 3
User.hasMany(Post, { as: 'Articles' });
-
- targetKey
- 자식 모델이 참조할 부모 모델의 키를 지정한다.
sourceKey는
hasMany메소드에서 사용되고, targetKey는belongsTo메소드의 옵션으로 사용된다.1 2 3 4
Post.belongsTo(User, { foreignKey: 'UserId', targetKey: 'id', });