SQLAlchemy是python中最著名的ORM(Object Relationship Mapping)框架了。
前言:什么是ORM?
ORM操作是所有完整软件中后端处理最重要的一部分,主要完成了后端程序和数据库之间的数据同步和持久化的操作。
数据库表示一个二维表,包含多行多列。把一个表的内容用python的数据结构表示出来的话,可以用一个list表示多行,list的每一个元素是tuple,表示一行记录,比如,包含id和name的user表:
[图片上传失败...(image-82751b-1586172584834)]
Python的DB-API返回的数据结构就是像上面这样表示的。但是用tuple表示一行很难看出表的结构,如果把一个tuple用class实例来表示,就可以更容易的看出表的结构来:
[图片上传失败...(image-3b0876-1586172584834)]
这就是传说中的ORM技术:Object-Relational Mapping ,把关系数据库的表结构映射到对象上。但是由谁来做这个转化呢?所以ORM框架应运而生。目前来说也是描述程序中对象和数据库中数据记录之间的映射关系的统称,是一种进行程序和数据库之间数据持久化的一种编程思想。
ORM思想的核心是隐藏了数据访问细节,提供了通用的数据库交互,并且完全不用考虑SQL语句,从而快速开发。
一句话解释ORM就是:一种可以把model中的模型和数据库中的一条数据相互转换的工具。
举个例子:
[图片上传失败...(image-3804b0-1586172584834)]
[图片上传失败...(image-5837f6-1586172584834)]
在Python中,最有名的ORM框架是SQLAlchemy。我们来看看其语法。
一,初始化数据库连接
SQLAlchemy本身无法操作数据库,其必须通过pymysql等第三方插件。上图中Dialect用于和数据API进行交流,根据配置文件的不同调用不同的数据库API,从而实现对数据库的操作。
使用sqlalchemy进行数据库操作,首先我们需要建立一个指定数据库的链接引擎对象,而建立引擎对象的方式被封装在了sqlalchemy.create_engine函数中,通过指定的数据库连接信息即可创建。
create_engine()用来初始化数据库连接。SQLAlchemy用一个字符串表示连接信息:
'数据库类型+数据库驱动名称://用户名:口令@机器地址:端口号/数据库名'
你只需要根据需要替换掉用户名,口令等信息即可。举个例子:
初始化数据库链接
engine = create_engine('mysql+mysqlconnector://root:123456@localhost:3306/test')
我使用了mysql数据库,数据库连接框架用的是mysqlconnector,用户名为root,密码是123456,端口号是localhost(127.0.0.1),端口号是3306(mysql服务器默认端口号),test是数据库的名字。
二,创建user模型及其数据库建表操作
2.1,安装SQLAlchemy
如果没有安装SQLAlchemy请先安装,这个不是python自带的。
pip install sqlalchemy
2.2,数据库建表
在test数据库中,创建user表,SQL如下:
create table user (id varchar(20) primary key, name varchar(20))
创建表,表名为user,表中有两个字段。一个是id,varchar类型,最多支持20个字符,设置为主键,另一个是name,varchar类型,最多支持20个字符。
(PS:主键时唯一的,当你重复插入时会报错,并终止插入操作)。
插入数据,如下:
[图片上传失败...(image-2c6faa-1586172584834)]
更改表名
alter table origin_name rename to new_name;
2.3,创建user模型
在model.py中创建 一个User类,用来和User表中的字段进行关联。
[图片上传失败...(image-72132b-1586172584834)]
这段代码的意思,User类的字段名和表中完全一致,而且字段的属性也一样,以id字段为例,表中id的属性是Varchar(20),primary key,模型中String(20),primary_key=True。我们可以清洗的看到varchar在程序中对应String,primary_key对应程序中的primary_key属性,而且是一个bool类型。
(PS:这里的String是sqlAlchemy中的一个类,这样类似的类我们还会用到Column)
2.4,Column类的学习
构建函数为
Column.init(self, name, type_, *args, **kwargs)
name 列名
type_ 类型 ,更多类型SQLAlchemy.types
下面是*args参数定义
Constraint(约束)
ForeignKey(外键)
ColumnDefault(默认)
Sequenceobjects(序列)定义
下面是**kwargs参数定义
primary_key 如果为True,则是主键
nullable 是否可谓Null,默认是True
default 默认值,默认是None
index 是否是索引,默认是True
unique 是否唯一键,默认是False
onupdate 指定一个更新时候的值,这个操作是定义在SQLAlchemy中,不是在数据库里的,当更新一条数据时设置,大部分用于update Time这类字段
autoincrement 设置为整型自动增长,只有没有默认值,并且是Integer类型,默认是True
quote 如果列明是关键字,则强制转义,默认False
三,CRUD(Create Read Update Delete,增查改删)
在SQLAlchemy中,增删改查操作是通过一个session对象(DBSession是由sessionmaker创建的)来完成的。
3.1,初始化DBSession,连接会话
我们的程序中的对象要使用sqlalchemy的管理,实现对象的ORM操作,就需要按照框架指定的方式进行类型的创建操作,SQLAlchemy封装了基础类的声明操作和字段属性的定义限制方式,开发人员要做的事情就是引入需要的模块并在创建对象的时候使用他们即可。
基础类封装在sqlalchemy.ext.declarative_base模块中,字段属性的定义封装在SQLAlchemy模块中,通过sqlalchemy.Column定义属性,通过封装的Integer,String,Float等定义属性的限制。
首先我们需要创建DBSession,在此基础上就可以进行增删改查操作了。
[图片上传失败...(image-132536-1586172584834)]
上面的代码完成SQLAlchemy的初始化和具体每个表的class定义。如果有多个表,就继续定义其他class。
注意:如果在创建会话的时候还没有指定数据库引擎,可以通过如下的方式完成会话:
3.2,添加操作
程序中存在一个对象Object数据,通过ORM核心模块进行增加的函数定义将对象保存在数据库的操作过程,就是增加操作。比如注册操作中,通过用户输入的账号密码等信息创建了一个独立的对象,通过add()函数将对象增加保存到数据库中,数据库中就存在用户这个对象数据了。
下面,我们看看如何向数据库表中添加一行记录。
由于有ORM,我们向数据库表中添加一行记录,可以视为添加一个User对象:
[图片上传失败...(image-919e2c-1586172584834)]
可见,关键是获取session,然后把对象添加到session,最后提交并关闭。DBSession对象可视为当前数据库连接。
3.3,查询操作
查询时通过Session的query()方法创建一个查询对象,这个函数的参数可以是任何类或者类的描述的集合。查询出来的数据是一个对象,直接通过对象的属性调用。
如何从数据库表中查询数据呢?有了ORM,查询出来的可以不再是tuple,而是User对象。SQLAlchemy提供的查询接口如下:
可见,ORM就是把数据库表的行与相应的对象建立关联,互相转换。
由于关系数据库的多个表还可以用外键实现一对多,多对多等关联,相应的,ORM框架也可以提供两个对象之间的一对多,多对多等功能。
例如,如果一个User拥有多个Book,就可以定义一对多关系如下:
当我们查询一个User对象时,该对象的books属性将返回一个包含若干个Book对象的list .
3.4,更新操作
程序中存在的一个对象Object数据,有自己的id编号(可以使程序中自行赋值定义,更多的操作是从数据库中查询出来存在的一个对象),通过ORM核心模块进行修改函数的定义将对象改变的数据更新到数据库中已经存在的记录中的过程,就是更新操作。比如用户更改登录密码操作时,根据程序中查询得到的一个用户{id编号,账号,密码},在程序中通过改变其密码属性数据,然后通过update()函数将改变的数据更新保存到数据库中,数据库原来的数据就发生了新的改变。
更新操作要多一步,就是要先根据筛选条件拿到要更改的对象,然后给对象赋值,再次提交(commit)即可。
3.5,删除操作
程序中存在的一个对象或者已知的id编号,通过主键编号或者对象的任意属性进行数据库中数据记录的删除的操作过程称为删除操作。如管理员删除某个会员账号的操作,通过获取要删除会员的账号,然后通过delete()函数将要删除的会员信息告知数据库执行删除操作,数据库中的某条存在的数据记录就被删除掉了。
3.6,代码整合
四,查询对象Query
Session是sqlalchemy和数据库交互的桥梁,Session提供了一个Query对象实现数据库中数据的查询操作。
4.1,常规查询query
直接指定类型进行查询
4.2,指定排序查询
通过类型的属性指定排序方式
4.5,指定类型别名
对于类型名称较长的情况,同样可以指定别名进行处理
4.6,切片查询
对于经常用于分页操作单额切片查询,在使用过程中直接使用python内置的切片即可。
1user_list = session.query(User).all()[1:3]
五,条件筛选filter
上面主要对数据查询对象query有一个比较直观的感受和操作,在实际使用过程中经常用到条件查询,主要通过filter 和 filter_by 进行操作,下面重点学习最为频繁的filter条件筛选函数。
5.1,等值条件——equals / not equals
5.2,模糊条件——like
1session.query(User).filter(User.name.like(‘%james%’))
5.3,范围条件——in / not in
5.4,空值条件——is null / is not null
5.5,并且条件——and
5.6,或者条件——or
5.7,SQL语句查询
某些特殊的情况下,我们也可能在自己的程序中直接使用sql语句进行操作。
5.8,filter() 和 filter_by()的区别
首先看一个例子:
结果如下:
1001 James
1001 James
所以,从例子可以看出,filter可以像写SQL的where条件那样写 < , > 等条件,但引用列名时,需要通过类名,属性名的方式。 filter_by可以使用python的正常参数传递条件,指定列名时,不需要额外指定类名,参数名对应类中的属性名,不能用 < , > 等条件。
filter不支持组合查询,只能连续调用filter变相实现,filter_by的参数是 **kwargs,直接支持组合查询。
六,查询结果
6.1,all()函数返回查询列表
返回一个列表,可以通过遍历列表获取每个对象。
session.query(User).all()
6.2,filter()函数返回单项数据的列表生成器
session.query(User).filter(..)
6.3,one() / one_or_none() / scalar() 返回单独的一个数据对象
one()返回且仅返回一个查询结果,当结果数量不足或者多余一个时会报错。
session.query(User).filter(..).one()/one_or_none()/scalar()
6.4,first() 返回一个单项结果
返回至多一个结果,而且以单项形式,而不是只有一个元素的tuple形式返回。
session.query(User).filter(User.id > 1001).first()
七,遇到的问题及其解决方法
7.1 sqlalchemy查询中,如果没有符合条件的结果,会返回一个空的对象,如何判断这个对象是空的?
解决问题的代码:
八,扩展知识
8.1 Python双下划线开头的函数和变量
python 用下划线作为变量前缀和后缀指定特殊变量。
__XXX 不能用 ‘ from module import * ’ 导入
XXX 系统定义名称
__XXX 类中的私有变量名
核心风格:避免用下划线作为变量名的开始。
因为下划线对解释器有特殊的意义,而且是内建标识符所使用的符号,我们建议程序员避免使用下划线作为变量名的开始。一般来说,变量名__XXX 被看做是“私有”的,在模块或类外不可以使用。当变量是私有的时候,用XXX来表示变量是很好地习惯。因为变量名XXX对python来说有特殊含义,对于普通的变量应当避免这种命名风格。
“单下划线”开始的成员变量叫做保护变量,意思是只有类对象和子类对象自己能访问到这些变量;
“双下划线”开始的是私有成员,意思是只有类对象自己能访问,连子类对象也不能访问到这个数据。
以单下划线开头(__foo)的代表不能直接访问的类属性,需通过类提供的接口进行访问,不能用“from XXX import * ”而导入;
以双下划线开头的(__foo)代表类的私有成员;
以双下划线开头和结尾的(foo)代表python里特殊方法专用的标识,如init() 代表类的构造函数。
8.2 if name == ''main''
所有的python模块都是对象并且有几个有用的属性,你可以使用这些属性方便的测试你所书写的模块。
模块是对象,并且所有的模块都有一个内置属性name。一个模块的name的值要你您如何应用模块。如果import模块,那么name的值通常为模块的文件名,不带路径或者文件扩展名。但是我们也可以像一个标准的程序一样直接运行模块,在这种情况下name的值将会是一个特别的缺省值:main。