지금까지 sqlalchemy의 core layer에 대해 공부했고, 이제는 orm layer에 대해 알아보자. SQLAlchemy를 이용한 application을 다루게 된다면 가장 많이 접할 것이다.
Session #
session은 sqlalchemy 객체로, DBMS와 ORM수준으로 상호작용하고 트랜잭션을 관리하기 위한 객체이다.
생성하는 방법으로는 두가지가 있는데, 모두 Engine
인스턴스가 필요하다.
-
Session(Engine)
-
sessionmaker(bind=Engine)()
sessionmaker는 Session을 생성하기 위한 factory이며 호출시 session을 생성해준다. Engine처럼 보통 DB당 하나씩만 있으면 된다.
주의할 점으로는 Session 객체가 생성되었다고 해서 connection이 할당된 것은 아니라는 점이다. 다음 코드를 interactive mode로 실행해보자!
# test.py
from sqlalchemy import create_engine, text
from sqlalchemy.orm import sessionmaker
engine = create_engine("mysql+pymysql://root:password@localhost/test_db", echo=True)
sessionmaker = sessionmaker(bind=engine)
session = sessionmaker()
❯ python -i test.py
>>> (아무것도 출력되지 않는다)
>>> session
<sqlalchemy.orm.session.Session object at 0x106472710>
>>> engine.pool.status()
'Pool size: 5 Connections in pool: 0 Current Overflow: -5 Current Checked out connections: 0'
session이 생겼다고 해서 connection을 가져오지(connect
) 않은 것을 확인할 수 있다. 실제 DB 실행시에 connection 객체를 가져올 것이다.
쿼리 실행 & 객체 매핑 #
execute
query
scalars/scalar
위 3개의 차이점 설명 필요
add
flush
refresh
delete
위 메서드들은 session과 연결된 ORM 객체들을 관리하기 위해 사용한다.
트랜잭션 관리 #
with으로 열 수도 있고, begin() ~ close()로 할 수도 있다. Engine과 거의 유사하며, ORM을 사용하기 위해 Session이 필요하다고 봐도 무방하다.
추가로, rollback과 commit 메서드도 존재한다.
begin()시에는 트랜잭션이 열리며, close()시에 rollback()/commit()이 실행된다.
begin() 역시 engine과 마찬가지로 쿼리가 나감(+connection을 가져옴)을 보장하지 않는다!
역으로, close() 메서드도 connection이 할당되지 않았다면 굳이 쿼리가 나가지 않을 것이다.
❯ python -i test.py
>>> session.begin()
<sqlalchemy.orm.session.SessionTransaction object at 0x1046085f0>
>>> engine.pool.status()
'Pool size: 5 Connections in pool: 0 Current Overflow: -5 Current Checked out connections: 0'
ORM과 트랜잭션의 관계 & expire_on_commit #
inspect(obj).expired를 통해 해당 orm 객체가 expire되었는지 확인할 수 있다.
생성시에 줄 수 있는 옵션으로 expire_on_commit이 있는데, 이는 commit 시에 기존에 session에 묶여있던 ORM 객체들을
engine connection session #
이제, engine, connection, session에 대해 알아봤는데, 차이점을 알 필요가 있다.
connection은 DB API에 있는 connection을 래핑한 객체이다.
engine은 connection들을 관리하기 위한 객체이다.
session은 트랜잭션을 수행하고 ORM 객체와 매핑&객체의 영속화를 수행하기 위한 객체이다. engine으로부터 만들어진다.
정확한 관계 정리: #
- **
Connection
**과 **Connection Pool
**은 SQLAlchemy가 사용하는 DBAPI 드라이버와 연관이 있습니다. DBAPI 드라이버는 데이터베이스와의 연결을 관리하며, 이는 PEP 249에 정의된 표준을 따릅니다. - **
Session
**은 SQLAlchemy의 ORM 레이어에서 데이터베이스와 상호작용하는 객체로, 데이터베이스 연결을 직접 관리하지 않고 Connection Pool을 통해 필요할 때만 Connection을 가져와 사용합니다. - Connection은 데이터베이스와의 직접적인 연결을 나타내며, DBAPI 드라이버(예:
psycopg2
,pymysql
)를 통해 데이터베이스와 연결됩니다. - Connection Pool은 SQLAlchemy가 여러 Connection 객체를 재사용하여 성능을 최적화하기 위한 연결 관리 메커니즘입니다.
따라서 Connection과 Connection Pool은 DB 스펙이라고 보기보다는, DBAPI 드라이버와 SQLAlchemy의 연결 관리 메커니즘에 해당합니다. Session은 그 위에서 동작하는 SQLAlchemy ORM의 트랜잭션 관리 도구라고 할 수 있습니다.
암묵적 begin #
세션을 통한 쿼리 실행은 암묵적으로 트랜잭션을 열어준다.
session은 스레드 세이프한가? #
https://docs.sqlalchemy.org/en/20/orm/session_basics.html#session-faq-threadsafe
Scoped Session (Thread safe?!) #
하지만 session은 threadlocal하지 않음. 즉 여러 스레드가 동시에 접근시 문제가 생길 수 있음 (도대체 어떤 문제?!)
이에 scoped_session이 나옴. scoped_session은 여러 세션을 key ~ value로 저장할 수 있도록 해주는 함수이다. 따라서 요청마다 고유한 값을 key로 하면 요청마다 session이 공유되게 할 수 있는 것이다!
만약, async session의 경우에는 싱글스레드에서 돌아가므로 여러 스레드가 접근하지 않으므로 문제가 생기지 않는 것인가? 오류 전파 등의 이슈가 없는 건지..
https://jay-ji.tistory.com/115
참고 : https://docs.sqlalchemy.org/en/20/orm/session_basics.html#