• 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)
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

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

    1.1 繁琐易错

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

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

    该行代码较长,无法在一行显示。在实际的软件开发中,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)
    
    • 1

    要插入的数据包含有 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)
    
    • 1
    • 2
    • 3

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

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

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

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

    select * from users where username = "admin"
    
    • 1

    只要数据库表 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
    
    • 1
    • 2
    • 3
    • 4
    • 5

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

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

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

    这个对象就相当于我们数据库表中的一条记录,一行数据,然后使用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
    
    • 1
    • 2
    • 3
    • 4

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

    配置 SQLAlchemy

    首先,引入相关库

    from flask import Flask
    from flask_sqlalchemy import SQLAlchemy
    
    • 1
    • 2

    接着对访问 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
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

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

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

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

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

    db = SQLAlchemy(app)
    
    • 1

    参数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)
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

    设定 __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)
    
    • 1
    • 2
    映射到数据库中

    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()
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9

    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.<过滤方法>.<查询方法>
    
    • 1

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

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

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

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

    type(Student.query)
    <class 'flask_sqlalchemy.BaseQuery
    
    • 1
    • 2

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

    type(students)
    <class 'flask_sqlalchemy.BaseQuery'>
    
    • 1
    • 2

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

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

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

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

    在这里插入图片描述

    上面说过,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()	#把第一条记录的信息输出
    
    • 1
    • 2
    • 3
    • 4
    • 5

    在这里插入图片描述

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

    常用的查询方法:

    在这里插入图片描述

    update

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

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

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

    或者:

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

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

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

    Delete

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

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

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

  • 相关阅读:
    Redis分布式锁这样用,有坑?
    K_A02_006 基于单片机驱动四位 数码管显示动态静态滚动显示
    整合SSM(Mybatis-Spring-SpringMVC层)
    jsoup框架技术文档--java爬虫--基本概念
    “阿里爸爸”又爆新作!Github新开源303页Spring全家桶高级笔记
    使用Grpc实现高性能PHP RPC服务
    搜索技术【二分搜索】 - 简介 & 原理
    react native Switch动态改变开关
    云存储应急演练体系建立及场景设计
    微服务之流控、容错组件sentinel
  • 原文地址:https://blog.csdn.net/weixin_42576837/article/details/126329174