【Python】ORM 持久层框架 SQLAlchemy 的基本使用

Python SQLAlchemy ORM框架的使用

前言

作为从隔壁Java村来的Python小白,接触Python后,就像乡下人进城了一样——新奇!今年年初接了一个用Python Scrapy + FastAPI 重构原本 Java 写的爬虫网页分析逻辑。那到管道数据持久层的一步时,不能直接存储,而是要先查询,比对数据,再进行存储,而一些简单的Python连接数据库操作我始终觉得太像JDBC,每次都要这样连,似乎会重复工作量。如果能有像Mybatis一样的ORM框架就好了。

果然,搜了一圈发现了一位宝藏up主跟峰哥学编程的教程:SQLAlchemy ORM框架的使用

学习目标

  • 学习SQLAlchemy的基本使用

准备工作
准备一个python项目,这里我准备的是FastAPI项目,在requirements.txt加入各依赖:

mysqlclient==2.1.1
SQLAlchemy==2.0.23

或者 pip install 也可以。

准备好环境后,下面我们开始!

1. 数据库操作

1.1 连接数据库

SQLAlchemy+mysqldb 连接数据库,示例代码如下:

# 所需依赖
import sqlalchemy

# 创建引擎
engine = sqlalchemy.create_engine('<数据库类型>://<用户名>:<密码>@<地址>:<端口号>/<数据库名>?[编码方式]')
# 创建连接
connect = engine.connect()

# 示例查询
query = sqlalchemy.text('SELECT id, user_name, create_time, update_time FROM kcl_user')
# 返回的是值的集合
result_set = connect.execute(query)

for row in result_set:
    print(row)

# 关闭连接
connect.close()

其中,具体创建引擎应该参考上述代码改为自己实际的,我这里的示例做了描述,< >代表必填,[ ]代表可选项,例如连接本地的是:

engine = sqlalchemy.create_engine('mysql://root:123456@<地址>:<端口号>/<数据库名>?[编码方式]')

2.2 SQLAlchemy创建表

创建表的需求在一些爬虫需求里比较常见。以下演示如何创建表:

# 暂存元数据
meta_data = sqlalchemy.MetaData()

# 创建表
subject = sqlalchemy.Table(

    'subject',meta_data,
    sqlalchemy.Column('id', sqlalchemy.Integer, primary_key=True),
    sqlalchemy.Column('name', sqlalchemy.String(255), unique=True, nullable=False),
    sqlalchemy.Column('score', sqlalchemy.Integer, unique=False),
)

meta_data.create_all(engine)

其中,nullable 标识是否为空,false表示不可以,unique表示是否唯一,ture表示是。
而且,我们在实际使用的时候可以大胆执行meta_data.create_all(engine)方法,若该表存在,则不会覆盖掉它,哪怕字段变更了。

2.3 SQLAlchemy CRUD

这一小节我们来学习SQLAlchemy的一些CRUD基本操作。用我们上一小节刚刚创建的表做示例

2.3.1 新增

若读者有Python或Java等编程语言基础+SQL基础,那么基础的CRUD就很好理解了,我们只需要直接通过示例代码了解SQLAlchemy提供了何种方法,如何传参,如何提交即可:

新增单个

# 创建引擎
engine = sqlalchemy.create_engine('mysql://root:root@localhost:3306/kcl2024?charset=utf8', echo=True)

# 暂存元数据
meta_data = sqlalchemy.MetaData()

# 创建表
subject = sqlalchemy.Table(

    'subject',meta_data,
    sqlalchemy.Column('id', sqlalchemy.Integer, primary_key=True),
    sqlalchemy.Column('name', sqlalchemy.String(255), unique=True, nullable=True),
    sqlalchemy.Column('score', sqlalchemy.Integer, unique=False),
    sqlalchemy.Column('create_time', sqlalchemy.DateTime, unique=False),
)

meta_data.create_all(engine)

# 新增单个
insert_single = subject.insert().values(name='数学', score=100, create_time='2021-01-01 00:00:00')

with engine.connect() as connection:
    result = connection.execute(insert_single)
    print(result.inserted_primary_key)
    # 事务提交
    connection.commit()

批量新增
批量新增需要在execute带上.insert(),和一个字典列表:

# 新增多个
dict_list = [
    {'name': '体育', 'score': 100},
    {'name': '英语', 'score': 100},
    {'name': '物理', 'score': 100},
    {'name': '化学', 'score': 100},
    {'name': '生物', 'score': 100},
    {'name': '政治', 'score': 100},
]

with engine.connect() as connection:
    result_multi = connection.execute(subject.insert(), dict_list)
    # 事务提交
    connection.commit()

2.3.2 查询

示例:

with engine.connect() as conn:
    # 查询数据
    query = subject.select().where(subject.c.name == '语文')
    result = conn.execute(query)
    print(f'result_set={result.fetchall()}')
    # 关闭连接
    conn.close()

其中, 上面的.c表示column

其它条件
where里如果我们要加or、and等条件,需要额外引入sqlalchmy的对应方法,例如:

with engine.connect() as conn:
    # 查询数据
    query = subject.select().where(
        or_(subject.c.name == '数学',subject.c.name == '语文'),
        and_(subject.c.score > 60, subject.c.score <= 100)
    )
    result = conn.execute(query)
    print(f'result_set={result.fetchall()}')
    # 关闭连接
    conn.close()

2.3.3 删除

有了上面的基础,我们学习删除和修改将会快很多,直接上示例:

# 删除数据
conn.execute(subject.delete().where(subject.c.name == '语文'))
    query = subject.select()
    result = conn.execute(query)
    print(f'result_set={result.fetchall()}')

2.3.4 修改

示例:

# 更新数据
    conn.execute(subject.update().where(subject.c.id == 1).values(name='书法', score=99))

    query = subject.select().where(
        id == 1,
    )
    result = conn.execute(query)
    print(f'result_set={result.fetchall()}')

学到这里,我们发现SQLAlchemy的用法和Mybatis-Plus很像!

2. ORM映射

SQLAlchemy 也是一个 ORM对象关系映射框架,和Java的Mybatis、Mybatis-plus 类似,也支持直接通过对象自动映射到表,从而更方便CRUD的操作。

2.1 实体类

Mybatis 需要有对应的实体类,SQLAlchemy 也一样。实体类的属性其实对应的就是表的字段,因此这里的实体类形式很像对表的描述,例如:

from sqlalchemy import create_engine, Column, Integer, String, DateTime
from sqlalchemy.ext.declarative import declarative_base

# 创建引擎
engine = create_engine('mysql://root:root@localhost:3306/kcl2024?charset=utf8', echo=True)
Base = declarative_base()

# 定义映射关系类

class Subject(Base):
    __tablename__ = 'subject'
    id = Column(Integer, primary_key=True)
    name = Column(String(255), unique=True, nullable=True)
    score = Column(Integer, unique=False)

2.2 ORM CRUD

这里介绍通过ORM的方式实现CRUD

篇幅有限,我们假定我们已经前提准备好Session

2.2.1 增加

事实上语法与我们直接从connection执行sql很像,区别仅在于Session与对象实例传参方式,直接上示例:

# 创建实例
math = Subject(name='math', score=100)
chinese = Subject(name='chinese', score=90)

# 添加实例到 session
subject_list = [math, chinese]

# 添加实例到 session
session.add_all(subject_list)
session.commit()

这个示例演示了如何批量新增。单个新增参考connection的方式添加单个对象即可。最后别忘了commit哦。

2.2.2 查询

这里介绍几种常见的查询情况:

所有记录

# 查询所有记录
result = session.query(Subject).all()
# 遍历获取记录列表
for subject in result:
    print(subject.name, subject.score,type(subject))

查询出来的就是我们的Subject对象类型列表。

一条记录的情况

# 查询第一条记录
result_first = session.query(Subject.id, Subject.name, Subject.score).first()
if result_first:
    print(result_first.name, result_first.score)
    
# 只有一条记录的情况
result_one = session.query(Subject).filter(Subject.id == 1).one()
print(result_one.name, result_one.score)

# scalar() 返回第一列的值
result_scalar = session.query(Subject.name).filter(Subject.id == 1).scalar()
print(result_scalar)

查询记录数量

# 查询记录数量
result_count = session.query(Subject).count()
print(result_count)

2.2.3 删除

删除很简单,只需要调用delete()方法,我们直接看例子:

# 删除
session.query(Subject).filter(Subject.id == 22).delete()

result = session.query(Subject).all()
for subject in result:
    print(subject.id, subject.name, subject.score)

2.2.4 修改

SQLAlchemy 提供了如下修改方式,我们直接看示例:

# 修改方式1 ,先查询后修改
subject1 = session.query(Subject).filter(Subject.id == 1).first()
subject1.name = 'Java'

session.commit()

# 修改方式2 ,直接修改
session.query(Subject).filter(Subject.id == 2).update({'name': '高数'})

# 批量直接修改
session.query(Subject).filter(Subject.id > 2, Subject.id < 5).update({'score': 99})

# 查询全部
result = session.query(Subject).all()
for subject in result:
    print(subject.id, subject.name, subject.score)

值得注意的是,随便定一个对象是不能直接commit的,commit中间必须是从表里查询出来的对象。

2.3 更通用的Session

为了代码更复用,我们可以直接使用sqlalchemy.orm的Session:

from sqlalchemy import delete
from sqlalchemy.orm import Session
from db_init import engine
from orm_init import Subject

def batch_deleted():
    with Session(engine) as session:

        # session.excute 当 score < 100 时删除
        session.execute(delete(Subject).where(Subject.score < 100))

        result = session.query(Subject).all()
        for subject in result:
            print(subject.id, subject.name, subject.score)

3. 补充与总结

3.1 关联表如何查询?

使用 表的 join 方法。

对于现阶段的我们来说,由于Alchemy也只是作为一个python写的工具或者中间件以及独立的项目,一般还用不上join。真要join的情况一般还是在Java里的Mybatis/Mybtais-plus实现。

篇幅有限,这里暂时不描述了。
详情请参考参考教程以及官方文档:sqlalchemy官方文档

3.2 Mapped的映射方式

Mapped映射方式较新版本的SQLAlchemy特性。有了上文的基础,我们举个例子,就很好理解了。效果和colunm的映射方式是一致的:

from sqlalchemy.orm import sessionmaker, Mapped, mapped_column

# Mappped 的方式定义映射关系类
class Student(Base):
    __tablename__ = 'student'
    id: Mapped[int] = mapped_column(Column(Integer, primary_key=True))
    name: Mapped[str] = mapped_column(Column(String(255), unique=True, nullable=True))
    score: Mapped[int] = mapped_column(Column(Integer, unique=False))
    subject: Mapped[str] = mapped_column(Column(String(255), unique=False))
    
    
Base.metadata.create_all(engine)

3.3 Annotated 公共字段抽取

我们在Java字段中,常用BaseEntity来存放公共字段,然后用具体的entity extends 这个BaseEntity。在Python的SQLAlchemy中,类似的操作是用Annotated抽取公共字段,如下例子:

from typing_extensions import Annotated

# 公共字段
int_primary_key = Annotated[int, mapped_column(Integer, primary_key=True)]

# Mappped 的方式定义映射关系类
class Student(Base):
    __tablename__ = 'student'
    id: Mapped[int_primary_key]
    name: Mapped[str] = mapped_column(String(255), unique=True, nullable=True)
    score: Mapped[int] = mapped_column(unique=False)
    subject: Mapped[str] = mapped_column(String(255), unique=False)

3.4 总结

本文我们学习了Python的SQLAlchemy ORM 映射框架的使用。
我们首先学习了基于Connection的SQL语句执行,然后重点学习了基于ORM的映射CRUD。实际工作与学习中,基于ORM的方式更常用。

在学习的过程中,我们发现,Python 的SQLAlchemy使用方式与Java的Mybatis-Plus比较像,除了不用写xml,内置的方法单表查询也够用了。

3.5 结语

感谢大家阅读此文,希望我的内容能够给您带来启发和帮助。如果您喜欢我的文章,欢迎点赞、评论和分享,让更多人看到。期待与您在下一篇文章再相会!

参考教程

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值