728x90
728x90

파이썬에서 ORM(Object Relational Mapping) 라이브러리 사용해보기 (SQLAlchemy)

들어가며

  • 파이썬에서 SQLAlchemy ORM(Object Relational Mapping) 라이브러리를 사용해보자.

SQLAlchemy Logo

 

ORM(Object Relational Mapping)

개념

  • 데이터베이스를 사용하려면 SQL 쿼리(Query)라는 구조화된 질의를 작성하고 실행하는 등의 복잡한 과정이 필요하다.
  • 이때 ORM(Object Relational Mapping)을 이용하면 파이썬 문법만으로도 데이터베이스를 다룰 수 있다.
    • 즉, ORM을 이용하면 개발자가 쿼리를 직접 작성하지 않아도 데이터베이스의 데이터를 처리할 수 있다.
    • ORM은 데이터베이스에 데이터를 저장하는 테이블을 파이썬 클래스로 만들어 관리하는 기술로 이해해도 좋다.
  • 예를 들어 다음과 같이 @QUESTION@ 테이블이 있다고 해보자.

 

 @QUESTION@ 테이블
id subject content
1 오늘의 날씨 오늘의 날씨는 어때요?
2 안녕하세요? 만나서 반가워요.
... ... ...

 

  • 위의 테이블을 SQL(Structured Query Language)을 이용하여 구성하려면 아래와 같은 코드를 작성해야 한다.
INSERT INTO QUESTION(subject, content) VALUES("오늘의 날씨", "오늘의 날씨는 어때요?");
INSERT INTO QUESTION(subject, content) VALUES("안녕하세요?", "만나서 반가워요.");

 

 

  • 하지만, ORM(Object Relational Mapping)을 이용하면 아래와 같은 파이썬 코드로 작성할 수 있다.
question1 = Question(subject="오늘의 날씨", content="오늘의 날씨는 어때요?")
db.session.add(question1)
question2 = Question(subject="안녕하세요?", content="만나서 반가워요.")
db.session.add(question2)

 

  • 코드에서 @Question@은 파이썬 클래스이며, 이처럼 데이터를 관리하는 데 사용하는 ORM 클래스모델(Model)이라고 한다.
  • 모델을 사용하면 내부에서 SQL 쿼리를 자동으로 생성해 주므로 직접 작성하지 않아도 된다. 

 

ORM을 이용한 새 데이터 삽입 예는 코드 자체만 놓고 보면 양이 많아 보이지만 별도의 SQL 문법을 배우지 않아도 된다는 장점이 있어 훨씬 좋다.

 

 

장점

  • ORM을 이용하면 데이터베이스 종류에 상관 없이 일관된 코드를 유지할 수 있어서 프로그램을 유지·보수하기가 편리하다. 
  • 또한 내부에서 안전한 SQL 쿼리를 자동으로 생성해 주므로 개발자가 달라도 통일된 쿼리를 작성할 수 있고 오류 발생률도 줄일 수 있다.

 

설치하기

  • 파이썬 ORM 라이브러리 중 가장 많이 사용하는 @SQLAlchemy@를 설치해보자.
> pip install sqlalchemy

 

사용 방법

  • 예제 코드와 함께 SQLAlchemy 사용 방법을 알아보자.

 

모듈 불러오기

from sqlalchemy import create_engine, Column, Integer, String, Sequence
from sqlalchemy.ext.declarative import declarative_base
from sqlalchemy.orm import sessionmaker

 

  • 불러온 각 함수와 클래스에 대한 설명은 아래와 같다.
모듈 설명
@create_engine@ 함수 - SQLAlchemy에서 데이터베이스와의 연결을 설정하는 역할을 한다.
- 연결 문자열을 받아 해당 데이터베이스에 연결하는 엔진(Engine) 객체를 생성한다.
- 예) @create_engine('sqlite:///:memory:', echo=True)@ (SQLite를 메모리에 연결하는 엔진을 생성하고, @echo=True@는 생성되는 SQL을 터미널에 출력하도록 설정한다.)
@declarative_base@ 함수 - 클래스를 정의할 때 사용되는 기본 클래스를 생성한다.
- 이 기본 클래스를 상속받은 클래스는 데이터베이스의 테이블을 나타내게 된다.
- @Base = declarative_base()@와 같이 사용한다.
@Column@ 클래스 - 데이터베이스의 테이블에서 각 열(Column)을 나타낸다.
- 예) @id = Column(Integer, Sequence('user_id_seq'), primary_key=True)@ (정수형의 @id@ 열을 정의하고, 이 열을 기본 키(Primary Key)로 설정한다.)
@Sequence@ 클래스 - 일련번호(Sequence)를 생성하는데 사용된다.
- 주로 기본 키를 자동으로 생성할 때 활용된다.
@sessionmaker@ 클래스 -  데이터베이스와의 세션을 생성하는 역할을 한다.
- 세션은 트랜잭션과 관련된 작업을 처리하며, 데이터베이스와의 상호 작용을 담당한다.
- 예) @Session = sessionmaker(bind=engine)@ (엔진에 바인딩된 세션을 생성한다.)
@Session@ 객체 - 실제로 데이터베이스와의 세션을 나타내는 인스턴스
- @session = Session()@과 같이 생성하여 사용한다.

 

데이터베이스 연결하기

  • 파이썬에 기본으로 내장되어 있는 @SQLite@에 연결해본다.
  • 연결 문자열은 사용하는 데이터베이스에 따라 다르다.
  • @echo@ 옵션을 @True@로 지정하여 생성되는 SQL을 터미널에 출력해보도록 한다.
engine = create_engine('sqlite:///:memory:', echo=True)

 

모델 정의하기

  • 모델을 정의하여 파이썬 클래스 테이블로 나타낸다.
Base = declarative_base()

 

 

클래스(테이블) 생성하기 (CREATE)

  • 다음과 같이 @User@ 클래스를 선언한 후, 테이블 이름(@__tablename__@)을 @users@로 설정한다.
  • 그리고 각 컬럼(@id@, @name@, @age@)을 추가한다.
  • @id@를 기본 키(Primary Key) 및 정수형(@Integer@)으로 설정하였다.
  • 각 컬럼(@Column@)의 인자에 해당 컬럼의 자료형을 넣어준다.
class User(Base):
    __tablename__ = 'users'
    id = Column(Integer, Sequence('user_id_seq'), primary_key=True)
    name = Column(String(50))
    age = Column(Integer)

 

  • @Column@ 클래스의 매개 변수는 아래에서 확인할 수 있다.
더보기
  • @Column@ 클래스의 매개 변수는 다음과 같다.
class Column(
    __name_pos: str | type[TypeEngine[str]] | TypeEngine[str] | SchemaEventTarget | None = None,
    __type_pos: type[TypeEngine[str]] | TypeEngine[str] | SchemaEventTarget | None = None,
    *args: SchemaEventTarget,
    name: str | None = None,
    type_: type[TypeEngine[str]] | TypeEngine[str] | None = None,
    autoincrement: _AutoIncrementType = "auto",
    default: Any | None = None,
    doc: str | None = None,
    key: str | None = None,
    index: bool | None = None,
    unique: bool | None = None,
    info: _InfoType | None = None,
    nullable: bool | Literal[SchemaConst.NULL_UNSPECIFIED] | None = SchemaConst.NULL_UNSPECIFIED,
    onupdate: Any | None = None,
    primary_key: bool = False,
    server_default: _ServerDefaultArgument | None = None,
    server_onupdate: FetchedValue | None = None,
    quote: bool | None = None,
    system: bool = False,
    comment: str | None = None,
    insert_sentinel: bool = False,
    _omit_from_statements: bool = False,
    _proxies: Any | None = None,
    **dialect_kwargs: Any
)

 

테이블 생성하기

  • 아래의 명령을 실행하여 테이블(Table)을 생성한다.
Base.metadata.create_all(engine)

 

 

세션 생성하기

  • 아래의 명령을 실행하여 세션(Session)을 생성한다.
Session = sessionmaker(bind=engine)
session = Session()

 

 

데이터 추가하기 (INSERT)

  • 위에서 생성한 클래스(테이블)의 인자에 값을 넣어 새로운 클래스 객체를 생성한 후, @add@ 명령으로 테이블에 추가한다.
  • 그리고 커밋(@commit@)을 해준다.
new_user = User(name='dev-astra', age=777)    # 새로운 클래스 객체 생성
session.add(new_user) 
session.commit()

 

데이터 조회 해보기 (READ)

  • @query(User).all()@로 모든 데이터들을 불러온 후, 데이터들을 한 줄씩 불러와 출력한다.
users = session.query(User).all()
for user in users:
    print(user.id, user.name, user.age)

 

데이터 업데이트(수정) 해보기 (UPDATE)

  • @filter_by@ 함수를 이용하여 조건에 맞는(@name='dev-astra'@) 데이터를 불러온 후, 나이(@age@)를 @123@으로 바꿔본다.
  • 데이터를 업데이트를 하면 반드시 커밋(@commit@)을 해줘야 업데이트된 내용이 반영된다.
# 데이터 업데이트
user_to_update = session.query(User).filter_by(name='dev-astra').first()
if user_to_update:
    user_to_update.age = 123
    session.commit()

# 업데이트 후 데이터 조회
updated_users = session.query(User).all()
for user in updated_users:
    print(user.id, user.name, user.age)

 

 

데이터 삭제 해보기 (DELETE)

  • @filter_by@ 함수를 이용하여 조건에 맞는(@name='dev-astra'@) 데이터를 불러온 후, @delete@ 함수를 이용하여 해당 데이터를 삭제해본다.
  • 데이터를 삭제한 후 반드시 커밋(@commit@)을 해줘야 삭제된 내용이 반영된다.
# 데이터 삭제
user_to_delete = session.query(User).filter_by(name='dev-astra').first()
if user_to_delete:
    session.delete(user_to_delete)
    session.commit()

# 삭제 후 데이터 조회
remaining_users = session.query(User).all()
for user in remaining_users:
    print(user.id, user.name, user.age)

 

세션 닫기

  • 모든 작업을 완료했으면 반드시 세션을 닫아준다.
session.close()

 

전체 코드

from sqlalchemy import create_engine, Column, Integer, String, Sequence
from sqlalchemy.ext.declarative import declarative_base
from sqlalchemy.orm import sessionmaker

# 데이터베이스에 연결 (연결 문자열은 사용하는 DBMS에 따라 다름)
engine = create_engine('sqlite:///:memory:', echo=True)

# 모델 정의
Base = declarative_base()

class User(Base):
    __tablename__ = 'users'
    id = Column(Integer, Sequence('user_id_seq'), primary_key=True)
    name = Column(String(50))
    age = Column(Integer)

# 테이블 생성
Base.metadata.create_all(engine)

# 세션 생성
Session = sessionmaker(bind=engine)
session = Session()

# 데이터 추가
new_user = User(name='dev-astra', age=777)
session.add(new_user)
session.commit()

# 데이터 조회
users = session.query(User).all()
for user in users:
    print(user.id, user.name, user.age)

# 데이터 업데이트
user_to_update = session.query(User).filter_by(name='dev-astra').first()
if user_to_update:
    user_to_update.age = 123
    session.commit()

# 업데이트 후 데이터 조회
updated_users = session.query(User).all()
for user in updated_users:
    print(user.id, user.name, user.age)

# 데이터 삭제
user_to_delete = session.query(User).filter_by(name='dev-astra').first()
if user_to_delete:
    session.delete(user_to_delete)
    session.commit()

# 삭제 후 데이터 조회
remaining_users = session.query(User).all()
for user in remaining_users:
    print(user.id, user.name, user.age)

# 세션 닫기
session.close()
c:\databaseEx1.py:9: MovedIn20Warning: The ``declarative_base()`` function is now available as sqlalchemy.orm.declarative_base(). (deprecated since: 2.0) (Background on SQLAlchemy 2.0 at: https://sqlalche.me/e/b8d9)
  Base = declarative_base()
2023-11-14 11:47:10,818 INFO sqlalchemy.engine.Engine BEGIN (implicit)
2023-11-14 11:47:10,819 INFO sqlalchemy.engine.Engine PRAGMA main.table_info("users")
2023-11-14 11:47:10,819 INFO sqlalchemy.engine.Engine [raw sql] ()
2023-11-14 11:47:10,820 INFO sqlalchemy.engine.Engine PRAGMA temp.table_info("users")
2023-11-14 11:47:10,820 INFO sqlalchemy.engine.Engine [raw sql] ()
2023-11-14 11:47:10,821 INFO sqlalchemy.engine.Engine
CREATE TABLE users (
        id INTEGER NOT NULL,
        name VARCHAR(50),
        age INTEGER,
        PRIMARY KEY (id)
)


2023-11-14 11:47:10,821 INFO sqlalchemy.engine.Engine [no key 0.00067s] ()
2023-11-14 11:47:10,822 INFO sqlalchemy.engine.Engine COMMIT
2023-11-14 11:47:10,823 INFO sqlalchemy.engine.Engine BEGIN (implicit)
2023-11-14 11:47:10,824 INFO sqlalchemy.engine.Engine INSERT INTO users (name, age) VALUES (?, ?)
2023-11-14 11:47:10,825 INFO sqlalchemy.engine.Engine [generated in 0.00053s] ('dev-astra', 777)
2023-11-14 11:47:10,825 INFO sqlalchemy.engine.Engine COMMIT
2023-11-14 11:47:10,826 INFO sqlalchemy.engine.Engine BEGIN (implicit)
2023-11-14 11:47:10,828 INFO sqlalchemy.engine.Engine SELECT users.id AS users_id, users.name AS users_name, users.age AS users_age 
FROM users
2023-11-14 11:47:10,828 INFO sqlalchemy.engine.Engine [generated in 0.00031s] ()
1 dev-astra 777
2023-11-14 11:47:10,830 INFO sqlalchemy.engine.Engine SELECT users.id AS users_id, users.name AS users_name, users.age AS users_age
FROM users
WHERE users.name = ?
 LIMIT ? OFFSET ?
2023-11-14 11:47:10,830 INFO sqlalchemy.engine.Engine [generated in 0.00036s] ('dev-astra', 1, 0)
2023-11-14 11:47:10,832 INFO sqlalchemy.engine.Engine UPDATE users SET age=? WHERE users.id = ?
2023-11-14 11:47:10,832 INFO sqlalchemy.engine.Engine [generated in 0.00044s] (123, 1)
2023-11-14 11:47:10,833 INFO sqlalchemy.engine.Engine COMMIT
2023-11-14 11:47:10,834 INFO sqlalchemy.engine.Engine BEGIN (implicit)
2023-11-14 11:47:10,834 INFO sqlalchemy.engine.Engine SELECT users.id AS users_id, users.name AS users_name, users.age AS users_age
FROM users
2023-11-14 11:47:10,835 INFO sqlalchemy.engine.Engine [cached since 0.006951s ago] ()
1 dev-astra 123
2023-11-14 11:47:10,836 INFO sqlalchemy.engine.Engine SELECT users.id AS users_id, users.name AS users_name, users.age AS users_age
FROM users
WHERE users.name = ?
 LIMIT ? OFFSET ?
2023-11-14 11:47:10,836 INFO sqlalchemy.engine.Engine [cached since 0.006343s ago] ('dev-astra', 1, 0)
2023-11-14 11:47:10,837 INFO sqlalchemy.engine.Engine DELETE FROM users WHERE users.id = ?
2023-11-14 11:47:10,837 INFO sqlalchemy.engine.Engine [generated in 0.00031s] (1,)
2023-11-14 11:47:10,837 INFO sqlalchemy.engine.Engine COMMIT
2023-11-14 11:47:10,838 INFO sqlalchemy.engine.Engine BEGIN (implicit)
2023-11-14 11:47:10,838 INFO sqlalchemy.engine.Engine SELECT users.id AS users_id, users.name AS users_name, users.age AS users_age
FROM users
2023-11-14 11:47:10,838 INFO sqlalchemy.engine.Engine [cached since 0.01119s ago] ()
2023-11-14 11:47:10,838 INFO sqlalchemy.engine.Engine ROLLBACK

 

 

참고 사이트

 

2-04 모델로 데이터 처리하기

* `[완성 소스]` : [github.com/pahkey/jump2flask/tree/2-04](https://github.com/pahkey/jump2flask/tree/2-…

wikidocs.net

 

SQLAlchemy Documentation — SQLAlchemy 2.0 Documentation

SQLAlchemy Documentation New to SQLAlchemy? Start here: New users of SQLAlchemy, as well as veterans of older SQLAlchemy release series, should start with the SQLAlchemy Unified Tutorial, which covers everything an Alchemist needs to know when using the OR

docs.sqlalchemy.org

 

728x90
728x90