건프의 소소한 개발이야기

[MySQL-Flask SqlAlchemy] Python 으로 ORM 완벽사용 (1) 본문

개발 이야기/MySQL(DB) 이야기

[MySQL-Flask SqlAlchemy] Python 으로 ORM 완벽사용 (1)

건강한프로그래머 2016. 11. 27. 23:20

안녕하세요, 건프입니다. 


 MySQL DB 를 다루는 여러가지 방법 중에서, 객체지향적 사고방식을 접목한 ORM(Object Relational Mapping) 방법을 소개하고, 간단한 코드를 메모하려고 합니다.


 기존의 DB Query 문을 로우레벨에서 직접 작성했던 사람들은, 새로운 프로젝트를 진행함에 있어서 여간 불편한 것이 아닙니다. 할때마다 달라지는 테이블 명, Type들.. 오브젝트가 달라질때마다 받아내는 클래스의 맴버들도 모두 바꿔주어야 하는 불편함이 있었습니다.


 ORM 은 객체지향적으로 작성하는 모델들(Class) 들의 개념과 관계형 데이터베이스에 속하는 MySQL의 Relation 을 따로따로 보지않고 연결해서 보겠다는 관점에서 출발합니다.  


 코드를 확인하면 빠르게 이해할 수 있습니다.


 예를들어 User 클래스를 구현한다고 생각해보겠습니다.


from app import db
from datetime import datetime

class User(db.Model):
__tablename__ = 'travel_user'
__table_args__ = {'mysql_collate': 'utf8_general_ci'}

user_id = db.Column(db.String(30), primary_key=True, unique=True)
user_name = db.Column(db.String(30))
profile_url = db.Column(db.String(100))
created = db.Column(db.DateTime)

def __init__(self, user_id, user_name, profile_url):
self.user_id = user_id
self.user_name = user_name
self.profile_url = profile_url
self.created = datetime.now()


def __repr__(self):
return 'user_id : %s, user_name : %s, profile_url : %s' % (self.user_id, self.user_name, self.profile_url)

def as_dict(self):
return {x.name: getattr(self, x.name) for x in self.__table__.columns}

 

 이 예제는 app 이라는 파이썬폴더의 __init__.py 아래에 다음과 같이 db 가 정의되어 있습니다.

from flask import Flask
from flask_sqlalchemy import SQLAlchemy

app = Flask(__name__)

# app config
app.config['SQLALCHEMY_DATABASE_URI'] = 'mysql+pymysql://root:mysql-root@localhost/travel_mate?charset=utf8'
app.config['SQLALCHEMY_ECHO'] = True
app.config['SQLALCHEMY_TRACK_MODIFICATIONS'] = True
app.secret_key = 'manyrandombyte'

db = SQLAlchemy(app)

from app.models import *
from app.routes import *

db.create_all()


 이 예제는 Flask 위에서 동작하는 ORM 을 Flask-Sqlalchemy 를 이용해서 구현해본 것입니다. 같은 환경을 구성하기 위해서는 다음과같이 pip 설정을 해주셔야할겁니다.


pip install flask, flask-sqlalchemy, pymysql


 이제 위에 언급한 User 클래스 부터 확인을 해보겠습니다.

Class User(db.Model): 이렇게 User 는 db.Model 을 상속하는 형태로 만들어집니다. 이렇게 상속한 클래스들은 나중에 db.create_all() 함수가 불리면 테이블을 만들게 됩니다.


만드는 테이블의 속성들은 User Class 아래 member 변수들의 정의로 표현합니다.


__tablename__ = 'travel_user'

__tablename__ 문자열로 채워주면 해당 문자열의 테이블이 생성되고

__table_args__ = {'mysql_collate': 'utf8_general_ci'}

이 코드를 함께 삽입해주면 문자들을 넣을때 utf-8 형태로 들어가 한글을 인식할 수 있습니다.

DB 에서 한글을 인식 못하는 경우 때문에 원치않는 삽질로 시간을 잡아먹습니다. 위의 꿀팁을 잘 쓰시길 바랍니다.


user_id = db.Column(db.String(30), primary_key=True, unique=True)

이 코드는 user_id 라는 이름으로, db.Column 을 생성하는데, 그 타입은 string(30) 타입이며, primary_key 를 의미하고 unique 합니다.

created = db.Column(db.DateTime)

DateTime 의 타입일 경우 이런식으로 표현합니다.


def __repr__(self):
return 'user_id : %s, user_name : %s, profile_url : %s' % (self.user_id, self.user_name, self.profile_url)

얘는 User 객체 자체로 print 에 찍었을때, 어떤 모습으로 보여줄지를 정해주는 것입니다.


def as_dict(self):
return {x.name: getattr(self, x.name) for x in self.__table__.columns}

얘도 하나 꿀팁인데,

DB에서 query 한 결과를 받아오면, 이것이 dict 형태가 아닌 QueryObject 타입입니다. 이를 dict 타입으로 만들고 싶으면 위의 코드를 클래스 안에 넣어주고 호출해주면 됩니다. :)



이렇게 해놓고 서버를 리로드 해보면, 만약 해당 데이터베이스에 해당 테이블이 존재하면 놔두고

테이블이 존재하지 않는다면 위의 타입대로 테이블을 자동 생성해줍니다.


생성한 테이블에서 쿼리한 작업을 저 클래스의 형태로 넘겨주게 되므로

우리는 객체를 정의한 그대로 그 값들을 쿼리문에서 받아 사용할 수 있게 되는 것이지요.


하지만 ORM 에서는 큰 한계가 있는데

"객체 클래스를 바꾼다고 해서 기존 테이블이 Alter 되지 않습니다!" 

기존 테이블에서 user_id -> user_friend_id 로 칼럼명을 바꾸고 싶다고, Class User 에서 바꿔놓고 재실행한다고 user_friend_id 로 바뀌지 않는다는 의미입니다.

그럼 어떡하죠?


서버의 디비에 직접 접속해서 테이블을 지우고 다시 서버를 실행시켜야 합니다..


이를 간단하게 해결하는 방법을 알아낸다면 추가 포스팅을 하겠습니다.


여튼 이런 문제점을 빼고는 굉장히 객체지향적으로 일관되게 DB와 서버사이드를 구축할 수 있습니다!


도움이 되었길 바랍니다.


다음 포스팅에서는 ORM 에서의 Select, Insert, Delete, Update 를 어떻게 하는지를 코드와 함께 확인해보겠습니다. 


감사합니다 :)

Comments