본문 바로가기

카테고리 없음

ORM(SQLAlchemy) 장단점 및 Flask 적용기

ORM 이란?

- 객체 관계 매핑(Object-relational mapping; ORM)의 약자로 간단히 말해서,

데이터베이스 내의 리소스(테이블)들을 체화하여, 각 DBMS(MySQL, MSSQL 등)들에 대해서 CRUD 등을 공통된 접근기법으로 사용할 수 있다. 대표적인 Python ORMDjango 자체적인 ORMSQLAlchemy 등이 있다.

 

- ORM 장점:

1) 위에서 언급했던 내용과 같이, 프로그래머는 DBMS에 대한 큰 고민없이, ORM에 대한 이해만으로 웬만한 CRUD를 다룰 수 있기 때문에, 비즈로직에 집중할 수 있으므로 개발 생산성을 증가시킬 수 있다.

2) 객체를 통하여 대부분의 데이터를 접근 및 수정을 진행하므로, 코드 가독성이 좋다.

3) 데이터 구조 변경시, 객체에 대한 변경만 이루어지면 되므로, 유지보수성이 좋다.

 

- ORM 단점: 

1) 복잡한 쿼리 작성시, ORM 사용에 대한 난이도가 급격히 증가한다.

2) 호출 방식에 따라, 성능이 천차만별이다.

3) DBMS 고유의 기능을 전부 사용하지는 못한다.

 

위의 단점들이 있음에도 불구하고, ORM은 여전히 프로그래머한테 매력적인 기법이다.

대표적인 Python 웹 프레임워크 중 하나인, Flask에서 SQLAlchemy ORM을 통한 기본적인 CRUD를 구현해보기로 해보았다.

 

 

 

- 프로젝트 개요

개발하는 토이 프로젝트는 월별 아파트 실거래 조회 웹페이지로서,

지역코드 별로 아파트 실거래가를 조회해서 DB에 저장한 후, 주소 조회시에 거래가 내역을 보여주는 프로젝트이다.

 

 

1. 법정동코드 모델 설계

- 기초데이터로 지번주소데이터(법정동코드)를 DB에 저장해 놓고 쓰기로 하였다.

# models/code.py 

# 법정동코드 모델
from app import db

# 10자리 법정동코드
class AddressCodes(db.Model):
    __tablename__ = 'address_codes'
    __table_args__ = {'mysql_collate': 'utf8_general_ci'}

    code = db.Column(db.String(10), primary_key=True)
    address_name = db.Column(db.String(128))
    parent_code = db.Column(db.String(5))
    updated_at = db.Column(db.DateTime, server_default=db.func.now())

    price_info = db.relationship('TradeInfo', backref='address_codes', lazy=True)

 

2. 실 거래가데이터 모델

- 지역코드 별 아파트 실거래가를 저장하는 모델로서, 지역코드를 address_codes 의 code를 참조키로 가져간다.

# models/trade_info.py
from app import db


# 실거래 가격
class TradeInfo(db.Model):
    __tablename__ = 'trade_info'
    __table_args__ = {'mysql_collate': 'utf8_general_ci'}
    id = db.Column(db.Integer, primary_key=True)
    serial_no = db.Column(db.String(14))  # 일련번호
    trade_price = db.Column(db.String(10))  # 거래금액
    name= db.Column(db.String(128)) # 아파트명
    year = db.Column(db.String(4))  # 거래년도
    month = db.Column(db.String(2))  # 거래월
    day = db.Column(db.String(2))  # 거래일
    road_name = db.Column(db.String(32))  # 도로명
    si_gun_code = db.Column(db.String(5))  # 법정동시군구코드
    dong_code = db.Column(db.String(5))  # 법정동읍면동코드
    ep_area = db.Column(db.String(20))  # 전용면적
    floor = db.Column(db.Integer)  # 층수
    updated_at = db.Column(db.DateTime, server_default=db.func.now())
    code_info = db.Column(db.String(10), db.ForeignKey('address_codes.code'), nullable=False)

    def __repr__(self):
        return 'serial_no = %s, trade_price = %s, ep_area =%s, updated_at = %s' % (
            self.serial_no, self.trade_price, self.ep_area, self.updated_at)

    @property
    def serialize(self):
        return {
            'id': self.id,
            'name': self.name,
            'serial_no': self.serial_no,
            'trade_price': self.trade_price,
            'date': self.year + '-' + self.month + '-' + self.day,
            'road_name': self.road_name,
            'ep_area': self.ep_area,
            'floor': self.floor,

        }

 

2. Flask 초기화

# app.py
# flask 초기화: AddressCodes 및 TradeInfo 는 db 객체가 생성된 이후에 import 해야 정상적으로 초기화가능

from flask_sqlalchemy import SQLAlchemy

app = Flask(__name__)

db = SQLAlchemy(app)

from models.code import AddressCodes
from models.trade_info import TradeInfo

# table 생성 및 반영
db.create_all()

 

3. 사용한 SQLAlchemy 문법

- LIKE 검색

# address 를 요청 파라미터로 받아 like 검색

keyword = "%{}%".format(address)
addr = AddressCodes.query.filter(AddressCodes.address_name.like(keyword)).all()

- AND 조건정렬, Pagination

# month.rjust(2,'0') 은 2를 02 와 같이 두자리 형태를 맞춰준다.

# page = 페이지번호
# max_per_page = 페이지당 표시 개수

res = TradeInfo.query.filter(TradeInfo.year == year, TradeInfo.month == month.rjust(2, '0'),
                                             TradeInfo.code_info == address_cd).order_by(TradeInfo.day.desc()).paginate(
                    page=int(page), error_out=False, max_per_page=int(amount))

# res는 아래와 같이 반환하며, 해당 값을 응답값으로 전달
return jsonify(
                    {
                        'has_next': res.has_next,
                        'has_prev': res.has_prev,
                        'next_num': res.next_num,
                        'prev_num': res.prev_num,
                        'items': [t.serialize for t in res.items]
                    })

- Group By

# 동일한 부모 주소코드로 Group By 처리
codes = db.session.query(AddressCodes.parent_code).group_by(AddressCodes.parent_code).all()

 

* 소스코드 : https://github.com/johyju03/-apartment_deal_history

 

-- 결과 페이지 --