学习与使用FastApi的过程中选择Sqlalchemy作为Sql数据库映射的模型,在这记录一下学习与使用过程
sqlalchemy与Django-ORM都实现了对关系型(SQL)数据的映射,但是他们在使用的时候有很大的不同,因为sqlalchemy入侵小, Django-ORM是嵌入在Django框架里的,入侵更大, sqlalchemy更通用,可以适用于任何Python与数据库映射的地方, 但由此也带来一个问题: sqlalchemy与Django-ORM使用上有很多不同的地方甚至对于我这用用惯了Django-ORM的感觉不是很方便, 所以本文侧重记录Sqlalchemy的增删改查, 偶尔对比一下Django-ORM, 以作学习记录
版本:
- Python 3.11.3
- SQLAlchemy 1.4.47
- PyMySQL 1.0.3
- aiomysql 0.1.1
- databases 0.7.0
同步操作数据库方案:PyMySQL+SQLAlchemy
异步操作数据库方案:aiomysql+databases+SQLAlchemy
同步(Sync)连接数据库并增删改查
为什么说"同步"?
- sqlalchemy有同步操作数据库的模式也有异步操作数据库的方式, 这个得分开来记录, 不然很容易混淆,特别是对FastApi这种异步框架而言, 如果错误地在异步代码里写了同步连接数据库对性能影响非常大
这个是最最最基本的增删改查, 而后关于所有的其他花样大体也是基于此基础上不断的加条件啥的,知道了这几个最基础的,然后遇到实际需要再增加条件即可
Sqlalchemy连接数据库:
from sqlalchemy import create_engine
from sqlalchemy.orm import sessionmaker, scoped_session
# mysql+pymysql 指定引擎
# root:rootroot 为账号密码
# 127.0.0.1:3306/xxx 为地址 端口 数据库
# charset=utf8mb4 字符设定
SYNC_DATABASE_URL = "mysql+pymysql://root:rootroot@127.0.0.1:3306/xxx?charset=utf8mb4"
# 初始化引擎,echo=True可以在输出端看到执行的sql有助于debug, pool_size为连接池最大大小,如果不想限制这个就把这个去掉并加上参数poolclass=NullPool, 这样就关闭连接池,每次连接都重新创建
sql_engine = create_engine(
SYNC_DATABASE_URL, echo=True, pool_size=max_size
# poolclass=NullPool
)
SessionLocal = sessionmaker(autocommit=False, autoflush=True, bind=sql_engine)
ScopedSession = scoped_session(SessionLocal)
def get_sync_db() -> Session:
"""
返回与mysql交互的session
线程与协程都是安全的(不建议在async标记的request请求里使用, 会阻塞所有请求的, async应使用aio_db)
需要特别注意,需要手动close(必须)
demo1:
try:
db = get_sync_db()
...
finally:
db.close()
demo2:
with get_sync_db() as db:
...
:return:
"""
return ScopedSession()
# Dependency
def get_sync_db_depend() -> Session:
"""
详见官方文档: https://fastapi.tiangolo.com/zh/tutorial/sql-databases/
这是一个协程,用Depends装入后会自动帮你执行yield后面的代码(fastapi帮做的), 建议自己用with get_sync_db() as db: 自己控制也挺好的
:return:
"""
db = get_sync_db()
try:
yield db
finally:
db.close()
class DBSession:
"""
跟get_sync_db()作用差不多, 但是get_sync_db()需要手动close, 或者用with get_sync_db() as db, 感觉太麻烦了
用这个可以自动close
使用方式1:
db_session = DBSession()
db = db_session.get_db()
db.query()... 就正常使用就好了,不用手动close了
注意! 不可以这么写:
db = DBSession().get_db()
如果这么写会直接调用close
这个变量db_session看似没用, 但是销毁的时候会自动调用db.close()
使用方式2:
db_session = DBSession()
db_session.db.query()... 就正常使用就好了,不用手动close了
"""
def __init__(self):
gen = self._get_sync_db()
self.db = next(gen)
self.gen = gen
def get_db(self):
return self.db
@staticmethod
def _get_sync_db():
db = ScopedSession()
try:
yield db
finally:
db.close()
建立自己的Model
from datetime import datetime
import sqlalchemy
from sqlalchemy.ext.declarative import declarative_base
Base = declarative_base()
metadata = Base.metadata
# 没找到办法直接继承Base然后再让别人继承, 所以就多一个类抽出来, 定义model的时候就这么定义: class ModeName(Common, Base): ...
class Common:
id = sqlalchemy.Column(sqlalchemy.Integer, primary_key=True)
gmt_created = sqlalchemy.Column(sqlalchemy.DateTime, default=datetime.now)
gmt_modified = sqlalchemy.Column(sqlalchemy.DateTime, default=datetime.now, onupdate=datetime.now)
# 封装一个转化为dict的方法
def to_dict(self):
return {column.name: getattr(self, column.name) for column in self.__table__.columns}
# 类似于这样声明
class DemoModel(Common, Base):
__tablename__ = 'demo_table'
column1 = sqlalchemy.Column(
sqlalchemy.String(50),
comment='xxx',
index=True,
unique=True,
nullable=False,
)
column2 = sqlalchemy.Column(
sqlalchemy.String(100),
comment='xx',
nullable=False,
)
....
新增
with get_sync_db() as db:
demo = DemoModel(column1=xxx, column2=xxx)
db.add(demo)
db.commit() # 这句属于提交db.add()这个修改类似于Django中的Model.save()
db.refresh(demo) # 这句在Django里没有,但是实际也在save里帮做了,相当于查一次数据库,把这个新插入的数据复制给demo,补全一下类似与Id gmt_created这种默认值
print(demo.to_dict())
查询
# 使用filter_by
with get_sync_db() as db:
db.query(DemoModel).filter_by(column1='xx').first() # 类似Django里的filter, 不过Sqlalchemy还有个一个自己的filter
print(demo.to_dict())
# 使用filter
with get_sync_db() as db:
db.query(DemoModel).filter(DemoModel.column1 == 'xx').first() # 类似Django里的Q, 里面能加一些筛选条件,如">" "<" "==" 等
print(demo.to_dict())
修改
# 第一种方式: 直接改对应的model数据实例,然后commit()
with get_sync_db() as db:
demo = db.query(DemoModel).filter_by(column1='xx').first() # 类似Django里的filter, 不过Sqlalchemy还有个一个自己的filter
demo.column1 = 'other'
db.commit()
# 第二种方式: 先查再改,然后commit()
with get_sync_db() as db:
db.query(DemoModel).filter_by(column1='xx').update({'column1':'other'})
db.commit()
删除
# 第一种方式: 删除model数据实例,然后commit()
with get_sync_db() as db:
demo = db.query(DemoModel).filter_by(column1='xx').first()
db.delete(demo)
db.commit()
# 第二种方式: 直接删对应条件的,然后commit()
with get_sync_db() as db:
db.query(DemoModel).filter_by(column1='xx').delete()
db.commit()