ORM과 PYTHON
기존의 sqlite 라이브러리는 sql문법에 의존해서 파이썬코드로 sql을 동작했었다.
그러지 말고 sql문법을 전부 파이썬 객체로 맵핑하여 그냥 파이썬 객체의 인스턴스, 메소드를 통해서 구현할 수 있다면 훨씬 더 체계적이고 가독성이 좋을 것이다.
그러한 객체와 기능의 MAPPING을 바로 ORM이라고 한다.
1. 객체란?
우선 ORM을 보기 전에 파이썬의 객체에 대해서 생각해보자.
CREATE TABLE articles (
id INTEGER PRIMARY KEY AUTOINCREMENT,
title TEXT NOT NULL,
content TEXT NOT NULL,
created_at TEXT NOT NULL,
author TEXT NOT NULL
);
이러한 sql의 table의 데이터를 가져와본다고 생각해보자.
만약 그런 데이터가 리스트로 저장된다면,
articles1 = [
[1,'제목입니다','내용입니다','신동찬','2019-02-07']
[2,'제목입니다','내용입니다','신동찬','2019-02-07']
]
이런식으로 되어있을거고
딕셔너리라면,
articles2 = [
{'id':1,"title":"제목입니다","content":"내용입니다","author":신동찬,"created_at":'2019-02-07'}
{'id':2,"title":"제목입니다","content":"내용입니다","author":신동찬,"created_at":'2019-02-07'}
]
이런식으로 정의되서 키를 통해서 데이터를 갖고올 수 있을 것이다.
그리고 Object라면?
class Article:
def __init__(self,id,title,content,author,created_at):
self.id=id
self.title=title
self.content=content
self.author=author
self.created_at=created_at
a1 = Article(1,'제목입니다','내용입니다','신동찬','2019-02-07')
a2 = Article(2,'제목입니다','내용입니다','신동찬','2019-02-07')
articles3 = [
a1,a2
]
이런식으로 저장되서 a1.title 이런식으로 데이터를 갖고올 수 있을 것이다.
그런데 클래스를 이용했을 때의 압도적인 장점이 있는데,
가져온 데이터에 대해서 메소드를 수행할 수 있다는 점이다.
예를 들어서,
class Article:
def delete(self):
del(self)
이런 식으로 delete메서드를 만들게 되면, sqlite라이브러리의 connect의 힘을 빌리지 않고도 데이터가 스스로 소멸될 수 있게 되는 것이다. 물론 이것 이외에도 무수한 메서드가 있고, orm의 장점은 엄청 많다.
2. SQLAlchemy
그러면 그런 ORM이 어떻게 적용되는지 예를 한번 들어보자.
class Flight(db.Model):
__tablename__ = "flights"
id = db.Column(db.Integer, primary_key=True)
origin = db.Column(db.String, nullable=False)
destination = db.Column(db.String, nullable=False)
duration = db.Column(db.Integer, nullable=False)
굳이 설명을 하지 않았는데도 불구하고 무슨 기능을 하는건지 감이 쉽게 올 것이다.
그럼 SQLAlchemy를 설치해보자.
sudo pip3 install sqlalchemy
sudo pip3 install flask-sqlalchemy
이름을 보면 알 수 있듯이 flask와 결합해서 좋은 기능을 발휘할 수 있다.
이것을 파이썬에서 기본적으로 세팅하는 방법은 이렇다.
from flask import Flask
from flask_sqlalchemy import SQLAlchemy
app = Flask(__name__)
app.config["SQLALCHEMY_DATABASE_URL"] = "sqlite:///blog2.db"
# DB를 지정 + 파일명을 지정, 작대기 3개면 상대경로 4개면 절대경로
app.config["SQLALCHEMY_TRACK_MODIFICATIONS"] = False # 꼭 필요하다고 합니다.
db = SQLAlchemy(app) # Flask app과 SQLAlchemy를 합체함.
db.init_app(app)
여기서 ORM의 장점을 볼 수 있다. 우리가 sqlite3을 쓰다가 다른 db(예를 들면 postgre)로 넘어가야할 일이 생기는데, sqlite3문법으로 파이썬코드를 작성하면 다 갈아엎어야하는 문제가 발생한다.
그러나 ORM으로 구성되었다면
app.config[“SQLALCHEMY_DATABASE_URL”] = “sqlite:///blog2.db”
위의 문장을 postgre에 대해서 바꿔놓기만 했을 뿐인데 ORM이 모두 postgre db의 문법으로 맵핑시켜주기 때문에 매우 편리하다.
그리고 이런 ORM은 여러 테이블들을 이용할 때 더 빛을 발한다고 합니다.
3. SQLAlchemy 사용해보기
-
CREATE TABLE
처음 언급했던 articles 테이블을 생성하는 것은 sqlalchemy에서는 아래와 동일하다.
class Article(db.Model): # db.Model을 상속받으면 db.Column()이라는 메소드를 사용가능함. __tablename__ = "articles" id = db.Column(db.Integer, primary_key=True) title = db.Column(db.String, nullable=False) content = db.Column(db.String, nullable=False) author = db.Column(db.String, nullable=False) created_at = db.Column(db.String, nullable=False) db.create_all() # 이렇게 만든 테이블을 다 생성해봐라.
Article은 그냥 테이블에 접근하는 class의 이름이고,
실제로 db에는 articles라는 이름의 테이블이 생성된다. (두개를 다르게 생각하는게 좋음)
-
INSERT INTO
""" ORM을 이용하여 데이터를 저장한다. """ title = request.args.get('title') content = request.args.get('content') a = Article(title=title,content=content) # Article이라는 객체 생성 db.session.add(a) # 생성한 객체를 디비에 넣어라. db.session.commit() # commit
-
SELECT
article = Article.query.all() # SELECT * FROM articles와 동일함 # 이렇게 뽑힌 것들은 객체들의 리스트가 된다. # [<article1>, <article2>, ...] # 따라서 객체의 원소를 참조하려면 멤버변수를 통해서 들고와야한다!
sqlite 라이브러리를 이용하면 db연결하고, cursor만들고, execute하고 fetch해야하는데 이건 한줄로 다 처리할 수 있다.
-
WHERE
a = Article.query.filter_by(title="제목인가요?").all()
filter_by라는 메서드를 통해서 제어를 할 수 있다.
그리고 우리가 많이 쓰는 where문은 바로 id를 통해서 제어하는 방식인데,
이건 더 간단한 방식으로 구현되어있다.
a = Article.query.filter_by(id=28).first() # SELECT * FROM articles WHERE id=28 LIMIT 1과 동일한 문법 a = Article.query.get(28) # 이렇게 간단하게도 줄여쓸 수 있다!
-
DELETE
a = Article.query.get(28) db.session.delete(a) db.session.commit() # 삭제할 객체를 찾는다 -> 객체를 제거한다
역시 데이터베이스를 변화시킬때는 반드시 커밋과정을 거쳐야한다.
-
UPDATE
a.title = "Good Title!" # 각 row가 인스턴스이기 때문에 클래스의 멤버변수에 직접 접근해서 바꾸면 됩니다. db.session.commit() # 반드시 커밋!
-
기타 함수들
-
Count
#SELECT COUNT(*) FROM articles Article.query.count()
-
MAX
a = Article.query.order_by(Article.id.desc()).first()
이상하게 Article.query.max()가 안되는데, 다른 방법이 있다.
id기준으로 max인 데이터를 출력하고 싶으면 id의 역순으로 정렬한 다음에 첫번째를 뽑으면 된다.
-
인스턴스 전체가 아니라 특정 column만 뽑고 싶을 때
a = Article.query.with_entities(Article.title).all() # [(title1,),(title2,),...]
이렇게 뽑으면 인스턴스들의 리스트가 출력되는게 아니라 튜플이 출력된다.
-
4. 왜 DB에 Commit이 필요한가?
여러 사람이 DB에 동시에 접근하기 때문이다.
은행계좌를 관리하는 은행 DB에 대해서 생각해보자.
은행 DB로 접근하는 방식은 인터넷뱅킹이나, ATM기를 통해서 하는 등 여러가지 방법이 있다.
이러한 여러 접근들이 같은 계좌에 겹치게 되면 문제가 발생하는데,
예를 들어서 신동찬이라는 사람의 계좌에 100원이 있을 때
-
A는 12시 00분 00초에 ATM기를 통해서 신동찬 계좌에 100원이 있음을 확인하고,
12시 00분 03초에 100원을 인출하였다.
-
B는 12시 00분 02초에 인터넷뱅킹을 통해서 신동찬 계좌에 100원이 여전히 있음을 확인하고,
12시 00분 05초에 100원 인출명령을 내렸다.
만약 커밋과정이 없다면 돈은 100원밖에 없지만 인출을 둘 다 시행하여 결국 -100원이 되는 것이다.
이러한 상황을 막기 위해서 은행 DBMS에는 transaction이라는 개념이 있다.
(transaction : 여러 가지 sql문장들을 쌓아서 한번에 실제로 반영시키는 하나의 단위)
A와 B는 각자의 transaction을 가지고 있으며,
A가 (Commit을 통해서) 인출명령을 내리면 A의 transaction이 시행되며, 동시에 다른 transaction들의 접근을 막는다 (transaction locking) 그러면 그 시간에는 B의 transaction이 시행될 수 없는 것이다.
따라서, B는 3초 이후에 A의 거래가 끝나고 계좌에 0원이 있다는 것을 확인하게 될 것이다.
이러한 개념을 SQLAlchemy에서는 Session이라는 것을 통해서 관리하는데,
데이터베이스에 접근할 때 마다 사용자 각각마다 Session을 줘서 관리하는 것이다.
그래서 Session을 커밋하면 이제껏 구성한 transaction이 시행되는 개념이라고 생각하면 된다.