Node.js Mongoose 사용해보기 - 2
2018-06-18 21:49:34

node.js에서 mongoose로 mongodb update, delete, join, group 정리한 글

문서 갱신

문서를 업데이트 하는 방법이 몇 개 잇다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
router.get('/update1', (req, res) => {
userModel.findOne({ userName: '날강두' }, (err, User) => {
if (err) {
res.json(err);
} else if (User.length == 0) {
res.json('데이터가 없습니다.');
} else {
User.position = '먹튀';
User.save((err, UpdateUser) => {
if (err) {
req.json(err);
} else {
res.json(UpdateUser);
}
});
}
});
});

findOne에서 문서를 조회 후 콜백 받은 데이터에 바꿀 꺼 넣고 세이브 하면 갱신 된다.

1
2
3
4
5
6
7
8
9
10
{
"createAt": "2019-08-08T15:37:45.028Z",
"_id": "5d4c41cc2a822207ecb315e8",
"userName": "크리스티아누 날강두",
"userNum": 7,
"position": "먹튀",
"userMail": "날강두@유벤투스.com",
"team": "5d4c412caf60c707c225565d",
"__v": 0
}

이전엔 update 하나 엿는데 updateOne, updateMany로 바뀌엇다 -_-

뭔가 sql의 update set … 느낌 나는 방법으로도 갱신 할 수 잇다.

1
2
3
4
5
6
7
8
9
10
11
12
13
// @TODO: 통상적인 update
router.get('/update2', (req, res) => {
userModel.updateOne(
{ userName: '크리스티아누 날강두' },
{ $set: { userMail: '먹튀@유벤투스.com' } },
(err) => {
if (err) {
res.json(err);
} else {
res.json('update success');
}
});
});

updateOne은 뭐 조건에 맞는 데이터 중 첫 번째 데이터만 갱신 한다는 것.

그리고 이거는 따로 갱신된 데이터를 콜백으로 못 받는다.

결과

여러 문서를 갱신할려면 updateMany를 쓰면 된다.

1
2
3
4
5
6
7
8
9
10
11
12
13
/ @TODO: 통상적인 update
router.get('/update3', (req, res) => {
userModel.updateMany(
{ position: '공격수' },
{ $set: { userNum: 999 } },
(err) => {
if (err) {
res.json(err);
} else {
res.json('update success');
}
});
});

마찬가지로 콜백 받는 데이터가 업음.

결과

어휴 많다. 마지막으로 업데이트 하는 거 하나 더 적음

1
2
3
4
5
6
7
8
9
10
11
12
13
router.get('/update4', (req, res) => {
userModel.findOneAndUpdate(
{ userName: '크리스티아누 날강두' },
{ $set: { userMail: '예전우리형@유벤투스.com' } },
{ new: true }, // new: true를 꼭 적어줘야 결과 반환이 됨
(err, data) => {
if (err) {
res.json(err);
} else {
res.json(data);
}
});
});

조건에 맞는 데이터 중에 첫 번째 데이터를 가져와서 갱신을 함. 나름 편리하다.

결과도 콜백으로 받아 볼수도 잇다.

1
2
3
4
5
6
7
8
9
10
{
"createAt": "2019-08-08T15:37:45.028Z",
"_id": "5d4c41cc2a822207ecb315e8",
"userName": "크리스티아누 날강두",
"userNum": 7,
"position": "먹튀",
"userMail": "예전우리형@유벤투스.com",
"team": "5d4c412caf60c707c225565d",
"__v": 0
}

근데 예전 버전에선 안 떳는데 아마 5.X부터 생겻나봄. deprecated 어쩌구가 뜬다.

해결 방법은 mongoose connect에서 하나 추가 해주면 안 뜸.

1
2
3
4
const mongodb = mongoose.connect('mongodb://localhost:27017/localtest', {
useNewUrlParser: true,
useFindAndModify: false // 이거 추가 하고 false로 하면 된다.
});

delete

이전엔 remove로 했는데 문서 보니 얘도 deleteOne, deleteMany가 생김ㅋ 컨셉인가?!

암튼 해보자.

1
2
3
4
5
6
7
8
9
10
// @TODO: 조건에 맞는 첫 번째 문서를 삭제할 때. 새로 생겻군...
router.get('/deleteOne', (req, res) => {
userModel.deleteOne({ userName: '크리스티아누 날강두' }, (err) => {
if (err) {
res.json(err);
} else {
res.json('삭제됨.');
}
});
});

뭐 조건에 맞는 첫 번째 데이터 삭제임. 로컬에 잇는 날강두 문서가 삭제되엇다.

deleteMany는 뭐 여러 개 삭제하는거겟지..

1
2
3
4
5
6
7
8
9
10
// @TODO: 조건에 맞는 여러 데이터를 삭제 할 때. 이거도 마찬가지로 새로 생겻구먼..
router.get('/deleteMany', (req, res) => {
userModel.deleteMany({}, (err) => {
if (err) {
res.json(err);
} else {
res.json('삭제됨.');
}
});
});

조건 빼고 지워봣더니 다 지워지네. ㅇㅋ

join

mongodb는 nosql이라 일단 관계를 지양 하는데 그래도 필요? 할 때 쓰라고 만든거 같음.

populate랑 lookup 으로 이전에 조인을 햇엇는데 지금도 잘 되는지 확인 해보자.

조인을 하기 위해 팀 데이터를 저장할 컬렉션을 하나 미리 만드럿엇음.

1
2
3
4
5
6
7
8
9
10
11
12
13
'use strict';

const mongoose = require('mongoose');
const Schema = mongoose.Schema;

const teamSchema = new Schema({
teamName: {
type: String,
required: true
}
});

module.exports = mongoose.model('teams', teamSchema);

더미

근데 조인을 할려면 뭔가 데이터가 잇어야 하니 미리 더미 데이터를 만들어 두엇고

저번에 유저 도큐먼트를 만들 때 위의 생성 값으로 넣어준거임.

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

'use strict'

const mongoose = require('mongoose');
const Schema = mongoose.Schema;

const userSchema = new Schema({ // 유저 스키마 정의
userName: {
type: String,
required: [true, '아이디는 필수입니다.'] // 필수 값 validation
},
userNum: {
type: Number,
required: [true, '등번호는 필수입니다.'] // 필수 값 validation
},
position: String,
userMail: String,
team: {
type: Schema.Types.ObjectId, // populate를 이용하기 위한 타입 처리
ref: 'teams'
},
createAt: {
type: Date,
default: Date.now() // 기본 값 오늘날짜로 설정
}
});

module.exports = mongoose.model('users', userSchema);

저번 글에 적은 유저 스키마에 team이라는 프로퍼티가 rdb에서 fk 설정 하듯이

그런 느낌인데 type은 mongo의 고유 ID, 참조 테이블은 팀 테이블이다.

즉 teams 컬렉션의 고유 아디를 fk로.

후 이제 populate를 이용해 join이 된 결과를 보자.

1
2
3
4
5
6
7
8
9
router.get('/populate', (req, res) => {
userModel.find({}).populate('team').exec((err, data) => {
if (err) {
res.json(err);
} else {
res.json(data);
}
});
});

결과는 이러 하다.

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
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
[
{
"createAt": "2019-08-08T15:42:53.213Z",
"_id": "5d4c4428ebc6bc081f7a83c1",
"userName": "크리스티아누 날강두",
"userNum": 7,
"position": "공격수",
"userMail": "날강두@유벤투스.com",
"team": {
"_id": "5d4c412caf60c707c225565d",
"teamName": "유벤투스",
"__v": 0
},
"__v": 0
},
{
"createAt": "2019-08-08T15:42:53.213Z",
"_id": "5d4c442debc6bc081f7a83c2",
"userName": "모하메드 살라",
"userNum": 11,
"position": "공격수",
"team": {
"_id": "5d4c412caf60c707c225565e",
"teamName": "리버풀",
"__v": 0
},
"userMail": "이집트메시@리버풀.com",
"__v": 0
},
{
"createAt": "2019-08-08T15:42:53.213Z",
"_id": "5d4c442debc6bc081f7a83c3",
"userName": "피에르에메릭 오바메양",
"userNum": 17,
"position": "공격수",
"team": {
"_id": "5d4c412caf60c707c225565f",
"teamName": "아스날",
"__v": 0
},
"userMail": "득점왕@아스날.com",
"__v": 0
},
{
"createAt": "2019-08-08T15:42:53.213Z",
"_id": "5d4c442debc6bc081f7a83c4",
"userName": "메수트 외질",
"userNum": 11,
"position": "미드필더",
"team": {
"_id": "5d4c412caf60c707c225565f",
"teamName": "아스날",
"__v": 0
},
"userMail": "노랑머리@아스날.com",
"__v": 0
}
]

선수 밑에 팀이 다 종속으로 들어가 있다.

암튼 populate를 쓰려면 미리 스키마에서 join 하기 전에 타입을 정해놔야 하고,

타입을 objectId 외에는 사용할 수가 없다.

다른 방법으로 좀 전의 조인을 반대로 해보자.

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
router.get('/lookup', (req, res) => {
teamModel.aggregate([
// {
// "$match": { // 조건절 쓰고 싶으면 여기에.
//
// }
// },
{
"$sort": {
"_id": 1
}
},
{
"$lookup": {
"localField": "_id",
"from": "users",
"foreignField": "team",
"as": "users"
}
}
]).exec((err, data) => {
if (err) {
res.json(err);
} else {
res.json(data);
}
});
});

결과는 이러하다.

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
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
[
{
"_id": "5d4c412caf60c707c225565d",
"teamName": "유벤투스",
"__v": 0,
"users": [
{
"_id": "5d4c4428ebc6bc081f7a83c1",
"createAt": "2019-08-08T15:42:53.213Z",
"userName": "크리스티아누 날강두",
"userNum": 7,
"position": "공격수",
"userMail": "날강두@유벤투스.com",
"team": "5d4c412caf60c707c225565d",
"__v": 0
}
]
},
{
"_id": "5d4c412caf60c707c225565e",
"teamName": "리버풀",
"__v": 0,
"users": [
{
"_id": "5d4c442debc6bc081f7a83c2",
"createAt": "2019-08-08T15:42:53.213Z",
"userName": "모하메드 살라",
"userNum": 11,
"position": "공격수",
"team": "5d4c412caf60c707c225565e",
"userMail": "이집트메시@리버풀.com",
"__v": 0
}
]
},
{
"_id": "5d4c412caf60c707c225565f",
"teamName": "아스날",
"__v": 0,
"users": [
{
"_id": "5d4c442debc6bc081f7a83c3",
"createAt": "2019-08-08T15:42:53.213Z",
"userName": "피에르에메릭 오바메양",
"userNum": 17,
"position": "공격수",
"team": "5d4c412caf60c707c225565f",
"userMail": "득점왕@아스날.com",
"__v": 0
},
{
"_id": "5d4c442debc6bc081f7a83c4",
"createAt": "2019-08-08T15:42:53.213Z",
"userName": "메수트 외질",
"userNum": 11,
"position": "미드필더",
"team": "5d4c412caf60c707c225565f",
"userMail": "노랑머리@아스날.com",
"__v": 0
}
]
}
]

lookup이 원래 관계를 맺으라고 나온 기능이 아니고 집계 관련으로 알고 잇는데

뭐 암튼 관계는 왠만 하면 쓰지 말자.

아 추가로 위에 $lookup 밑에

1
{ "$unwind": "$users" }

추가하면 아스날 처럼 배열에 잇던 애들의 결과가 분리된다. 뭐 쓸 일이 언젠가 잇을래나?

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
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
[
{
"_id": "5d4c412caf60c707c225565d",
"teamName": "유벤투스",
"__v": 0,
"users": {
"_id": "5d4c4428ebc6bc081f7a83c1",
"createAt": "2019-08-08T15:42:53.213Z",
"userName": "크리스티아누 날강두",
"userNum": 7,
"position": "공격수",
"userMail": "날강두@유벤투스.com",
"team": "5d4c412caf60c707c225565d",
"__v": 0
}
},
{
"_id": "5d4c412caf60c707c225565e",
"teamName": "리버풀",
"__v": 0,
"users": {
"_id": "5d4c442debc6bc081f7a83c2",
"createAt": "2019-08-08T15:42:53.213Z",
"userName": "모하메드 살라",
"userNum": 11,
"position": "공격수",
"team": "5d4c412caf60c707c225565e",
"userMail": "이집트메시@리버풀.com",
"__v": 0
}
},
{
"_id": "5d4c412caf60c707c225565f",
"teamName": "아스날",
"__v": 0,
"users": {
"_id": "5d4c442debc6bc081f7a83c3",
"createAt": "2019-08-08T15:42:53.213Z",
"userName": "피에르에메릭 오바메양",
"userNum": 17,
"position": "공격수",
"team": "5d4c412caf60c707c225565f",
"userMail": "득점왕@아스날.com",
"__v": 0
}
},
{
"_id": "5d4c412caf60c707c225565f",
"teamName": "아스날",
"__v": 0,
"users": {
"_id": "5d4c442debc6bc081f7a83c4",
"createAt": "2019-08-08T15:42:53.213Z",
"userName": "메수트 외질",
"userNum": 11,
"position": "미드필더",
"team": "5d4c412caf60c707c225565f",
"userMail": "노랑머리@아스날.com",
"__v": 0
}
}
]

그룹

aggregate에 내장되어 있는 메소드 중에 group을 사용하면 된다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
router.get('/group', (req, res) => {
userModel.aggregate([
{
"$group": { // 고정된 형식.
"_id": { "position": "$position" }, // { 'alias ( 결과 필드명 )': '$사용할 필드명' }
"count": { "$sum": 1 } // 집계 메소드 따로 Count가 업음.
}
}
]).exec((err, data) => {
if (err) {
res.json(err);
} else {
res.json(data);
}
});
});

결과는 이러하다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
[
{
"_id": {
"position": "미드필더"
},
"count": 1
},
{
"_id": {
"position": "공격수"
},
"count": 3
}
]

여기서의 _id는 $group이 사용하는 고정된 형식. 바꾸면 에러 난다..

이 밖에도 aggregate는 다양한 함수들이 있으니 차차 필요 할 때마다

번역기를 돌려서 문서를 봐야겠다..

휴 끝.

참고