User Collection에 Place Collection 여러개를 push 하고 싶을 때 연결하는 방법.
Place Schema
const mongoose = require('mongoose');
const Schema = mongoose.Schema;
const placeSchema = new Schema({
// 1단계 스키마
title:{type:String, required:true},
description:{type:String, required:true},
image:{type:String, required:true},
address:{type:String, required:true},
location: {
lat:{type:Number, required:true},
lng:{type:Number, required:true}
},
// MongoDB에게 creator는 User Collection내에 존재하는 ID임을 알려주고(mongoose.Types.ObjectId) ref를 통해 User Schema와 연결한다.
// collection끼리 연결해주는 mongoose의 populate method에 중요하게 사용된다.
creator:{type: mongoose.Types.ObjectId, required:true, ref: 'User'}
})
// model은 생성자 함수를 반환하기에 대문자로 시작하고, 단수형을 사용한다.
// model의 첫번째 인수는 컬렉션의 이름이 된다.
// 2번째 인수로 작성한 스키마를 인수로 전달하여, place 생성자 함수를 통해
// 생성된 컬렉션에 들어가는 documents들이 해당 스키마의 구조를 갖게 만든다.
module.exports = mongoose.model('Place',placeSchema)
User Schema
const mongoose = require('mongoose');
// mongoose unique validator를 통해서 unique이메일을 확인하고, 추가할 수 있다.
const uniqueValidator = require('mongoose-unique-validator')
const Schema = mongoose.Schema;
const userSchema = new Schema({
// 1단계 스키마
name:{type:String, required:true},
// unique를 통해서 쿼리 프로세스 속도를 빠르게 한다.
email:{type:String, required:true, unique: true},
password:{type:String, required:false, minlength: 8},
image: {type: String, required: true},
// User collection과 Place collection을 연결한다.
// 한 user가 여러개의 places를 가질 수 있으므로 배열 형태로 저장.
places: [{type: mongoose.Types.ObjectId, required:true, ref: 'Place'}]
})
//unique-validator는 unique의 존재 유무도 파악한다.
userSchema.plugin(uniqueValidator);
// model은 생성자 함수를 반환하기에 대문자로 시작하고, 단수형을 사용한다.
// model의 첫번째 인수는 컬렉션의 이름이 된다.
// model의 2번째 인수로 작성한 스키마에 객체를 인수로 전달하여, place 생성자 함수를 통해 생성된 컬렉션에 들어가는 documents들이 해당 스키마의 구조를 갖게 만든다.
module.exports = mongoose.model('User',userSchema)
User - Place 간 연결
Transaction - session의 관계 이해
// place schema로 생성한 Place생성자 함수로 객체 인자 전달
const createdPlace = new Place({
title,
description,
address,
location:coordinates,
image: 'https://lh5.googleusercontent.com/p/AF1QipMH_S511fGMCBX4Wjx5mgNamkO_oycZScZbp4Oe=w426-h240-k-no',
creator
})
let user;
try{
user = await User.findById(creator);
}catch(err){
const error = new HttpError('Creating place failed. please try again',500)
return next(error)
}
if(!user){
const error = new HttpError('Could not find user for provided id',404);
return next(error);
}
try{
// save method는 promise를 반환하며 스키마 생성자 함수를 통해 추가한 documents를 저장하기 위해 필요한 MongoDB 코드를 처리하는 method이다.
//await createdPlace.save();
// 2개 이상의 Collection에서의 작업 수행.
// Place collection에 creator Document를 업데이트하고,
// User collection의 places에 Place Collection의 places가 등록되게 만들려면 save method로는 할 수 없다.
// 연관되지 않거나 서로 다른 여러개의 작업들을 실행할 수 있어야 한다.
// 그리고 여러개의 작업 중 하나가 실패하면 전체 작업을 실행을 취소할 수도 있어야 한다.
// 즉 하나라도 실패하면 바로 catch문으로 넘어가야 한다.
// 여러개의 작업이 모두 성공할 때에만 문서를 변경하고 싶을 때에는 transactions와 session을 사용해야 한다.
// transaction이 하는 일 : 서로 분리된 여러 작업을 수행하고, 개별적으로 취소할 수 있다.
// transaction은 session을 기반으로 만들어진다.
// 따라서 transaction으로 작업하려면 먼저 session을 시작하고, tarnsaction이 성공하면
// session이 종료된 뒤 transaction이 commit 된다.
// 쉽게 설명하자면, 여러개의 작업들을 하나 씩(transaction) 수행하고,
// 최종적으로 완료 되었을 때에만 tarnsaction이 작업한 결과들을 session이 최종 승인(committed)한다는 것이다.
// 1. transactions을 수행 할 session 생성
const session = await mongoose.startSession()
// 2. transaction start
session.startTransaction();
// 3. 실제 작업 1. createdPlace 객체를 session에 저장한다.
// transactions 전체가 성공해야하기에 먼저 sessions에 저장.
await createdPlace.save({session: session});
// 4. model 간 연결
// 여기서 사용하는 push는 javascript의 arrayMethod가 아닌, mongoose에서 제공하는 method로서 델간의 연결 관계를 설정한다.
// user는 User collection의 creator document다.
user.places.push(createdPlace)
// 5. 실제 작업 2. user에 save한다.
await user.save({session: session})
// 6. transactions 종료
session.commitTransaction();
}catch(err){
const error = new HttpError('Creating place failed, please try again',500)
return next(error);
}
console.log(user);
// 생성은 201
res.status(201).json({place: createdPlace})
}