Python 數(shù)據(jù)庫(kù)管理ORM 的終極形態(tài)?
ORM
大家都知道ORM(Object Relational Mapping)是一種將對(duì)象和關(guān)系數(shù)據(jù)庫(kù)中的表進(jìn)行映射的技術(shù),它可以讓開(kāi)發(fā)者更加方便地操作數(shù)據(jù)庫(kù),而不用直接使用SQL語(yǔ)句。
直接使用SQL語(yǔ)句操作數(shù)據(jù)庫(kù),雖然可以讓開(kāi)發(fā)者直接與數(shù)據(jù)庫(kù)打交道,但手動(dòng)編寫(xiě)SQL語(yǔ)句,容易出錯(cuò),而且靈活性上比較欠缺。相比之下,使用ORM(以SQLAlchemy
為例)有更加易于使用、更加靈活、能防止?SQL
?注入攻擊、更加易于測(cè)試的優(yōu)勢(shì)。
點(diǎn)擊查看優(yōu)勢(shì)說(shuō)明
當(dāng)然,使用?SQLAlchemy
?也會(huì)增加代碼的復(fù)雜度,需要學(xué)習(xí)額外的知識(shí)和 API。因此,在實(shí)際應(yīng)用中需要根據(jù)具體情況進(jìn)行選擇。
那么有沒(méi)有一種技術(shù)或者框架?既不用增加太多的應(yīng)用成本,又兼具以SQLAlchemy
為代表的ORM 框架的優(yōu)勢(shì)?呢?答案是肯定的,那就是我們今天介紹的主角?sqlmodel
.
我們就以?Fastapi
?開(kāi)發(fā)創(chuàng)建用戶和查詢用戶?兩個(gè)功能的接口來(lái)對(duì)比一下 ,SQLAlchemy
?和?sqlmodel
,?sqlmodel
?和 只使用?SQL
的差異。
使用SQLAlchemy
安裝
pip install sqlalchemy
示例代碼
from fastapi import FastAPI, Depends, HTTPExceptionfrom sqlalchemy import create_engine, Column, Integer, Stringfrom sqlalchemy.orm import Session, declarative_base, sessionmaker
SQLALCHEMY_DATABASE_URL = "mysql://user:password@host:port/database"engine = create_engine(SQLALCHEMY_DATABASE_URL)
SessionLocal = sessionmaker(autocommit=False, autoflush=False, bind=engine)
Base = declarative_base()
app = FastAPI()class User(Base):
? ?__tablename__ = "users"
? ?id = Column(Integer, primary_key=True, index=True)
? ?name = Column(String(50))
? ?age = Column(Integer)class UserIn(Base):
? ?name: str
? ?age: intclass UserOut(Base): ? ?id: int
? ?name: str
? ?age: intclass UserUpdate(Base):
? ?name: Optional[str] = None
? ?age: Optional[int] = NoneBase.metadata.create_all(bind=engine)def get_db():
? ?db = None
? ?try:
? ? ? ?db = SessionLocal() ? ? ? ?yield db ? ?finally:
? ? ? ?db.close()def create_user(db: Session, user: UserIn):
? ?db_user = User(name=user.name, age=user.age)
? ?db.add(db_user)
? ?db.commit()
? ?db.refresh(db_user) ? ?return db_userdef read_user(db: Session, user_id: int):
? ?db_user = db.query(User).filter(User.id == user_id).first() ? ?if not db_user: ? ? ? ?raise HTTPException(status_code=404, detail="User not found") ? ?return db_userdef read_all_user(db: Session, ):
? ?db_user = db.query(User).all() ? ?if not db_user: ? ? ? ?raise HTTPException(status_code=404, detail="User not found") ? ?return db_userasync def create_user_view(user: UserIn, db: Session = Depends(get_db)): ? ?return create_user(db, user)async def read_user_view(user_id: int, db: Session = Depends(get_db)): ? ?return read_user(db, user_id)async def read_all_user_view(db: Session = Depends(get_db)): ? ?return read_all_user(db)
代碼解釋
User
?是數(shù)據(jù)模型類的名稱,id
、name
、age
?是表中的列名。UserIn
?是創(chuàng)建用戶的請(qǐng)求參數(shù)模型,UserOut
?是查詢用戶的響應(yīng)數(shù)據(jù)模型,UserUpdate
?是更新用戶的請(qǐng)求參數(shù)模型。
使用?create_engine
?函數(shù)創(chuàng)建一個(gè)數(shù)據(jù)庫(kù)連接引擎,使用?sessionmaker
?函數(shù)創(chuàng)建一個(gè)數(shù)據(jù)庫(kù)會(huì)話工廠,使用?declarative_base
?函數(shù)創(chuàng)建一個(gè)基類。在創(chuàng)建表時(shí),使用?Base.metadata.create_all
?函數(shù)創(chuàng)建表。
使用?get_db
?函數(shù)獲取數(shù)據(jù)庫(kù)會(huì)話對(duì)象,使用?create_user
?和?read_user
?函數(shù)進(jìn)行數(shù)據(jù)庫(kù)操作。在視圖函數(shù)中,只需要調(diào)用這些函數(shù)即可完成相應(yīng)的業(yè)務(wù)邏輯。
上面的代碼已經(jīng)非常簡(jiǎn)潔直觀,但是還是有有一定的學(xué)習(xí)成本,下面我們來(lái)看下使用我們今天的主角 --?sqlmodel
?需要怎樣來(lái)實(shí)現(xiàn)上面的接口。
使用sqlmodel
安裝 sqlmodel
pip install sqlmodel
示例代碼
點(diǎn)擊查看完整代碼
和SQLAlchemy
的主要使用差異在參數(shù)的定義上,使用多處繼承,而不是各自定義的方法:
# Code above omitted ??...class UserBase(SQLModel):
? ?name: Optional[str] = None
? ?age: Optional[int] = Noneclass User(UserBase, table=True): ? ?id: Optional[int] = Field(default=None, primary_key=True)class UserIn(UserBase): ? ?passclass UserOut(UserBase): ? ?id: intclass UserUpdate(UserBase): ? ?pass...# Code below omitted ??
繼承這一點(diǎn)對(duì)于還在頻繁迭代的系統(tǒng)中非常重要,因?yàn)橥瑯犹砑?一個(gè)user的數(shù)據(jù)結(jié)構(gòu),SQLAlchemy需要修改4處地方,而sqlmodel
?僅僅只需要修改一處。如果有多個(gè)表,這個(gè)便利性的優(yōu)勢(shì)會(huì)尤為突出。
這也就引出了sqlmodel
具有的優(yōu)勢(shì):
簡(jiǎn)短:?最小化代碼重復(fù)。一個(gè)單一的類型注解做了很多工作。無(wú)需在 SQLAlchemy 和?
Pydantic
?中復(fù)制模型。簡(jiǎn)單易用:?API 設(shè)計(jì)簡(jiǎn)單易用,強(qiáng)大的編輯器支持,學(xué)習(xí)曲線較低,可以快速上手。它使用 Python 類型注解來(lái)定義數(shù)據(jù)模型,可以自動(dòng)推斷數(shù)據(jù)庫(kù)表結(jié)構(gòu),同時(shí)支持類型檢查和數(shù)據(jù)驗(yàn)證。
可擴(kuò)展:?擁有?
SQLAlchemy
?和?Pydantic
?的所有功能。高性能:?
sqlmodel
?采用了一些性能優(yōu)化策略,比如使用預(yù)編譯 SQL 語(yǔ)句、減少數(shù)據(jù)庫(kù)連接次數(shù)等,可以提高數(shù)據(jù)庫(kù)操作的性能。支持異步操作:?
sqlmodel
?支持異步操作,可以與 asyncio 庫(kù)一起使用,可以在高并發(fā)場(chǎng)景下提高程序的性能。支持原生 SQL:?
sqlmodel
?支持原生 SQL,可以使用原生 SQL 語(yǔ)句進(jìn)行數(shù)據(jù)庫(kù)操作,同時(shí)還支持參數(shù)綁定和 SQL 注入防護(hù)。
SQLModel 實(shí)際上是在 Pydantic 和 SQLAlchemy 之間增加了一層兼容適配,經(jīng)過(guò)精心設(shè)計(jì)以兼容兩者。SQLModel 旨在簡(jiǎn)化 FastAPI 應(yīng)用程序中與 SQL 數(shù)據(jù)庫(kù)的交互。它結(jié)合了 SQLAlchemy 和 Pydantic,并嘗試盡可能簡(jiǎn)化代碼,讓代碼重復(fù)減少到最低限度,同時(shí)盡可能讓開(kāi)發(fā)人員獲得最佳的開(kāi)發(fā)體驗(yàn)。
原生的SQL語(yǔ)句支持舉例
有時(shí)候我們可能需要使用原生的SQL語(yǔ)句來(lái)進(jìn)行一些復(fù)雜的操作。
from sqlmodel import create_engine, Session# 創(chuàng)建數(shù)據(jù)庫(kù)引擎engine = create_engine("sqlite:///example.db")# 創(chuàng)建Session對(duì)象with Session(engine) as session: ? ?# 執(zhí)行原生的SQL語(yǔ)句
? ?result = session.execute("SELECT * FROM users WHERE age > :age", {"age": 18}) ? ?# 處理查詢結(jié)果
? ?for row in result: ? ? ? ?print(row)
高級(jí)用法:結(jié)合mixin類,簡(jiǎn)化數(shù)據(jù)庫(kù)操作
結(jié)合mixin類,簡(jiǎn)化數(shù)據(jù)庫(kù)操作,一處封裝,處處適用。
如果熟悉fastapi,且仔細(xì)觀察上面的完整代碼就會(huì)發(fā)現(xiàn),除了下面這段,其他的都是標(biāo)準(zhǔn)的Fastapi
?接口開(kāi)發(fā)需要的信息。而這樣的操作結(jié)合我們接下來(lái)介紹的mixin
方法,就可以給這只虎添上翅膀。
class User(UserBase, table=True): ? ?id: Optional[int] = Field(default=None, primary_key=True)
tips: 在面向?qū)ο缶幊讨?,Mixin是一種重用代碼的方式,它是一個(gè)類,包含一些方法和屬性,可以被其他類繼承和使用。Mixin類通常不是獨(dú)立的類,而是用于增強(qiáng)其他類的功能。Mixin類的優(yōu)點(diǎn)在于可以將代碼分解為小的、可重用的部分,從而減少代碼的重復(fù)和冗余。Mixin類可以被多個(gè)類繼承,從而避免了多重繼承的問(wèn)題。
import uvicornfrom typing import Optional, Unionfrom fastapi import FastAPI, Depends, HTTPExceptionfrom sqlmodel import Field, Session, SQLModel, create_engine, selectclass ActiveRecord(SQLModel):
? ?def by_id(cls, _id: int, session):
? ? ? ?obj = session.get(cls, _id) ? ? ? ?if obj is None: ? ? ? ? ? ?raise HTTPException(status_code=404, detail=f"{cls.__name__} with id {id} not found") ? ? ? ?return obj
? ?def all(cls, session): ? ? ? ?return session.exec(select(cls)).all()
? ?def create(cls, source: Union[dict, SQLModel], session): ? ? ? ?if isinstance(source, SQLModel):
? ? ? ? ? ?obj = cls.from_orm(source) ? ? ? ?# elif isinstance(source, dict):
? ? ? ?elif isinstance(source, dict):
? ? ? ? ? ?obj = cls.parse_obj(source)
? ? ? ?session.add(obj)
? ? ? ?session.commit()
? ? ? ?session.refresh(obj) ? ? ? ?return obj ? ?def save(self, session):
? ? ? ?session.add(self)
? ? ? ?session.commit()
? ? ? ?session.refresh(self)class UserBase(SQLModel):
? ?name: Optional[str] = None
? ?age: Optional[int] = Noneclass User(UserBase, ActiveRecord, table=True): ? ?id: Optional[int] = Field(default=None, primary_key=True)
? ?__table_args__ = {'extend_existing': True}class UserIn(UserBase): ? ?passclass UserOut(UserBase): ? ?id: intclass UserUpdate(UserBase): ? ?pass# 注意:需要提前安裝pymysql, pip install pymysqlSQLALCHEMY_DATABASE_URL = "mysql+pymysql://user:password@host:port/database"engine = create_engine(SQLALCHEMY_DATABASE_URL)def create_db_and_tables():
? ?SQLModel.metadata.create_all(engine)def get_session(): ? ?with Session(engine) as session: ? ? ? ?yield session
app = FastAPI()def on_startup():
? ?create_db_and_tables()def create_user(hero: UserIn, session: Session = Depends(get_session)): ? ?return User.create(hero, session)def read_user(session: Session = Depends(get_session)): ? ?return User.all(session)def read_user(user_id: int,session: Session = Depends(get_session)): ? ?return User.by_id(user_id, session)if __name__ == '__main__':
? ?uvicorn.run("main:app", reload=True)
總結(jié)
使用SQLModel
?+?mixins
可以在公共的邏輯里面實(shí)現(xiàn)增刪改查操作,處封裝,處處適用,減少了代碼的重復(fù)性和冗余性。
特點(diǎn)SQLAlchemysqlmodel數(shù)據(jù)庫(kù)支持支持多種數(shù)據(jù)庫(kù),包括MySQL、PostgreSQL、SQLite等支持多種數(shù)據(jù)庫(kù),包括MySQL、PostgreSQL、SQLite等ORM功能提供全面的ORM功能,支持對(duì)象關(guān)系映射、事務(wù)處理、查詢構(gòu)建等提供輕量級(jí)的ORM功能,支持對(duì)象關(guān)系映射、查詢構(gòu)建等性能性能較好,支持緩存、連接池等優(yōu)化手段性能較好,支持緩存、連接池等優(yōu)化手段學(xué)習(xí)難度學(xué)習(xí)曲線較陡峭,需要掌握復(fù)雜的概念和API學(xué)習(xí)曲線較平緩,易于上手和使用文檔和社區(qū)支持提供完善的文檔和活躍的社區(qū)支持文檔和社區(qū)支持相對(duì)較少代碼規(guī)范代碼規(guī)范較為靈活,可以自由組織代碼結(jié)構(gòu)代碼規(guī)范較為嚴(yán)格,需要按照規(guī)范組織代碼結(jié)構(gòu)
建議:
根據(jù)上述比較,我們可以得出以下選擇建議:
如果需要使用全面的ORM功能,或者需要使用復(fù)雜的查詢構(gòu)建和事務(wù)處理等功能,建議選擇SQLAlchemy。
如果需要使用輕量級(jí)的ORM功能,或者需要快速上手和使用,建議選擇sqlmodel。
如果需要支持多種數(shù)據(jù)庫(kù),建議兩者都可以考慮使用。
如果對(duì)文檔和社區(qū)支持有較高的要求,建議選擇SQLAlchemy。
如果對(duì)代碼規(guī)范有較高的要求,建議選擇sqlmodel。