본문 바로가기

Node.js

ORM(Object Relation Mapping)을 이용해보자!!! 1편 Sequelize.js


시작하기 전에


해당 블로그에 작성되는 글은 주인장의 지극히 주관적인 생각이 다수이며, 대부분의 지식은 구글링을 통해 얻고 있기 때문에 옳지않은 정보가 있습니다. 

잘못된 부분이나 수정해야 하는 부분이 있다면 과감히 덧글을 남겨주세요! 모르는게 많은 새싹입니다



오늘의 주제


오늘의 주제는 ORM(Object Relation Mapping)에 대한 포스팅을 할 예정입니다. 주제를 선정한 특별한 이유는 없고.. 평소 궁금했고 어쩌다 보니 ORM을 이용한 프로젝트의 소스코드를 분석해야하는 일이 생겨서 주제로 선정하게 되었답니다!

ORM이 무엇인지, 어떤 장단점이 있는지, 간단한 예제를 만들어보는 실습위주로 진행하도록 하겠습니다. 재밋게 읽어주세요.



ORM? 넌 누구냐!!!!


ORM은 Object Relation Mapping의 약자로 프로젝트에서 사용하는 객체들의 관계를 RDB에 그대로 매핑하는 것입니다. 제가 사용하는 Node.js는 Javascript 기반이기 때문에 좀 더 OOP다운 프로그래밍을 도와줄 수 있답니다.
물론, javascript는 객체지향이다 함수지향이다. 사용자간에 많은 의견이 있지만, 개발자가 사용하기에 따라 어떤 프로그래밍 방법을 지향하는지 바뀔 수 있다고 생각합니다. 계란을 삶아먹고 구워먹고 부쳐먹는 것처럼요...ㅎㅎㅎ


ORM의 장점


첫번째, 전체적인 생산성이 향상될 수 있습니다. SQL쿼리를 작성하는 구조가 아닌 객체의 프로퍼티를 이용하기 때문에 좀 더 향상된 퍼포먼스를 낼 수 있습니다. Model부분에서 확실한 분리가 가능하기 때문에, 객체에 대한 재사용이 편리하고, 코드양 자체가 줄어들며 가독성 면에서 장점이 있기도 합니다.

두번째, 코드 전반에 대한 유지 및 보수가 편리해집니다.

세번째, DBMS에 종속적이지 않고 다양한 DBMS에 적용이 가능합니다. 현재 제가 사용하는 Sequelize.js에서는 mysql, sqlite, postgress, mssql를 지원합니다.


ORM의 단점


첫번째,DAO에 익숙한 개발자에게는 약간의 진입장벽이 존재합니다. ORM자체는 객체간의 관계를 이용하기 때문에 초기사용시 어색할 수 있습니다.

두번째, 오용으로 인한 성능저하가 있을 수 있다. 프로젝트 자체의 볼륨이 크고 복잡도가 높은경우 오히려 ORM의 사용은 독이 될 수 있습니다.

세번째, OLAP이나 Procedure 작업이 많은 경우 객체지향의 장점을 이용하기 힘들 수 있습니다.



ORM을 이용해보자!


역시 직접 해보는게 최고의 선생님 아닐까 싶습니다. 그렇다면 설치를 해야겠죠?
Sequelize.js 패키지와 mysql 패키지를 설치해 줍니다.

npm install sequelize --save

npm install mysql --save




  여기서 잠깐  


여기서 제가 사용하는 DBMS는 MYSQL입니다. Object와 Mapping을 할 DBMS가 필요하기 때문에 로컬 혹은 클라우드 환경에서 이용할 수 있는 DBMS가 필요합니다.




저는 이렇게 도커를 통해  mysql 환경을 구축해 주었습니다. 버전은 lastest로 이용했는데, docker hub에 찾아보니 8.0v인것 같습니다. 버전에 따른 특별한 기능을 이용하지 않으니 무난하게 5.6이나 5.7 버전을 설치해주셔도 됩니다.


좀 더 편리한 설명을 위해 저는 디렉터리 구조를 이렇게 잡았습니다!

기본 디렉터리는 sequelize-example/src 입니다.




Sequelize Setting


Sequelize와 Mysql의 설치를 완료하였으면, Sequelize에 대한 셋팅을 해봅시다.


var fs = require('fs');
var path = require('path');
var config = require('./config');
var Sequelize = require('sequelize');

const db = {}
const sequelize = new Sequelize(
  'databaseName', // 데이터베이스 이름
  'username', // 유저 명
  'password ', // 비밀번호
  {
    'host': 'localhost', // 데이터베이스 호스트
    'port': 3308,
    'dialect': 'mysql' // 사용할 데이터베이스 종류
  }
);

// 하나씩 파일을 캐싱해 두는 방식
db['Publisher'] = sequelize.import(path.join(__dirname, 'publisher.js'));

// 디렉터리 내의 모든 파일을 순회하며 캐싱하는 방식이다.
fs.readdirSync(__dirname)
  .filter(function(file) {
    return (file.indexOf('.') !== 0) && (file !== 'index.js' && file !== 'config.js');
  })
  .forEach(function(file) {
    var model = sequelize.import(path.join(__dirname, file));
    db[model.name] = model;
  });

// 모델간의 relation을 설정해주는 작업을 한다.
config.initAssociations(db) 

// 각각 실행타이밍에 맞춰 hooks 또한 설정 가능합니다.
config.initHooks(db); 

// 객체에 대한 definition을 모델링하며, 각각 객체에 대해 동기작업을 실행합니다.
db.user.sync().then(() => {
  db.book.sync();
});

// db.Publisher.drop();
// db.Publisher.sync({force: true});

// 객체를 편하게 쓰기위해 그냥 다 담아줍니다.
db.sequelize = sequelize;
db.Sequelize = Sequelize;

module.exports = db;


#Seqelize.import()


파일에 있는 모델의 정의와 왁벽히 같은 Object를 생성하여 저장합니다. 내부적으로 캐시되어 여러번의 호출이 발생하더라도 문제가 발생하지 않습니다. 서버구동시 앱단에서 캐싱을 실행하면 한번에 처리가 가능합니다.



Model definition


이제 Object를  Mysql에 매핑 해주기 위해서는 Object에 대한 Definition을 지정해야 합니다. 이러한 과정을 Model definition이라 하고, RDB에서 스키마를 생성하는 과정과 유사합니다.

module.exports = (sequelize, DataType) => {
  return sequelize.define('user', {
    user_id: {
      type: DataType.INTEGER,
      primaryKey: true,
      autoIncrement: true,
    },
    password: {
      type: DataType.STRING(45),
      allowNull: true,
    }},{
      classMethods: {},
      tableName: 'user',
      underscored: true,
      timestamps: false,
    })
})



TimeStamp

- createAt, updatedAt 컬럼이 생성된다. 기능을 끄려면  false로 설정해 줍니다.


paranoid

- true인 경우에 deleteAt 컬럼이 테이블에 추가됩니다. 해당 컬럼에 값(날짜)이 표기된 경우 find시 row는 검색되지 않는다.


underscored

- Naming 규칙을 정한다. snakeCase로 설정된다. default는 CamalCase이다.


freezeTableName

- define 메소드의 첫번쨰 인자를 설정한 이름으로 사용할 수 있다. (근데 저는 옵션을 켜고 설정해준 다음 첫번째 인자를 뺏더니 오류가..)


Comment

- description을 명세해줍니다.



Data Type (Mysql)


Sequelize에서 제공하는 Mysql의 데이터 타입은 다음과 같습니다. 이 외에도 많은 데이터 타입을 지원합니다.


더 많은 내용을 원하시면 여기를 방문해 주세요.


STRING => VARCHAR(255)STRING(1234) => VARCHAR(1234)STRING.BINARY => VARCHAR BINARYTEXT => TEXTTEXT('tiny') => TINYTEXTINTEGERBIGINTBIGINT(11)FLOATFLOAT(11)FLOAT(11, 12) => FLAOT(11,12)DOUBLEDECIMALDATE(6) => mysql5.6.4+DATEONLYBOOLEANENUM('value1', 'value2')JSONGEOMETRYGEOMETRY('POINT')GEOMETRY('POINT', 4326)


Defining as part of a property


각각의 프로퍼티는 get(), set()을 통해 직접 인스턴스의 속성에대해 접근할 수 있습니다.

const Employee = sequelize.define('employee', {
	name : {
		type: Sequelize.STRING,
		allowNull: false,
		get() {
		// this는 객체의 속성에 접근이 가능하게 해준다.
			const title = this.getDataValue('title');
			return this.,getDataValue('name') + ' (' + title + ')';
		}
	}
})



Object Association

'use strict';

var config = { initAssociations: function(db) { db.user.hasOne(db.book, { foreignKey: 'user_id' }); db.book.belongsTo(db.user) }, }; module.exports = config;
Sequelize에는 1:1 관계를 정의해주는 여러가지 메소드가 있다. 

=> hasOne()
=> belongsTo()

두가지 메소드는 모두 1:1 관계를 정의해주는 메소드이다.
관계를 정의함에 있어서 각각의 메소드는 기준이되는 모델이 다르다. 메소드의 기본구조는 다음과 같다.

Source.hasOne(Target) = Target.belongsTo(Source) 
Source 객체는 Target를 소유하고, Target 객체는 Source 객체에 포함된다.
또한, foreignKey 키워드를 통해 외래키를 설정해 줄 수 있다.

#Difference between HasOne And BelongsTo
HasOne inserts the association Key in target, whereas belongsTo inserts association key in the source model


#One-To-Many Associations
=> One source can connect with multiple target

1: N 관계를 지정해주는 메소드이다.
var config = {
  initAssociations: function(db) {
    db.user.hasMany(db.book, { foreignKey: 'user_id' });
    db.book.belongsTo(db.user)
    db.memo.belongsTo(db.memo)
  },
}

1:1 관계와 마찬가지로 hasMany() 메소드를 이용해 1:N 관계를 지정해 줄 수 있다. 


#belongs-To-Many Associations

N:M 관계를 정의하기 위해선 belongsToMany()를 사용한다.

N:M 관계를 지정해 줄 때 사용하는 메소드이다. 일반적으로 스키마간의 N:M 관계를 가지게 되면, 중간에 1:N, N:1 분해 되는 정규화 과정을 갖는다. 이때 생기는 테이블을 중간테이블이라 하겠다.(이름이 뭔지 도무지 모르겠다..)

user.belongsToMany(book, { through: 'user book'})
book.gelongsToMany(user, { throughout: 'user book'})

* 중간테이블을 생성하게 되면 여러가지 메소드들이 자동으로 생성된다.

book에 대하여 getUser, SetUser, addUser, addUsers 등의 메소드들이 자동으로 생성된다.
user에 대하여 getBook, SetBook, addBook, addBooks 도 마찬가지이다.

관계를 맺는 테이블에 대해 별칭(alias)를 설정해 줄 수 있다. 
foreignKey 와 otherKey에 대한 지정도 가능하다.

user.belongsToMany(book, {
  as : Target,
  through : 'Source_Target',
  foreignKey : 'user_id', 
  otherKey : 'status'
})

book.belongsToMany(user, {
  as : Source,
  through : 'Source_Target',
  foreign_key : user_id
})


물론, 중간테이블에 대해서도 추가컬럼을 설정해 줄 수 있다.

Source_Target = sequelize.define( 'Source_Task', { status: Datatype.String })
user.belongsToMany('book', { through : worker_task})



이렇게 ORM 중에서도 Sequelize.js 패키지에 대하여, ORM이란 무엇인지? ORM에 대한 장단점이 무엇인지? 기초적으로 Model에 대한 Definition을 정의하고 매핑 방법, 여러개의 Model에 대해 관계를 정리하고 설정하는 방법을  알아보았습니다.


2편에서는 Mapping을 완성한 Object들에 대하여 기본적인 CRUD 작업을 하는 방법에 대해 알아보도록 하겠습니다!



COMING SOON!!!!!!!!!!!!