前言
最近进行接口自动化框架优化时,总感觉直接写sql语句影响代码的美感,所以决定使用SQLAlchemy。但是有一个麻烦的地方, 就是需要手动定义表结构,这样就比较费时了,一个表都有几十个字段,手动定义显然不可行,投入产出比低。庆幸的是,SQLAlchemy提供了 反射技术,它使得 SQLAlchemy 能够在不提前定义表结构的情况下,直接从数据库中动态读取表结构并映射为Python对象。趁着这个机会正好 总结一下SQLAlchemy 反射技术。笔者采用的2.0版本。
自动映射
自动映射方法(automap_base() 和 prepare())更为简便和自动化,适用于不需要手动管理表对象的场景,适合于快速构建基于现有数据库的应用程序。
使用自动映射反射数据库
为了反射数据库,这里使用automap_base,使用automap_base创建一个Base对象,
ini
from sqlalchemy.ext.automap import automap_base
Base = automap_base()
然后,需要将一个引擎连接到想反射的数据库,初始化引擎,
ini
from sqlalchemy import create_engine
DATABASE_URL = f"mysql+pymysql://root:127.0.0.1:3306/pangxiaolu"
engine = create_engine(DATABASE_URL)
这样,就做好了反射数据库的准备工作:Base和引擎创建。
接下来,调用创建的Base对象的prepare方法,将扫描我们刚刚创建的引擎上的所有可用内容,并反射它所能反射的所有内容。
ini
Base.prepare(autoload_with=engine)
只需要这一行代码,就完成了整个数据库的映射工作。这个反射已经为每个表创建了ORM对象。我们输出测试一下,
python
for k, v in Base.classes.items():
print(f"{k}: {v}")
break
输出结果如下,
vbnet
user: <class 'sqlalchemy.ext.automap.user'>
当然,我们也可以这样访问映射的类,比如有一个表user
bash
User = Base.classes.user
print(User) # <class 'sqlalchemy.ext.automap.user'>
print(type(User)) # <class 'sqlalchemy.orm.decl_api.DeclarativeMeta'>
接下来,使用映射的类,就简单了
ini
from sqlalchemy.orm import Session
session = Session(bind=engine)
user = session.query(User).first()
注意:有些参数将被废弃,如果你是这样写的Base.prepare(engine, reflect=True)
,会发生警告,下面是官方文档介绍
vbnet
engine --
legacy; use AutomapBase.autoload_with. Used to indicate the Engine or Connection with which to reflect tables with, if AutomapBase.reflect is True.
Deprecated since version 1.4: The AutomapBase.prepare.engine parameter is deprecated and will be removed in a future release. Please use the AutomapBase.prepare.autoload_with parameter.
reflect --
legacy; use AutomapBase.autoload_with. Indicates that MetaData.reflect() should be invoked.
Deprecated since version 1.4: The AutomapBase.prepare.reflect parameter is deprecated and will be removed in a future release. Reflection is enabled when AutomapBase.prepare.autoload_with is passed.
可以看到这两参数后面将会被删除。
反射
反射单个表
我们需要创建元数据对象,
css
from sqlalchemy import create_engine, MetaData, Table
metadata = MetaData()
同样,需要初始化引擎,
ini
DATABASE_URL = f"mysql+pymysql://root:127.0.0.1:3306/pangxiaolu"
engine = create_engine(DATABASE_URL)
创建好元数据和引擎之后,就有了反射一个表所需要的一切,进行反射
ini
user = Table('user', metadata, autoload_with=engine)
autoload_with=engine:这是Table类的一个关键字参数,它告诉SQLAlchemy自动从数据库中加载user表的结构信息。autoload_with参数需要一个数据库引擎(engine)的实例作为值。
好了,我们来测试一下
scss
print(user.columns.keys()) # ['id', 'name', 'gender', 'area_code', 'mobile']
反射整个数据库
为了反射整个数据库,可以使用 metadata 对象的 reflect 方法。 reflect 方法会扫描引擎上的所有可用内容,并反射它所能反射的所有内容。
ini
metadata.reflect(bind=engine)
使用反射表,
ini
user = metadata.tables['user']
接下来使用就一样了。
反射原理
- 创建数据库引擎(Engine)
- 加载元数据:使用元数据(MetaData)对象来加载数据库的结构信息。
- 反射数据库结构:调用 MetaData 对象的 reflect() 方法
- 映射到类:Table对象被创建,使用反射得到的Table对象,SQLAlchemy可以自动创建或更新Python类,这些类映射到数据库表。这个过程称为ORM映射。
看自动映射的源码,发现也是调用MetaData 对象的 reflect() 方法,核心源码如下
ini
if reflect:
assert autoload_with
opts = dict(
schema=schema,
extend_existing=True,
autoload_replace=False,
)
if reflection_options:
opts.update(reflection_options)
cls.metadata.reflect(autoload_with, **opts) # type: ignore[arg-type] # noqa: E501
最后
好了,基本用法目前可以满足我做接口自动化,更多高级用法工作中用到再继续延伸吧,也可以查看官方文档介绍。