Flask 的 ORM 模型 - 概述

将ORM模型之前,先了解以下背景,为什么会有ORM模型

1.问题的产生

访问关系数据库的传统方式是:拼接 SQL 语句。例如,向数据库中插入一条数据,根据要插入的数据拼接一条 SQL INSERT 语句,下面的 Python 程序使用 SQL 语句向数据库中插入一条学生的信息:

sno = '20201916'
name = '张三'
age = 20
gender = 'male'
sql = 'INSERT INTO students(sno, name, age, gender) VALUES("%s", "%s", %d, "%s")' % (sno, name, age, gender)
rows = cursor.execute(sql)

SQL语句是通过与变量的拼接获得的,但是随着项目越来越大,通过拼接 SQL 语句访问数据库存在如下的问题:

1.1 繁琐易错

在上面的例子中,insert语句中需要插入4个字段:

sql = 'INSERT INTO students(sno, name, age, gender) VALUES("%s", "%s", %d, "%s")' % (sno, name, age, gender)

该行代码较长,无法在一行显示。在实际的软件开发中,INSERT 语句可能需要插入 10 个以上的字段,那么拼接 INSERT 语句的代码则非常的繁琐易错。

下面的 SQL 语句来自于一个实际的项目:

sql = "INSERT INTO Flights(FlightID, AircraftModel, RegisterID, Direction, ExpectApronTime, RunwayID, ApronID, AirwayID, TaxiwayTimes, AirwayTimes, Rank) VALUES('%s', '%s', '%s', '%s', %d, '%s', '%s', '%s', '%s', '%s', %d)" % (flightID, aircraftModel, registerID, direction, expectApronTime, runwayID, apronID, airwayID, taxiwayTimes, airwayTimes, rank)

要插入的数据包含有 11 个字段,造成 SQL 语句非常的冗长,需要在多行中才能完全显示,程序的可读性极差。

2.2 SQL 语句重复利用率低

越复杂的 SQL 语句条件越多、代码越长,在实际的项目中,会出现很多很相近的 SQL 语句。

3.3 Web 安全漏洞

直接使用 SQL 语句存在有 Web 安全漏洞的问题:通过把 SQL 命令插入到页面请求的查询字符串,最终达到欺骗服务器执行恶意的 SQL 命令。

下面的 SQL 语句根据页面请求中的用户名和密码查询数据库:

username = 从页面请求中获取用户名
password = 从页面请求中获取密码
sql = 'select * from users where username = "%s" and password = "%s"' % (username, password)

在第 3 行的 SELECT 语句中,where 条件进行权限检查,只有 username 和 password 与数据库表 users 中的数据匹配时,才返回有效数据,因此,只有用户输入正确的用户名和密码才可以获取数据。

这条 SQL 语句存在有安全漏洞,假设用户在页面中输入的用户名为 admin"# (共 7 个字符,前 5 个字符是 admin,后面 2 个字符是 " 和 #),密码为 123456,则最终拼接的 SQL 语句如下:

select * from users where username = "admin"#" and password = "123456"

在 SQL 中,# 是行注释,因此上述 SQL 语句相当于:

select * from users where username = "admin"

只要数据库表 users 中有 admin 这条记录,执行该条 SQL 语句就会返回数据,这样对 password 的检查就彻底失效了。

2. 对象 - 关系映射 (ORM)

随着面向对象的软件开发方法发展,出现了对象 - 关系映射 (Object Relation Mapping) 模型,简称为 ORM,ORM 通过使用描述对象和数据库之间映射的元数据,将面向对象语言程序中的对象自动持久化到关系数据库中.

ORM:对象关系映射:

  • O(object)对象,很容易联想到面向对象,想到类,
  • R(relational)关系,联想到关系数据库
  • M(mapping)映射,那么是不是可以联想到,把对象映射到关系数据库。

在几乎所有的程序里面,都存在对象和关系数据库。在业务逻辑层和用户界面层中,我们是面向对象的。当对象信息发生变化的时候,我们需要把对象的信息保存在关系数据库中。

当你开发一个应用程序的时候(不使用O/R Mapping),你可能会写不少数据访问层的代码,用来从数据库保存,删除,读取对象信息(各种类似的sql语句),等等。你在DAL(数据访问层)中写了很多的方法来读取对象数据,改变状态对象等等任务。而这些代码写起来总是重复的。

ORM解决的主要问题是对象关系的映射。域模型和关系模型分别是建立在概念模型的基础上的。域模型是面向对象的,而关系模型是面向关系的。一般情况下,一个持久化类和一个表对应,类的每个实例对应表中的一条记录,类的每个属性对应表的每个字段。

具体来说,关系数据库中存在着一张表

  • 关系数据库中的表对应于面向对象中的类;
  • 关系数据库中的数据行(记录)对应于面向对象中的对象;
  • 关系数据库中的字段对应于面向对象中的属性。
    在这里插入图片描述
    比如说,在python中创建了一个类:
class User(object):
    def __init__(self,id,name,age):
        self.id = id
        self.name = name
        self.age  = age

这样的一个类,如果使用ORM模型映射到数据库的话,在数据库中就变成了一张表,这个表的名字叫User,它有三个字段,id,name,age(这是简单理解,实际代码也不是这样写的)
在这里插入图片描述

然后我们用这个类创建一个实例化对象:

user1 = User(1,'zs',20)

这个对象就相当于我们数据库表中的一条记录,一行数据,然后使用ORM模型提供的方法就可以把这个对象插入到数据库中。

在这里插入图片描述
同样,对于其他的操作,增删改查等等,这时候我们就不需要在拼接sql语句了,直接像操作类一样,使用一些属性和方法,就可以对数据进行操作了。

SQLAlchemy 简介

SQLAlchemy 是 Python 中一个通过 ORM 操作数据库的框架。SQLAlchemy 对象关系映射器提供了一种方法,用于将用户定义的 Python 类与数据库表相关联,并将这些类实例与其对应表中的行相关联。SQLAlchemy 可以让开发者使用类和对象的方式操作数据库,从而从繁琐的 sql 语句中解脱出来。

简单来说,你把python中的类,映射到数据库中的表,必须要借助一个工具,这个工具就是SQLALchemy。然后呢,在flask中,为了更好的适配flask,有人做了一个flask-sqlalchemy的工具包,本质上是对sqlalchemy的进一步封装,是基于sqlalchemy的,所以需要sqlalchemy的支持,使用起来和本来的sqlalchemy的orm基本一样。

使用 flask_sqlalchemy 完成映射
安装相关库
pip3 install flask
pip3 install pymysql
pip3 install SQLAlchemy
pip3 install flask-sqlalchemy

其实在安装flask-sqlalchemy的时候,会自动安装sqlalchemy,然后操作数据库,也需要pymysql的支持。

配置 SQLAlchemy

首先,引入相关库

from flask import Flask
from flask_sqlalchemy import SQLAlchemy

接着对访问 mysql 进行配置,如下所示:

app = Flask(__name__)
username = 'root'
password = 'root'
database = 'school'
hostname = 'localhost'
port = '3306'
uri = f'mysql+pymysql://{username}:{password}@{hostname}:{port}/{database}'
app.config['SQLALCHEMY_DATABASE_URI'] = uri

第一行就不说了,下面的代码则是因为我们需要连接数据库而必须提供的变量
mysql数据库的用户名密码,需要操作的数据库名称,主机的域名端口号
然后利用这几个变量拼接成连接数据库要使用的uri

这里的uri = 'mysql+pymysql://root:root@localhost:3306/school'

然后配置连接使用的uri: app.config['SQLALCHEMY_DATABASE_URI'] = uri

接着,创建 SQLAlchemy 对象,用于映射数据库表和对象。

db = SQLAlchemy(app)

参数app是通过app = Flask(__name__)得到的app,这是固定的写法。

创建数据库模型

用来映射到数据库表的python类通常被叫做数据库模型(model),一个数据库模型类对应数据库中的一个表。

所有的模型类都需要继承flask-sqlalchemy中提供的db.Model基类。

class Student(db.Model):
    __tablename__ = 'students'
    sno = db.Column(db.Integer,primary_key=True)
    name = db.Column(db.String(255))
    age = db.Column(db.Integer)

    def dump(self):
        print(self.sno,self.name,self.age)

设定 __tablename__ 为 students,表示将类 Student 映射到数据库中的表 students,就是说数据库中的表名是叫students。

然后,建立属性和字段的映射关系:

  • 映射 sno 到表 students 的字段 sno,类型为整数 (db.Integer),primary_key=True
    表示该字段是主键;
  • 映射 name 到表 students 的字段 name,类型为整数 (db.String);
  • 映射 age 到表 students 的字段 age,类型为整数 (db.Integer)。

这里的属性名称,sno,name,age就对应着数据库表中的字段名称,这个名称是一样的。
同时表的所有的字段(列)都是由db.Column类的实例对象表示的。

所以创建字段时,使用db.Column类实例化对象,然后传入参数:
常用的参数包括:

  • 字段类型:指明该字段的数据类型或者同时限定字段的长度。

常用的字段类型有:
在这里插入图片描述

  • 其他常用字段参数:

在这里插入图片描述
同时我们在类内实现了一个方法:
每一个类的实例对象调用这个方法就会输出自己的信息

def dump(self):
    print(self.sno,self.name,self.age)
映射到数据库中

python类的数据库模型已经创建好了,现在需要把这个模型映射到数据库中,创建一张确实存在的数据库表students

使用db.create_all()方法实现建表: db.create_all() 创建已经建立映射关系的表 students,表 students 已经被映射到类 Student。

注意:数据库不存在的话会报错,所以首先需要确认你的数据库时存在的。
执行之后就可以在数据库中看到students表:
在这里插入图片描述

数据库和表一旦创建后,之后对模型的改动不会自动作用到实际的表中。比如创建表之后,对模型类中添加或者删除字段,修改字段的名称和类型,这时候再次调用db.create_all() 也不hi更新表结构。想要使改动生效,最简单的方式是调用db.drop_all()方法删除数据库和表,然后再调用db.create_all() 重新创建。(根据我的测试,这两个方法不会对数据库产生影响,就是说删除或者新建的时候只是对数据表的影响,对数据库不会删除或者新建)

数据库操作

数据表创建完成后,需要对数据表进行数据的操作,增删改查等。

Insert

添加一条新纪录到数据表students中,使用模型类实例化一个对象,这个对象就是数据表中的一条记录,如下:

def insert_students():
    tom = Student(sno=1,name='tom',age=12)
    db.session.add(tom)
    db.session.commit()

    jerry = Student(sno = 2, name = 'jerry', age = 11)
    mike = Student(sno = 3, name = 'mike', age = 11)
    db.session.add_all([jerry, mike])
    db.session.commit()

tom = Student(sno=1,name='tom',age=12)使用模型类实例化对象同时传入属性值。
一个实例化对象就是表中的一条记录。

调用 db.session.add(tom) 将该实例加入到数据库连接会话中,调用 db.session.commit() 提交保存到数据库。

SQLAlchemy中使用数据库会话来管理数据库操作,数据库会话代表一个临时存储区,你对数据库的改动都会存放在这里。可以调用add()方法将新创建的对象添加到数据库会话中,或者对会话中的对象进行更新。只有当你对数据库会话对象调用commit()方法时,改动才会被提交到数据库。

db.session.add_all()一次添加包含所有记录对象的列表。

Read

如何从数据库中取回数据?

使用模型类提供的query属性附加调用各种过滤方法和查询方法可以完成。
一般来说,一个完整的查询遵循下面的模式:

<模型类>.query.<过滤方法>.<查询方法>

现在数据表中有以下记录:
在这里插入图片描述
我们有以下代码:

students = Student.query.filter_by(age=11)

调用<模型类>.query之后,将返回一个类型为<class 'flask_sqlalchemy.BaseQuery'>查询对象,这个查询对象可以继续调用过滤方法,每个过滤方法都会返回新的查询对象。所以过滤器可以叠加。

Student.query返回一个查询对象:

type(Student.query)
<class 'flask_sqlalchemy.BaseQuery

students = Student.query.filter_by(age=11)查询对象通过过滤方法筛选符合条件的记录,返回的是一个新的查询对象students:

type(students)
<class 'flask_sqlalchemy.BaseQuery'>

新的查询对象students:可以直接调用查询方法all(),查看全部查询结果:
查询结果是一个列表,有两条记录,每一条记录的类型是<class 'app.Student'>
列表内部有多个查询记录,也可以使用for...in...取出每一条记录

 print(students.all())
[<Student 2>, <Student 3>]
students.all()[0]则是返回列表中的第一条数据

或者调用查询方法first()返回第一条数据:

stu = students.first()  #返回第一条数据,调用dump输出字段值
stu.dump()

在这里插入图片描述

上面说过,students = Student.query.filter_by(age=11)返回的是一个新的查询对象,我们可以接着使用过滤器查询。age=11的记录有两条,现在进一步查询name=mike的记录。

 students = Student.query.filter_by(age=11)
 stu = students.filter_by(name='mike')
 print(stu.all())	#all()方法查询全部记录的列表,这里只有一条记录
 print(stu.first())	#first()返回查询的第一条记录
 stu.first().dump()	#把第一条记录的信息输出

在这里插入图片描述

常用的查询过滤器:
在这里插入图片描述

常用的查询方法:

在这里插入图片描述

update

更新一条记录很简单,直接赋值给模型类字段属性就可以改变字段值,然后调用commit()方法提交会话。

 students = Student.query.filter_by(name = 'tom')
 students.first().name='TIM'
 db.session.commit()

使用过滤方法,返回查询对象 students,接着使用查询方法,返回指定的那条记录 students.first(),然后对它的name字段修改,接着commit()
在这里插入图片描述

或者:

直接使用查询方法,将会直接返回对应的记录,然后对字段值修改:

 stu = Student.query.get(1)#返回主键为1的那条记录
 stu.name = 'OTM'
 db.session.commit()

在这里插入图片描述
注意这两种方式的区别,使用过滤方法将返回新的查询对象,必须接着使用查询方法才能获取记录,但是直接使用查询方法就可以获取记录。

Delete

删除某一条记录,有两种方式可以删除:

  1. 通过过滤器获得查询对象,直接调用.delete()方法:
 students = Student.query.filter_by(age = 11)
 students.delete()#查询对象直接调用delete()方法
 db.session.commit()
  1. 获得查询记录,使用db.session.delete(记录):
students = Student.query.filter_by(age = 11)
db.session.delete(students.first())
db.session.commit()

这里的查询对象students中包含了两条记录,但是不能直接 db.session.delete(students.all())

  • 4
    点赞
  • 18
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值