mongoose 공부하기 [2] - Schemas 파헤치기

김진성·2021년 10월 19일
1

MongoDB

목록 보기
2/7
post-thumbnail

Schemas

Schema 정의하기

모든 Mongoose의 시작은 Schema를 정의하면서 나오게 된다. 각각의 Schema들은 MongoDB collection과 매칭되고 collection의 전체적인 구조를 정의하게 된다.

import mongoose from 'mongoose';
const { Schema } = mongoose;

const blogSchema = new Schema({
	title: String,
    author: String,
    body: String,
    comments: [{body: String, date: Date}],
    date: {type: Date, default: Date.now},
    hidden: Boolean,
    meta: {
    	votes: Number,
        favs: Number
    }
});

위 예시는 블로그를 구성하는 Schema의 구조를 한번 그려봤다. 단순한 1대1 매칭 구조부터, 리스트, 딕셔너리까지 Schema 데이터를 구성하는 것까지 나와있다. 만약 여기서 우리가 새로운 데이터를 넣으고자 할 때 Schema.add()를 활용해 블로그 스키마에 포스팅된 사이트 이름을 넣을 수 있다.

blogSchema.add({site: String})

or

const SiteSchema = new Schema();
SiteSchema.add({site: 'string'})
blogSchema.add(SiteSchema)

모델 생성하기

정의된 스키마를 사용하기 위해서 blogSchema를 Model로 변경하는 작업을 해야 한다. 기본 구조는 mongoose.model(modelName, schema)의 형태로 되어 있다.

const Blog = mongoose.model('Blog', blogSchema);

Ids

기본적으로 Mongoose는 우리가 생성한 스키마에 _id 라는 특성을 추가해준다. Django에서도 모델을 생성하면 자동적으로 index가 붙는 것처럼 Mongoose도 스키마에 번호가 붙여진다.

const schema = new Schema();
schema.path('_id'); // ObjectId { ... }

예시를 들면,
const Model = mongoose.model('Test', schema);
const doc = new Model();
doc._id instanceof mongoose.Types.ObjectId; // true

우리가 doc에서 doc._id가 ObjectId의 인스턴스인지 확인하면 True가 나오는 것처럼 자연스럽게 _id가 추가 된다. 만약, 우리가 자신만의 _id를 만들고자 한다면, 스키마를 생성할 때 _id: Number를 사용하면 된다.

const schema = new Schema({_id: Number});
const Model = mongoose.model('Test', schema);

const doc = new Model();
await doc.save();

doc._id = 1;
await doc.save();

첫번째 await doc.save()_id가 정의되지 않아 저장되지 않지만 두번째에는저장이 된다. 여기서 알 수 있는 점은 Mongoose에서 모든 데이터에는 _id가 붙어야 한다는 것이다.

Instance Methods 생성

Instance Methods는 간단히 설명하자면, 모델에 함수를 붙이는 것이다.

const animalSchema = new Schema({name: String, type: String});

animalSchema.methods.findSimilarTypes = function(cb) {
	return mongoose.model('Animal').find({type: this.type}, cb);
};

const Animal = mongoose.model('Animal', animalSchema);
const dog = new Animal({type: 'dog'});

dog.findSimilarTypes((err, dogs) => {
	console.log(dogs);
});

여기서 animalSchema에다가 findSimilarTypes 함수를 생성하고 새로 생성된 dob instance는 이 함수를 사용할 수 있지만 ES6 arrow function =>this.type로 묶어지는 것을 막기 때문에 여기서는 작동이 되지 않는다.

Statics 함수 생성

앞서 단순한 Methods를 생성한 것처럼 Mongoose에다가 정적인 함수를 생성할 수 있다.

  • schema.statics를 사용해 함수를 선언하기
  • Schme#static()으로 생성하기
animalSchema.statics.findByName = function(name) {
	return this.find({name: new RegExp(name, 'i')});
};

or

animalSchema.static('findByBreed', function(breed){return this.find({breed});});

const Animal = mongoose.model('Animal', animalSchema);
let animals = awati Animal.findByName('fido');
animals = animals.concat(await Animal.findByBreed('Poodel'));

여기서도 마찬가지로 ES6 arrow function =>를 사용하지 않도록 한다.

기본 Query로 데이터 찾기

animalSchema.query.byName = function(name) {
	return this.where({name: new RegExp(name, 'i')})
};

const Animal = mongoose.model('Animal', animalSchema);

Animal.find().byName('fido').exec((err, animals) => {
	console.log(animals);
});

Animal.findOne().byNme('fido').exec((err, animal) => {
	console.log(animal);
});

여기서 find()findOne()는 기본적으로 Mongoose에서 제공해주는 Query 함수이다.

Index를 이용한 분류

MongoDB에서는 secondary indexes를 지원함으로써 우리가 Schema level을 설정하는데 index를 사용할 수 있다.

const animalSchema = new Schema({
	name: String,
    type: String,
    tags: {type: [String], index: true}
});

animalSchema.index({name: 1, type: -1});

여기 나와있는 index를 설명하자면 먼저, name을 기준으로 오름차순(1)으로 정렬을 하고, type을 기준으로 내림차순(-1)으로 정렬을 하는 방식이다. 이외에 우리가 자동적으로 생성(createIndex)되는 index를 처음부터 정의하거나 막을 수 있다. 정의하는 방식을 막는 방법은 아래와 같다.

1. mongoose.connect('mongodb://user:pass@localhost:port/database', {autoIndex: false});
2. mongoose.createConnection('mongodb://user:pass@localhost:port/database', {autoIndex: false});
3. animalSchema.set('autoIndex', false);
4. new Schema({}, {autoIndex: false});

Mongoose는 에러가 발생하거나 인덱스가 이미 생성되었을 때 모델에서 index 이벤트가 생성된다.

// sparse가 false일 때 자동적으로 _id가 생성되기에 그 반대의 경우 오류 가 발생한다.
animalSchema.index({_id: 1}, {sparse: true});
const Animal = mongoose.model('Animal', animalSchema);

Animal.on('index', error => {
	// _id index cannot be sparse가 발생된다.
	console.log(error.message);
})

Virtuals 사용하기

Virtuals는 Spring Boot에서 자동으로 생성해주는 Getter와 Setter의 역할과 같다.

const personSchema = new Schema({
	name: {
    	first: String,
        last: String
    }
});

const Person = mongoose.model('Person', personSchema);

const axl = new Person({
	name: {first: 'Axl', last: 'Rose'}
})

console.log(axl.name.first + ' ' + axl.name.last); // Axl Rose

여기서 우리가 axl의 데이터를 출력하기 위해서는 형식에 맞춰서 코딩을 해줘야 하는 불편함이 존재한다. 이러한 불편함을 Virtual 함수를 만들어 정의를 하고 어느 때에나 가져와서 출력하거나 새롭게 정의할 수 있다.

personSchema.virtual('fullName').
	get(function() {
    	return this.name.first + ' ' + this.name.last;
    }).
    set(function(v) {
    	this.name.first = v.substr(0, v.indexOf(' '));
        this.name.last = v.substr(v.indexOf(' ') + 1);
    });

axl.fullName = 'William Rose';
console.log(axl.fullName); // William Rose

여기서 toJSON()/toObject() 같은 경우 virtual를 가지고 있지 않고 JSON.stringfy
()를 기본적으로 포함하고 있다. 만약 Virtual를 사용할 경우 {virtuals: true}로 정의해야 한다.

Aliases

Aliases는 Virtual의 Set 함수처럼 우리가 Schema를 정의할 때 타입과 명칭을 분류하는 방식을 말한다.

const personSchema = new Schema({
	n: {
    	type: String,
        alias: 'name'
    }
});

const person = new Person({name: 'Val'});
console.log(person); // { n: 'Val' }
console.log(person.toObject({ virtuals: true })); // { n: 'Val', name: 'Val' }
console.log(person.name); // "Val"

person.name = 'Not Val';
console.log(person); // { n: 'Not Val' }

여기서 나온 것처럼 alias는 n 이 또다른 명칭인 name을 가지고 있음을 알 수 있다. 이 뿐만 아니라 Schema 안쪽에 또다른 Schema에다가 alias 방식을 적용할 수 있다.

const childSchema = new Schema({
	n: {
    	type: String,
        alias: 'name'
    }
}, {_id: false});

const parentSchema = new Schema({
	c: childSchema,
    name: {
    	f: {
        	type: String,
            alias: 'name.first' // 안쪽에 선언된 path는 전체적인 경로를 포함하는 것을 권장한다.
        }
    }
})

Options

Schema에는 이외에 다양한 함수 및 변수가 존재함으로 추가적인 공부를 하고자 할때는 아래의 링크를 확인하면 된다.

profile
https://medium.com/@jinsung1048 미디엄으로 이전하였습니다.

0개의 댓글