# Postgresql + Sqlalchemy ORM CRUD方式 [ToC] ### 簡單介紹 - SQLAlchemy ORM是為Python提供的ORM框架,假如API Framework選用FastAPI、Flask...都蠻適合使用這個ORM框架來撰寫的,還很方便替換DB(Mysql、Postgresql...) ### 目錄結構 ![](https://i.imgur.com/CANA6pb.png) ### 建立連線 - `hello_postgres_with_sqlalchemy/database.py` ```python from sqlalchemy import create_engine from sqlalchemy.ext.declarative import declarative_base from sqlalchemy.orm import sessionmaker SQLALCHEMY_DATABASE_URL = "postgresql://<username>:<password>@<server:port>/<db_name>" engine = create_engine(SQLALCHEMY_DATABASE_URL, echo=True) SessionLocal = sessionmaker(autocommit=False, autoflush=True, bind=engine) db = SessionLocal() Base = declarative_base() # inherit from this class to create ORM models def get_db_session(): session = sessionmaker(autocommit=False, autoflush=True, bind=engine) db = session() try: yield db except Exception: db.rollback() raise finally: db.close() ``` - `SQLALCHEMY_DATABASE_URL`以`PostgreSQL`格式為例,如果使用不同種類的資料庫,要改為相對應的連線字串 - 透過`create_engine`來建立資料庫的連線,`sessionmaker`則是建立連線會話 - `echo=True`是可以顯示SQL操作了何種語法 - `autoflush(default: True)`: - 解釋: 把當前session存在的變更發給DB => 讓DB執行SQL語法 - ==True==: 會把當前操作的SQL發送給DB執行,所以可以在相同的事務裡面查到上一個sql新增的資料 - ==False==: 不會把累積的SQL發送給DB,當前的查詢操作也查不到上一個sql新增的資料 - `autocommit(default: False)`: - 解釋: 是否自動提交事務,一個事務裡面可能有一條或多條SQL語法。default是每個請求開啟了事務,並且要手動提交事務 - ==True==: 就不會開啟事務了,所以需要手動flush,告訴DB執行甚麼 - ==False==: 全部請求都開啟事務,每次結束SQL語法時,加上`db.commit()` - `Base`是SQLAlchemy model的基礎類別,透過繼承它來建立表格的model - 建立`get_db_session()`並實現一個 database session instance and yield it ### 建立model - `hello_postgres_with_sqlalchemy/models.py` ```python from sqlalchemy import Column, Integer, String from hello_postgres_with_sqlalchemy.database import Base, engine class Test(Base): __tablename__ = "test_table" # 資料表名稱 id = Column(Integer, primary_key=True, autoincrement=True) name = Column(String(55)) age = Column(Integer) def __str__(self): return f"Test<id={self.id}, name={self.name}, age={self.age}>" def create_table(): Base.metadata.create_all(engine) def drop_table(): Base.metadata.drop_all(engine) # if __name__ == '__main__': # # 因為sqlalchemy無法更新資料表,所以需要刪掉重創 # drop_table() # create_table() ``` - 對DB創建了一張新的table=`test_table`,使用`Column`來定義SQL表中的欄位 :::warning 要特別注意 sqlalchemy `不允許修改表結構`,如果需要修改的話,需要刪除重建 ::: ### CRUD - `hello_postgres_with_sqlalchemy/crud.py` ```python from sqlalchemy.orm import Session from hello_postgres_with_sqlalchemy.database import get_db_session from hello_postgres_with_sqlalchemy.models import Test # 新增 def create_test(db: Session, test_data: dict) -> Test: created_test = Test(**test_data) db.add(created_test) db.commit() return created_test # 讀取 def get_test_by_id(db: Session, test_id: int) -> Test: result = db.query(Test).filter_by(id=test_id).first() db.commit() return result # 一次讀取100筆 def get_all_test(db: Session, skip: int = 0, limit: int = 100): result = db.query(Test).offset(skip).limit(limit).all() db.commit() return result # 更新 def update_test(db: Session, test_id: int, _update_data: dict) -> int: r = db.query(Test).filter_by(id=test_id).update(_update_data) db.commit() return r # 刪除 def delete_test(db: Session, test_id: int) -> int: r = db.query(Test).filter_by(id=test_id).delete() db.commit() return r if __name__ == '__main__': _db = next(get_db_session()) print(len(get_all_test(_db))) # 6 data = {"name": "hello", "age": 18} test_data = create_test(_db, data) print(test_data) # Test<id=10, name=hello, age=18> print(get_test_by_id(_db, test_data.id)) # Test<id=10, name=hello, age=18> update_data = {"name": "hello2", "age": Test.age + 5} print(update_test(_db, test_data.id, update_data)) # 1 print(len(get_all_test(_db))) # 7 print(get_test_by_id(_db, test_data.id)) # Test<id=10, name=hello2, age=23> print(delete_test(_db, test_data.id)) # 1 print(get_test_by_id(_db, test_data.id)) # None print(len(get_all_test(_db))) # 6 ``` - 透過`sqlalchemy.orm`的`Session`,對不同的SQL表進行query - 如果只要查詢單一數據行,使用`first()`、如果要回傳所有符合條件的數據,使用`all()` - `add`此實例物件到資料庫`session` ### Postgresql command line 語法 - `select * from pg_stat_activity;`: 等於mysql `show processlist` 查看當前有哪些process在執行 - `psql -U <username>`: 登入方式 - `\l`: show all database - `\c <database_name>`: choose your database - `\dt`: show all tables in current database - `\d+ <table_name>`: 那張table的欄位描述 - `CREATE DATABASE <name>;`: 創建新的DB ### 完整的程式碼 - https://github.com/s9891326/Hello/tree/master/hello_postgres_with_sqlalchemy ### Reference - https://www.jianshu.com/p/b219c3dd4d1e - https://medium.com/@kevinwei30/sqlalchemy-in-python-with-postgresql-d965ca20de59