Sqlalchemy与Django的ORM的对照使用

643 阅读4分钟

学习与使用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()