Flask-SQLAlchemy数据库ORM

摘要:
SQLAlchemy首先需要知道什么是ORM:对象关系映射,对象关系映射。它可以将关系数据库的表结构映射到模型类对象,即实例化类对象。通过操作此对象,可以操作后台数据库表。使用ORM代替手动拼写本机SQL语句有两个优点:更易于使用;使用一组面向对象的操作来操作数据库可以有效地防止SQL注入。手动拼写SQL语句的一个主要缺点是可能没有考虑SQL注入
SQLAlchemy

首先需要知道ORM是什么

ORM: Object-Relational Mapping, 对象关系映射, 能够把关系型数据库的表结构映射到模型类对象上, 即实例化一个类对象, 通过操作该对象来操作后台数据库表

使用ORM而不使用手动拼写原生SQL语句有两点好处:

  1. 使用起来更加方便, 使用面向对象的一套操作即可操作数据库
  2. 能够有效防止SQL注入, 手动拼写SQL语句的一大缺点就是可能没有考虑到SQL注入

python中比较常用的ORM框架为Django自带的ORM和SQLAlchemy, 两者操作步骤上相差不到, 只是具体语法上有所差异. 且SQLAlchemy的功能比Django自带的ORM更加强大.

SQLAlchemy是一个单独的ORM框架, 可以直接安装并使用, 也可以与其他的python web框架一起使用, 当然一般情况下是和web框架一起使用的, 这里介绍的是在Flask框架中使用SQLAlchemy.

Flask-SQLAlchemy

安装数据库驱动

首先我们需要安装相应的数据库驱动,

这里我们需要知道的是, 当我们写好了SQL点击执行时, 是通过相应的数据库驱动把SQL语句传递给数据库服务器, 然后数据库服务器再解析并执行SQL, 把结果再通过数据库驱动返回.

而ORM的作用是将实例化模型类对象的操作转换为SQL语句, 或者将数据库返回的结果转化为实例化模型类对象. 并不能直接与数据库服务器进行交互

因此不管使用什么ORM, 首先就需要安装相应的数据库驱动, 这里我们以最常见的mysql数据库为例

python中mysql数据库驱动

  • 在python2中, 数据库驱动为MySQL-python(又叫MySQLdb), 通过命令pip install MySQL-python安装

  • 在python3中, 数据库驱动有两个:

    • mysqlclient: 完全兼容MySQLdb,同时支持 Python3.x

      通过命令pip install mysqlclient 安装

    • PyMySQL: PyMySQL 是纯 Python 实现的驱动,速度上比不上 MySQLdb, 但是在使用时需要加上pymysql.install_as_MySQLdb()才能兼容MySQLdb

      通过命令pip install PyMySQL安装

我们这里的环境是python3, 选择安装mysqlclient

创建模型类脚本

这里以简单的两个表(角色表test_roles和用户表test_users)为例, 他们的关系是一对多的, 即一个角色里面可以有多个用户, 如用户xiaoming和用户xiaohua都是系统管理员admin

首先创建最基本的脚本, 定义两个模型类

from flask import Flask
from flask_sqlalchemy import SQLAlchemy

# 创建Flask应用
app = Flask(__name__)

# 配置数据库连接
app.config['SQLALCHEMY_DATABASE_URI'] = 'mysql://root:root@127.0.0.1:3306/sqlalchemy_test'
# 创建数据库实例
db = SQLAlchemy(app)

# 定义角色类, 继承db.Model类
class Role(db.Model):
    # 手动定义表名, 否则默认为小写的类名
    __tablename__ = 'test_roles'
    # 定义字段, 格式为: 字段名 = db.Column(db.类型, 附加属性[主键/唯一性等])
    id = db.Column(db.Integer, primary_key=True)  # 主键必须显示定义出来
    name = db.Column(db.String(20), unique=True)

# 定义用户类, 继承db.Model类
class User(db.Model):
    # 手动定义表名, 否则默认为小写的类名
    __tablename__ = 'test_users'
    # 定义字段, 格式为: 字段名 = db.Column(db.类型, 附加属性[主键/唯一性等])
    id = db.Column(db.Integer, primary_keyTrue)  # 主键必须显示定义出来
    name = db.Column(db.String(20), unique=True)
    email = db.Column(db.String(30), unique=True)

现在只是定义好了单独的两张表, 接下来我们需要给这两张表添上一对多的关联关系

首先我们这里先提出两个概念帮助我们理解ORM: 逻辑上和物理上,

  • 类名和属性名, 属于逻辑上的概念, 即我们操作的模型对象

  • 数据库表名和表字段属于物理上的概念, 即真实表的结构

执行数据库迁移(Flask-Migrate)

这一步也可以放在定义表关联的后面, 即先把关联关系都定义好了再进行统一的迁移

我这里把这一步放在前面是要测试物理表结构改变后需要执行迁移, 而逻辑上表结构改变后不需要执行迁移

安装迁移插件Flask-Migrate

flask与Django框架定义上刚好相反, Django讲究的是大而全, 集成了很多可以直接使用的功能, 如ORM和管理脚本 python manager.py 等, 而flask讲究的是小而精, flask本身只是一个很小的框架, 有很多功能它本身并不具备, 而需要安装很多第三方插件来组装成一个完整的项目.

两个框架各有优缺点, django很全但是很笨重, 可能有些功能并不需要, flask很小但是很灵活, 可以自己添加想要的功能组件, 但是如果组件很多, 组装和维护都更加麻烦.

回到Flask-Migrate, 这个插件的功能是能够像django的python manager.py migrate一样, 用来迁移模型类, 在数据库中与创建定义的模型类对应的数据表

通过命令安装

pip install flask-migrate

app脚本中添加数据库迁移对象

在运行文件中导入flask_migrate模块并创建Migrate对象

from flask import Flask 
from flask_sqlalchemy import SQLAlchemy
from flask_migrate import Migrate
# 创建app对象
app = Flask(__name__)
# 创建数据库连接
db = SQLAlchemy(app)
# 创建数据库迁移对象
migrate = Migrate(app, db)

初始化数据库

在项目目录下, 先定义临时环境变量FLASK_APP, 这是运行flask应用必须定义的

export FLASK_APP=demo.py

执行下面命令进行初始化

flask db init

初始化完成后, 会自动在当前目录创建一个migrations的文件夹, 用来保存每次迁移的历史记录, 同时在数据库中也会创建一个alembic_version表, 用来记录版本信息

执行迁移

运行下面命令执行迁移

flask db migrate

迁移成功后, 会在migrations文件夹下的versions下创建一个迁移文件30b846daf2bd_.py, 该文件记录的是这一步迁移需要完成哪些数据库操作, 不过此时还没有将表同步至数据库中, 可以理解为只是生成了对应的sql语句而已

同步至数据库

运行下面命令执行同步数据库

flask db upgrade

同步完成后, 可以查看数据库中多出了test_rolestest_users两张表, 同时alembic_version表中也多了一条记录, 存的值就是上面的迁移文件30b846daf2bd_.py中的前缀30b846daf2bd

定义表关联

添加一对多的关联关系可以分为两步:

  1. 定义两个数据表的关系字段, 这里需要在用户表下定义外键字段role_id, 该字段需要写入数据库, 属于物理上表结构新增字段
  2. 定义两个模型类的关系字段, 这个字段只是为了在ORM操作时能够直接通过.属性的方式来获取到对应的模型类, 属于逻辑上的关联, 不需要写入数据库中, 且该关联字段并不是必须定义的, 不过为了方便建立联系, 我们一般都会定义它

首先定义两个数据表的关系字段(物理上)

在User类下在新增一个字段, 名为role_id, 该字段是外键, 存的是test_role表的id字段

注意定义ForeignKey的时候, 参数为数据库表名_字段名, 而不是类名_字段名, 因为这是物理表结构上的关联

class User(db.Model):
    # 手动定义表名, 否则默认为小写的类名
    __tablename__ = 'test_users'
    # 定义字段, 格式为: 字段名 = db.Column(db.类型, 附加属性[主键/唯一性等])
    id = db.Column(db.Integer, primary_keyTrue)  # 主键必须显示定义出来
    name = db.Column(db.String(20), unique=True)
    email = db.Column(db.String(30), unique=True)
    # 注意定义ForeignKey的时候, 参数为'数据库表名_字段名', 而不是'类名_字段名'
    role_id = db.Column(db.Integer, db.ForeignKey('test_roles.id'))

执行迁移命令

flask db migrate
flask db upgrade

可以看到数据库中的test_users表已经多了一个role_id字段

Flask-SQLAlchemy数据库ORM第1张

再定义两个模型类的关系字段(逻辑上)

理论上到这里已经可以算是完成了整个关系的定义, 可以直接创建数据了, 但是此时只是两个数据表之前建立了外键连接, 但是两个模型类之间还没有定义逻辑连接

此时我们如果想要查看某个User对象属于哪一个Role, 那么只能通过User对象拿到role_id, 再通过Role模型查询该role_id对应的Role对象

像这种情况如果想要能通过User.role的方式直接拿到对应的Role对象, 那么就需要建立两个模型类的逻辑连接

在User类下定义一个role字段, 该字段不是物理表结构字段, 而是逻辑关系字段, 因此使用的是db.relationship而不是db.Column

class User(db.Model):
    # 手动定义表名, 否则默认为小写的类名
    __tablename__ = 'test_users'
    # 定义字段, 格式为: 字段名 = db.Column(db.类型, 附加属性[主键/唯一性等])
    id = db.Column(db.Integer, primary_key=True)  # 主键必须显示定义出来
    name = db.Column(db.String(20), unique=True)
    email = db.Column(db.String(30), unique=True)
    # 注意定义ForeignKey的时候, 参数为'数据库表名_字段名', 而不是'类名_字段名'
    role_id = db.Column(db.Integer, db.ForeignKey('test_roles.id'))
    # 建立两个模型类的逻辑连接关系
    role = db.relationship('Role', backref='user')

第一个参数为目标对象的类名(不是表名), 第二个参数backref=为目标对象访问User对象使用的属性名, 即相当于在Role对象也定义了一个名为user的属性, 可以直接通过.user访问到对应的User对象, 当然这里返回的是一个User对象的列表, 因为一个Role下可能有多个User对象

同理, 这个逻辑关系不仅可以定义在User类中, 也可以定义在Role类中(只需要在其中一个类中定义即可), 但是需要把相应的属性名修改一下, ,如下:

# 定义角色类, 继承db.Model类
class Role(db.Model):
    # 手动定义表名, 否则默认为小写的类名
    __tablename__ = 'test_roles'
    # 定义字段, 格式为: 字段名 = db.Column(db.类型, 附加属性[主键/唯一性等])
    id = db.Column(db.Integer, primary_key=True)  # 主键必须显示定义出来
    name = db.Column(db.String(20), unique=True)
    # 建立两个模型类的逻辑连接关系
    user = db.relationship('User', backref='role')

创建数据测试逻辑关联

在前面定义了临时变量FLASK_APP目录中, 运行flask shell, 进入像Django的python manager.py shell一样的python shell中

# 导入app中的db和模型类
>>> from demo import db, User, Role
# 创建一个Role对象, 名为admin
>>> admin = Role(name='admin')
# 保存该条数据提交至数据库
>>> db.session.add(admin)
>>> db.session.commit()
# 创建一个User对象, 名为xiaoming, role_id为刚才创建的admin的id
>>> xiaoming = User(name='xiaoming', email='xiaoming.com', role_id=admin.id)
# 保存该条数据提交至数据库
>>> db.session.add(xiaoming)
>>> db.session.commit()
# 直接通过User.role访问xiaoming的role
>>> xiaoming.role
<Role 1>
# 直接通过Role.user访问admin下的user
>>> admin.user
[<User 1>]

可以发现创建的逻辑关系字段, 并不需要执行迁移命令, 因为并没有改变数据库的表结构

优化模型类

前面定义的模型类已经可以完成主要功能了, 接下来设置一些属性和方法, 完善模型类, 使用起来能够更加方便

重写__init__方法

在实例化模型类的时候, 每个字段都要使用关键字参数的方式赋值, 我们可以重写__init__方法, 让我们赋值的时候更加方便

重写__repr__方法

前面我们打印出来的对象结果都是<类名 ID>的形式, 阅读起来不太友好, 我们可以重写__repr__方法, 让打印结果的可读性更高

最终模型类如下:

from flask import Flask from flask_sqlalchemy import SQLAlchemy
from flask_migrate import Migrate

# 创建Flask应用
app = Flask(__name__)

# 配置数据库连接
app.config['SQLALCHEMY_DATABASE_URI'] = 'mysql://root:root@47.102.114.90:3306/sqlalchemy_test'
# 创建数据库实例
db = SQLAlchemy(app)
# 创建数据库迁移对象
migrate = Migrate(app, db)


# 定义角色类, 继承db.Model类
class Role(db.Model):
    # 手动定义表名, 否则默认为小写的类名
    __tablename__ = 'test_roles'
    # 定义字段, 格式为: 字段名 = db.Column(db.类型, 附加属性[主键/唯一性等])
    id = db.Column(db.Integer, primary_key=True)  # 主键必须显示定义出来
    name = db.Column(db.String(20), unique=True)

    def __init__(self, name):
        self.name = name

    def __repr__(self):
        return f'<Role {self.name}>'


# 定义用户类, 继承db.Model类
class User(db.Model):
    # 手动定义表名, 否则默认为小写的类名
    __tablename__ = 'test_users'
    # 定义字段, 格式为: 字段名 = db.Column(db.类型, 附加属性[主键/唯一性等])
    id = db.Column(db.Integer, primary_key=True)  # 主键必须显示定义出来
    name = db.Column(db.String(20), unique=True)
    email = db.Column(db.String(30), unique=True)
    # 注意定义ForeignKey的时候, 参数为'数据库表名_字段名', 而不是'类名_字段名'
    role_id = db.Column(db.Integer, db.ForeignKey('test_roles.id'))
    # 建立两个模型类的逻辑连接关系
    role = db.relationship('Role', backref='user')

    def __init__(self, name, email, role):
        self.name = name
        self.email = email
        self.role = role  # 注意这里的role参数类型是Role对象, 而不是role_id, 当然你也可以加上role_id参数

    def __repr__(self):
        return f'<User {self.name}>'

增删改查方法(CRUD)

以下操作在ipython中执行, 先进入ipython, 导入demo应用中的模型类和db数据库对象

$ ipython
In [1]: from demo import db, Role, User

新增一条记录 add(obj)

# 创建模型对象
In [2]: staff = Role('staff')
# 添加对象至会话中
In [3]: db.session.add(staff)
# 将会话中的操作提交至数据库
In [4]: db.session.commit()

这里的会话不是 Flask 的会话,而是 Flask-SQLAlchemy 的会话。它本质上是一个 数据库事务的加强版本

一次性新增多条记录 add_all(list)

# 创建多个User对象
In [37]: zhao = User('zhao', 'zhao.qq.com', admin)
In [38]: qian = User('qian', 'qian.163.com', admin)
In [39]: sun = User('sun', 'sun.qq.com', staff)
In [40]: li = User('li', 'li.163.com', staff)
# add_all([对象列表])
In [41]: db.session.add_all([zhao, qian, sun, li])
# 提交数据库
In [42]: db.session.commit() 

# 查询模型对象
staff = Role.query.filter_by(name='staff').first()
# 删除对象添加至会话中
In [10]: db.session.delete(staff)
# 将会话中的操作提交至数据库
db.session.commit()

方法一: 获取到模型对象再修改对象属性

# 查询模型对象
In [12]: admin = Role.query.filter_by(name='admin').first()
# 修改对象属性
In [13]: admin.name = 'super'
# 添加会话并提交数据库
In [14]: db.session.add(admin)
In [15]: db.session.commit()

方法二: 查询时直接update修改字段

# 调用查询结果集的update方法, 参数为一个字典, 将需要修改的字段以键值对的方式写入
# 注意这里没有使用first(), 因为first()返回的类型是一个模型对象, 只有查询结果集才有update方法
In [17]: Role.query.filter_by(name='super').update({'name': 'admin'})
Out[17]: 1
# 将修改提交, 这里不需要调用db.session.add(), 因为也没有模型对象可以传入
In [18]: db.session.commit() 

all()

返回一个列表, 列表中是所有查询到的模型类对象, 若查不到则返回空列表[]

In [43]: User.query.all()
Out[43]: [<User zhao>, <User qian>, <User sun>, <User li>]

first()

返回查找集的第一个模型类对象, 若查不到则返回None

In [44]: User.query.first()
Out[44]: <User zhao>

get(ident)

get参数只能是主键ID, 返回的是模型类对象(不是查找集), 因此不需要.first(), 若查不到则返回None

In [19]: role = Role.query.get(1)
In [20]: role
Out[20]: <Role admin>

filter_by(属性1=xxx, 属性2=xxx)

filter_by参数的字段条件只能用于等号, 且只能满足并且(and)关系的查询, 返回的是查找集, 若最终想返回模型类字段, 则需要使用.first()或者.all(), 若查不到则返回None

# 实现查询: User名字为li且邮箱为li.163.com的数据
In [3]: User.query.filter_by(name='li', email='li.163.com').first()
Out[3]: <User li>

filter(类名.属性1xxx, 类名.属性2xxx)

filter_by是filter的一个阉割版, filter的功能更加强大, 能实现或(or)关系, 也能实现模糊查询, 大于小于查询等, 需要注意的是条件中必须是类名.属性的格式, 而不能像filter_by那样只使用属性名

# 实现查询: User名字为li且邮箱为li.163.com的数据, 注意与上面的filter_by对比
# 1. 属性前面需要加上模型类名
# 2. 一个等号需要改成两个连等号
In [8]: User.query.filter(User.name=='li', User.email=='li.163.com').first()
Out[8]: <User li>
# 实现查询: User的ID大于3或者邮箱以qq.com结尾的数据
# or操作需要导入该模块
In [9]: from sqlalchemy import or_
# 1. or_(条件1, 条件2)参数为多个或者关系的条件
# 2. 等于(==), 大于(>), 大于等于(>=), 小于同理
# 3. 属性.endswith(), 以...结尾, 同理.startswith(), 以...开头
In [14]: User.query.filter(or_(User.id>3, User.email.endswith('qq.com'))).all()
Out[14]: [<User zhao>, <User sun>, <User li>]

limit(number)

查找number条数据

offset(number)

偏移number条数据

order_by(模型类.字段1 [.desc()], 模型类.字段2 [.desc()])

根据字段排序, 默认为升序asc, 降序为.desc()

# 实现查询: User表中从第二条数据开始, 查询3条数据, 并按ID降序排序
# 需要注意的是order_by需要早offset和limit前面, 否则会报错
In [22]: User.query.order_by(User.id.desc()).offset(1).limit(3).all()
Out[22]: [<User sun>, <User qian>, <User zhao>]

group_by(模型类.字段1, 模型类.字段2)

# 实现查询: User表根据Role分组, 汇总每个role下有多少条数据
# 导入func, 里面包含了分组汇总函数count等很多函数
In [26]: from sqlalchemy import func
# 1. 使用group时前面就不能使用User.query形式, 而是要使用db.session.query的形式, 括号中是要查询结果需要展示的字段
# 2. func类中有很多方法, count()参数为需要计算的字段
# 3. group_by()参数为根据什么字段来分组
In [29]: db.session.query(User.role_id, func.count(User.role_id)).group_by(User.role_id).all()
Out[29]: [(1, 2), (3, 2)]

免责声明:文章转载自《Flask-SQLAlchemy数据库ORM》仅用于学习参考。如对内容有疑问,请及时联系本站处理。

上篇对List中每个对象元素按时间顺序排序RocketMq 测试下篇

宿迁高防,2C2G15M,22元/月;香港BGP,2C5G5M,25元/月 雨云优惠码:MjYwNzM=

相关文章

mysql 数据库存储路径更改

使用了VPS一段时间之后发现磁盘空间快满了。本人的VPS在购买的时候买了500gb的磁盘,提供商赠送了20GB的高性能系统磁盘。这样系统就有两个磁盘空间了。在初次安装mysql 的时候将数据库目录安装在了系统盘。(第一个磁盘)使用了一段时间之后数据库存储量变大,快将20GB的存放空间占满了。因此必须将存放数据空间换地方了。嘿嘿下面是简单的操作了,不合理之处...

教你调用数据库读取短信 记事本 通讯录文件,让ios5的短信恢复到ios4

由于高版本的ios固件向下恢复到低版固件时无法通过itunes恢复备份,所以一些数据,比如SMS需要通过提取文件的方式单独进行备份恢复特别是ios5的短信,之前很是头痛,直接将文件恢复到指定目录修改权限是不行的,因为ios5对sms数据库进行了修改,与ios4不匹配,为了使短信恢复,就尝试打开数据,修改看看,结果证明可行我这里针对的是ios5的短信,当然如...

SQLServer2008/2012 安装、添加sa用户和密码、多实例安装、修改端口, 重启生效

目录: 1、SQLServer2008 安装2、SQL Server 添加 sa 用户和密码3、navicat12 连接 SQLServer4、不同 SQL Sever 实例的管理5、"开始 -- Microsoft SQL Server 2012"菜单下没有 sql server配置管理器6、修改 sql server 访问端口号 1、SQLServer...

mysql安装使用

  linux系统 mysql-5.7.14-linux.zip部署包支持在CentOS 6.x/7.x 服务器硬盘大小要求     a) /data/mysql_data  如果存在该独立分区,要求该分区 >10G b) 如果仅存在 /data 分区, 要求该分区 >10G c) 否则,要求根分区/ > 10G MySQL_INST...

性能测试指标

1. 测试环境指标折算: 测试环境平均并发数=(最大在线人数*10%)/n n是生产环境和测试环境服务器配置折算比,例如n=公倍数((生产web服务器数/测试web服务器数),(生产app服务器数/测试app服务器数))*(生产服务器内存/测试服务器内存),一般算下来n=4。 2. B/S架构。一般关注的web服务器性能指标 Avg Rps:平均每秒钟的响...

midway mysql egg-mysql 配置 基础操作 增删改查

egg-mysql:GITHUB传送门 一、配置安装egg-mysql npm install egg-mysql -S 在src/config/plugin.ts添加配置代码如下 export default { …… mysql: { enable: true, package: 'egg-mysql', } …… } as EggPlugin; 在s...