ORM操作在实际项目中的应用非常多,涉及到的框架也是根据不同的项目有不同的处理模块,不过操作流程和步骤都是大同小异基本没有什么太大变化,唯一需要注意的就是在实际操作过程中你要使用的ORM框架的处理性能和是否支持事务、是否支持分布式等特性来进行确定使用哪个ORM框架进行操作,一般在python程序中ORM操作都是对mysqldb和pymysql这样的底层模块进行的封装处理。例如文章中要讲解的sqlalchemy就是底层封装mysqldb的实现,不过我们的在使用过程中需要使用pymysql进行替代。
1、安装
$ pip install sqlalchemy
安装完成之后,可以通过引入sqlalchemy进行版本查看,确认sqlalchemy是否安装成功
2、连接引擎
使用sqlalchemy进行数据库操作,首先我们需要建立一个指定数据库的连接引擎对象。建立引擎对象的方式被封装在了sqlalchemy.create_engine函数中,通过指定的数据库连接信息就可以进行创建。
创建数据库连接引擎时参数设置语法:
# 引入建立引擎的模块
from sqlalchemy import create_engine
# pymsql创建引擎连接
# mysql+pymysql://<username>:<password>@<host>/<dbname>[?<options>]
engine = create_engine("mysql+pymysql://root:123456@192.168.3.109:3306/test", encoding="utf-8", echo=True)
指定的数据库连接字符串表示了目标数据库的配置信息;encoding配置参数指定了和和数据库之间交换的数据的编码方式,同时echo参数表示随时在控制台展示和数据库之间交互的各种信息。
create_engine()函数返回的是sqlalchemy最核心的接口之一,该引擎对象会根据开发人员指定的数据库进行对应的sql api的调用处理。
3、连接会话
创建了数据库连接引擎对象之后,我们需要获取和指定数据库之间的连接,通过连接进行数据库中数据的增删改查操作,和数据库的连接我们称之为和指定数据库之间的会话。
我们使用 sessionmaker 方法创建session。
# 引入创建session连接会话的模块
from sqlalchemy.orm import sessionmaker
# 创建一个连接会话对象;需要指定是和哪个数据库引擎之间的会话
Session = sessionmaker(bind=engine)
session = Session()
# 如果在创建会话的时候没有指定数据库引擎,可以通过如下的方式完成引擎绑定操作
# Session = sessionmaker()
# Session.configure(bind=engine)
# session = Session()
此时的session不是线程安全的,并且我们一般session对象都是全局的,那么在多线程情况下,当多个线程共享一个session时,数据处理就会发生错误。为了保证线程安全,需使用scoped_session方法。
# 引入创建session连接会话的模块
from sqlalchemy.orm import sessionmaker, scoped_session
# 创建一个连接会话对象;需要指定是和哪个数据库引擎之间的会话
Session = scoped_session(sessionmaker(bind=engine))
session = Session()
4、ORM之Object操作
我们的程序中的对象要使用sqlalchemy的管理,实现对象的orm操作,就需要按照框架指定的方式进行类型的创建操作,sqlalchemy封装了基础类的声明操作和字段属性的定义限制方式,开发人员要做的事情就是引入需要的模块并在创建对象的时候使用它们即可
基础类封装在sqlalchemy.ext.declarative.declarative_base模块中
字段属性的定义封装在sqlalchemy模块中,通过sqlalchemy.Column定义属性,通过封装的Integer、String、Float等定义属性的限制
常用的SQLAlchemy字段类型
类型名 | python中类型 | 说明 |
Integer | int | 普通整数,一般是32位 |
SmallInteger | int | 取值范围小的整数,一般是16位 |
BigInteger | int或long | 不限制精度的整数 |
Float | float | 浮点数 |
Numeric | decimal.Decimal | 普通整数,一般是32位 |
String | str | 变长字符串 |
Text | str | 变长字符串,对较长或不限长度的字符串做了优化 |
Unicode | unicode | 变长Unicode字符串 |
UnicodeText | unicode | 变长Unicode字符串,对较长或不限长度的字符串做了优化 |
Boolean | bool | 布尔值 |
Date | datetime.date | 时间 |
Time | datetime.datetime | 日期和时间 |
LargeBinary | str | 二进制文件 |
常用的SQLAlchemy列选项
选项名 | 说明 |
primary_key | 如果为True,代表表的主键 |
unique | 如果为True,代表这列不允许出现重复的值 |
index | 如果为True,为这列创建索引,提高查询效率 |
nullable | 如果为True,允许有空值,如果为False,不允许有空值 |
default | 为这列定义默认值 |
常用的SQLAlchemy关系选项
选项名 | 说明 |
backref | 在关系的另一模型中添加反向引用 |
primary join | 明确指定两个模型之间使用的联结条件 |
uselist | 如果为False,不使用列表,而使用标量值 |
order_by | 指定关系中记录的排序方式 |
secondary | 指定多对多中记录的排序方式 |
secondary join | 在SQLAlchemy中无法自行决定时,指定多对多关系中的二级联结条件 |
4.1 创建基础类
# 引入需要的模块
from sqlalchemy.ext.declarative import declarative_base
# 创建基础类
BaseModel = declarative_base()
4.2 创建模型类
定义模型类必须继承自之前创建的基础类BaseModel,同时通过指定__tablename__确定和数据库中某个数据表之间的关联关系,指定某列类型为primary_key设定的主键,其他就是通过Column指定的自定义属性了。
sqlalchemy会根据指定的__tablename__和对应的Column列字段构建自己的accessors访问器对象,这个过程可以称为instrumentation,经过instrumentation映射的类型就可以进行数据库中数据的操作了。
# 引入需要的模块
from sqlalchemy import Column, String, Integer, ForeignKey
# 创建模型类
class User(BaseModel):
# 定义表名
__tabelname__ = "tbl_user"
# 创建字段类型
id = Column(Integer, primary_key=True)
name = Column(String(50))
age = Column(Integer)
class Author(BaseModel):
__tablename__ = "tbl_author"
id = Column(Integer, primary_key=True)
name = Column(String(50))
class Book(BaseModel):
__tablename__ = "tbl_book"
id = Column(Integer, primary_key=True)
book_name = Column(String(50))
# ForeignKey 定义外键,需要导入
author_id = Column(Integer, ForeignKey("tbl_author.id"))
4.3 模型类映射操作
完成了模型类的定义之后,Declarative会通过python的metaclass对模型类进行操作,根据定义的模型类创建table对象,构建程序中类型和数据库table对象之间的映射mapping关系。
通过类型对象的metadata可以实现和数据库之间的交互,有需要时可以通过metadata发起create table操作,通过Base.metadata.create_all()进行操作,该操作会检查目标数据库中是否有需要创建的表,不存在的情况下创建对应的表。
# 删除所有模型类创建的表格(慎用)
BaseModel.metadata.drop_all(engine)
# 创建所有模型类的表格,有则不创建
BaseModel.metadata.create_all(engine)
5、数据的增、查、改、删
5.1 增
在程序代码中根据定义的数据类型创建对象的方式比较简单,创建对象并通过会话对象将数据对象持久化到数据库中。
# 增
user1 = User(name="张三", age=18)
user2 = User(name="李四", age=19)
user3 = User(name="王五", age=20)
# 将单个对象加入到会话对象中
session.add(user1)
# 将多个对象加入到会话对象中
session.add_all([user2, user3])
# 提交数据
session.commit()
5.2 查
Session是sqlalchemy和数据库交互的桥梁,Session提供了一个Query对象实现数据库中数据的查询操作
5.2.1 基础查询
查询所有数据
# 返回User对象所有字段的所有信息,返回结果为对象列表
session.query(User)
排序查询
# 指定排序查询
session.query(User).order_by(User.id) # 升序查询
session.query(User).order_by(-User.id) # 降序查询
session.query(User).order_by(-User.id, User.name) # 多条件排序查询
查询指定列
# 返回User对象字段为id和name的所有信息,返回结果为元组列表
session.query(User.id, User.name)
为字段取别名
# 为字段取别名
session.query(User.id, User.name.label("n"))
# 对应SQL语句:SELECT tbl_user.id AS tbl_user_id, tbl_user.name AS n FROM tbl_user
分页查询
# 使用limit()+offset()切片分页
# limit()限制返回条数
# offset()偏移
ret = session.query(User).limit(3).offset(2).all() # 返回第2条以后的3条数据,即第3至第5条数据
print(ret)
# 使用slice()切片分页
ret = session.query(User).slice(2, 5).all() # 返回第3至第5条数据
print(ret)
# 使用python列表切片分页
ret = session.query(User).all()[2:5] # 返回第3至第5条数据
print(ret)
分组查询
# 分组查询
session.query(User.name).group_by(User.name)
聚合函数
# 查询常用聚合函数
from sqlalchemy.sql import func
session.query(func.max(User.age), # 最大
func.min(User.age), # 最小
func.sum(User.age), # 求和
func.avg(User.age), # 平均
func.count(User.age) # 统计
)
去重
# 去重查询
from sqlalchemy import distinct
session.query(distinct(User.name))
5.2.2 条件筛选
在实际使用过程中经常用到条件查询,主要通过filter和filter_by进行操作,重点讲解使用最为频繁的filter条件筛选函数
等值条件_equals / not equals
# 等于
session.query(User).filter(User.name == "李四")
# 不等于
session.query(User).filter(User.name != "李四")
模糊条件_like/notlike
# 用户名包含“张”
session.query(User).filter(User.name.like("%张%"))
# 用户名不包含“张”
session.query(User).filter(User.name.notlike('%张%'))
范围条件_in / not in
# in
session.query(User).filter(User.id.in_([1, 2, 4]))
# not in
session.query(User).filter(~User.id.in_([1, 2, 4]))
空值条件
# 空值
session.query(User).filter(User.name == None)
session.query(User).filter(User.name.is_(None))
# 非空
session.query(User).filter(User.name != None)
session.query(User).filter(User.name.isnot(None))
并且条件
from sqlalchemy import and_
session.query(User).filter(User.name == '张三').filter(User.age == 18)
session.query(User).filter(User.name == '张三', User.age == 18)
session.query(User).filter(and_(User.name == '张三', User.age == 18))
或者条件
from sqlalchemy import or_
session.query(User).filter(or_(User.name == '张三', User.name == '李四'))
5.2.3 多表查询
# 多表查询
session.query(Author.name, Book.book_name).filter(Author.id == Book.author_id)
# 对应SQL:SELECT tbl_author.name, tbl_book.book_name FROM tbl_author, tbl_book WHERE tbl_author.id = tbl_book.author_id
# 内联查询join
session.query(Author.name, Book.book_name).join(Author, Author.id == Book.author_id)
# 对应SQL:SELECT tbl_author.name, tbl_book.book_name FROM tbl_book INNER JOIN tbl_author ON tbl_author.id = tbl_book.author_id
# 左联查询outerjoin。没有右联查询
session.query(Author.name, Book.book_name).outerjoin(Author, Author.id == Book.author_id)
# 对应SQL:SELECT tbl_author.name, tbl_book.book_name FROM tbl_book LEFT OUTER JOIN tbl_author ON tbl_author.id = tbl_book.author_id
# 并集查询
author_name = session.query(Author.name)
book_name = session.query(Book.book_name)
author_name.union(book_name)
# 对应SQL:SELECT anon_1.c_name FROM
# (SELECT tbl_author.name as c_name FROM tbl_author UNION SELECT tbl_book.book_name as c_name FROM tbl_book) AS anon_1
5.2.4 获取查询结果
获取查询对象有这么几种方法one()
,all()
,first(),one(),one_or_none()
,scalar(),以及get()等。
下面对这几个方法的用法及效果做简单解释。
all()
返回查询到的所有的结果。
这个方法比较危险的地方是如果数据量大且没有使用limit
子句限制的话,所有的结果都会加载到内存中。
它返回的是一个列表,如果查询不到任何结果,返回的是空列表。
ret = session.query(User).all()
print(ret) # [<__main__.User object at 0x0000000003894F60>, <__main__.User object at 0x00000000038942E8>]
first()
返回查询到的第一个结果,如果没有查询到结果,返回None
。
ret = session.query(User).first()
print(ret) # <__main__.User object at 0x0000000003894358>
one()
如果只能查询到一个结果,返回它,否则抛出异常。
没有结果时抛sqlalchemy.orm.exc.NoResultFound
,有超过一个结果时抛sqlalchemy.orm.exc.MultipleResultsFound
。
ret = session.query(User).filter(User.id == 2).one()
print(ret) # <__main__.User object at 0x00000000038954A8>
one_or_none()
比起one()
来,区别只是查询不到任何结果时不再抛出异常而是返回None
。
ret = session.query(User).filter(User.id == 2).one_or_none()
print(ret) # <__main__.User object at 0x00000000038954A8>
scalar()
这个方法与.one_or_none()
的效果一样。
如果查询到很多结果,抛出sqlalchemy.orm.exc.MultipleResultsFound
异常。
如果只有一个结果,返回它,没有结果返回None
。
ret = session.query(User).filter(User.id == 2).scalar()
print(ret) # <__main__.User object at 0x00000000038954A8>
get()
这是个比较特殊的方法。它用于根据主键来返回查询结果,因此它有个参数就是要查询的对象的主键。
如果没有该主键的结果返回None
,否则返回这个结果。
ret = session.query(User).get(2)
print(ret) # <__main__.User object at 0x00000000038954A8>
当获取的结果为一个对象时,可以通过 对象.模型类属性 的方式取出值
user = session.query(User).get(2)
print(user) # <__main__.User object at 0x00000000038954A8>
print(user.name) # 李四
5.3 改
# update()更新数据
session.query(User).filter(User.id == 2).update({"name": "李四改"})
session.query(User).filter(User.id >= 4).update({"age": 88})
session.commit()
5.4 删
# delete()删除数据
session.query(User).filter(User.id == 2).delete()
session.query(User).filter(User.id >= 4).delete()
session.commit()
6、执行原生SQL语句
使用原生sql语句需要引入text方法,传入要执行的sql语句,若需要给sql语句传参,需要使用 :param 的方式定义参数名,然后使用 params() 并以键值对的方式传入值
from sqlalchemy import text
session.query(User).from_statement(text('select * from tbl_user where name=:name and age=:age')).params(name='李四', age=19)
session.query(User).from_statement(text('update tbl_user set name=:name where id=:id')).params(name='赵六', id=1)
session.commit()
还有一种类似于pymysql使用 execute() 执行原生sql的方法
ret = session.execute('select * from tbl_user where name=:name', {"name": "赵六"}).fetchall() # 所有数据以元组的形式保存在列表中
print(ret) # [(1, '赵六', 18, 2), (4, '赵六', 18, 2)]
ret = session.execute('select * from tbl_user where name=:name', {"name": "赵六"}).fetchone() # 返回第一条数据,无则为None
print(ret) # (1, '赵六', 18, 2)
7、附码
# 引入建立引擎的模块
from sqlalchemy import create_engine
# 引入创建session连接会话的模块
from sqlalchemy.orm import sessionmaker, scoped_session
# 模型类需要使用的模块
from sqlalchemy.ext.declarative import declarative_base
from sqlalchemy import Column, String, Integer, ForeignKey
# 数据操作的模块
from sqlalchemy import distinct, and_, or_
# 执行原生SQL的模块
from sqlalchemy import text
# 1、pymsql创建引擎连接
# mysql+pymysql://<username>:<password>@<host>/<dbname>[?<options>]
engine = create_engine("mysql+pymysql://root:123456@192.168.3.109:3306/test", encoding="utf-8", echo=True)
# 2、创建一个连接会话对象;需要指定是和哪个数据库引擎之间的会话
Session = sessionmaker(bind=engine)
session = Session()
# 如果在创建会话的时候没有指定数据库引擎,可以通过如下的方式完成引擎绑定操作
# Session = sessionmaker()
# Session.configure(bind=engine)
# session = Session()
# 安全session
# Session = scoped_session(sessionmaker(bind=engine))
# session = Session()
# 3、模型类
# 创建基础类
BaseModel = declarative_base()
# 创建模型类
class User(BaseModel):
# 定义表名
__tabelname__ = "tbl_user"
# 创建字段类型
id = Column(Integer, primary_key=True)
name = Column(String(50))
age = Column(Integer)
class Author(BaseModel):
__tablename__ = "tbl_author"
id = Column(Integer, primary_key=True)
name = Column(String(50))
class Book(BaseModel):
__tablename__ = "tbl_book"
id = Column(Integer, primary_key=True)
book_name = Column(String(50))
# ForeignKey 定义外键,需要导入
author_id = Column(Integer, ForeignKey("tbl_author.id"))
# 删除所有模型类创建的表格(慎用)
# BaseModel.metadata.drop_all(engine)
# 创建所有模型类的表格,有则不创建
BaseModel.metadata.create_all(engine)
# 4、数据操作
# 增
user1 = User(name="张三", age=18)
user2 = User(name="李四", age=19)
user3 = User(name="王五", age=20)
# 将单个对象加入到会话对象中
session.add(user1)
# 将多个对象加入到会话对象中
session.add_all([user2, user3])
# 提交数据
session.commit()
# 返回User对象所有字段的所有信息,返回结果为对象列表
session.query(User)
# 指定排序查询
session.query(User).order_by(User.id) # 升序查询
session.query(User).order_by(-User.id) # 降序查询
session.query(User).order_by(-User.id, User.name) # 多条件排序查询
# 返回User对象字段为id和name的所有信息,返回结果为元组列表
session.query(User.id, User.name)
# 为字段取别名
session.query(User.id, User.name.label("n"))
# 对应SQL语句:SELECT tbl_user.id AS tbl_user_id, tbl_user.name AS n FROM tbl_user
# 分组查询
session.query(User.name).group_by(User.name)
# 查询常用聚合函数
from sqlalchemy.sql import func
session.query(func.max(User.age), # 最大
func.min(User.age), # 最小
func.sum(User.age), # 求和
func.avg(User.age), # 平均
func.count(User.age) # 统计
)
# 去重查询
session.query(distinct(User.name))
# 使用limit()+offset()切片分页
# limit()限制返回条数
# offset()偏移
ret = session.query(User).limit(3).offset(2).all() # 返回第2条以后的3条数据,即第3至第5条数据
print(ret)
# 使用slice()切片分页
ret = session.query(User).slice(2, 5).all() # 返回第3至第5条数据
print(ret)
# 使用python列表切片分页
ret = session.query(User).all()[2:5] # 返回第3至第5条数据
print(ret)
# 等于
session.query(User).filter(User.name == "李四")
# 不等于
session.query(User).filter(User.name != "李四")
# 用户名包含“张”
session.query(User).filter(User.name.like("%张%"))
# 用户名不包含“张”
session.query(User).filter(User.name.notlike('%张%'))
# in
session.query(User).filter(User.id.in_([1, 2, 4]))
# not in
session.query(User).filter(~User.id.in_([1, 2, 4]))
# 空值
session.query(User).filter(User.name == None)
session.query(User).filter(User.name.is_(None))
# 非空
session.query(User).filter(User.name != None)
session.query(User).filter(User.name.isnot(None))
# 并且(多条件查询)
session.query(User).filter(User.name == '张三').filter(User.age == 18)
session.query(User).filter(User.name == '张三', User.age == 18)
session.query(User).filter(and_(User.name == '张三', User.age == 18))
# 或者
session.query(User).filter(or_(User.name == '张三', User.name == '李四'))
# 多表查询
session.query(Author.name, Book.book_name).filter(Author.id == Book.author_id)
# 对应SQL:SELECT tbl_author.name, tbl_book.book_name FROM tbl_author, tbl_book WHERE tbl_author.id = tbl_book.author_id
# 内联查询join
session.query(Author.name, Book.book_name).join(Author, Author.id == Book.author_id)
# 对应SQL:SELECT tbl_author.name, tbl_book.book_name FROM tbl_book INNER JOIN tbl_author ON tbl_author.id = tbl_book.author_id
# 左联查询outerjoin。没有右联查询
session.query(Author.name, Book.book_name).outerjoin(Author, Author.id == Book.author_id)
# 对应SQL:SELECT tbl_author.name, tbl_book.book_name FROM tbl_book LEFT OUTER JOIN tbl_author ON tbl_author.id = tbl_book.author_id
# 并集查询
author_name = session.query(Author.name)
book_name = session.query(Book.book_name)
author_name.union(book_name)
# 对应SQL:SELECT anon_1.c_name FROM
# (SELECT tbl_author.name as c_name FROM tbl_author UNION SELECT tbl_book.book_name as c_name FROM tbl_book) AS anon_1
# all()返回所有的数据
session.query(User).all()
# first()返回查询到的第一个结果,如果没有查询到结果,返回None。
session.query(User).first()
# one()如果只能查询到一个结果,返回它,否则抛出异常。
session.query(User).filter(User.id == 2).one()
# one_or_none()比起one()来,区别只是查询不到任何结果时不再抛出异常而是返回None。
session.query(User).filter(User.id == 2).one_or_none()
# scalar()这个方法与.one_or_none()的效果一样。
session.query(User).filter(User.id == 2).scalar()
# get()这是个比较特殊的方法。它用于根据主键来返回查询结果,因此它有个参数就是要查询的对象的主键。如果没有该主键的结果返回None,否则返回这个结果。
session.query(User).get(2)
# update()更新数据
session.query(User).filter(User.id == 2).update({"name": "李四改"})
session.query(User).filter(User.id >= 4).update({"age": 88})
session.commit()
# delete()删除数据
session.query(User).filter(User.id == 2).delete()
session.query(User).filter(User.id >= 4).delete()
session.commit()
# 执行原生SQL
session.query(User).from_statement(text('select * from tbl_user where name=:name and age=:age')).params(name='李四', age=19)
session.query(User).from_statement(text('update tbl_user set name=:name where id=:id')).params(name='赵六', id=1)
session.commit()
8、Flask-SQLAlchemy
Flask-SQLAlchemy是在sqlalchemy基础上进行了封装
8.1 安装
pip install Flask-SQLAlchemy
8.2 使用
import datetime
from urllib.parse import quote_plus
from flask import Flask
from flask_sqlalchemy import SQLAlchemy
from flask_sqlalchemy.query import Query
class BaseConfig:
# mysql 配置
MYSQL_HOST = "192.168.3.66"
MYSQL_USERNAME = "root"
MYSQL_PASSWORD = "123456"
MYSQL_PORT = 3306
MYSQL_DATABASE = "PearAdminFlask"
# mysql 数据库的配置信息
SQLALCHEMY_DATABASE_URI = f"mysql+pymysql://{MYSQL_USERNAME}:{quote_plus(MYSQL_PASSWORD)}@{MYSQL_HOST}:{MYSQL_PORT}/{MYSQL_DATABASE}?charset=utf8mb4"
app = Flask(__name__)
# 加载配置
app.config.from_object(BaseConfig)
# 将SQLAlchemy注册到app中
db = SQLAlchemy(app=app, query_class=Query)
# 模型类继承db.Model,定义字段大同小异
class User(db.Model):
__tablename__ = 'admin_user'
id = db.Column(db.Integer, primary_key=True, autoincrement=True, comment='用户ID')
username = db.Column(db.String(20), comment='用户名')
realname = db.Column(db.String(20), comment='真实名字')
avatar = db.Column(db.String(255), comment='头像', default="/static/admin/admin/images/avatar.jpg")
remark = db.Column(db.String(255), comment='备注')
password_hash = db.Column(db.String(128), comment='哈希密码')
enable = db.Column(db.Integer, default=0, comment='启用')
dept_id = db.Column(db.Integer, comment='部门id')
create_time = db.Column(db.DateTime, default=datetime.datetime.now, comment='创建时间')
update_time = db.Column(db.DateTime, default=datetime.datetime.now, onupdate=datetime.datetime.now, comment='创建时间')
# 因为要启动app才能加载配置,这里简单定义一个视图
@app.post("/")
def index():
# 有两种使用方式:
# 第1种完全一样
# 第2种与Django的模型类查询类似,不清楚是否与Django语法完全一样,没空验证。。。
# 查询所有用户
print(1, db.session.query(User).all())
print(2, User.query.all())
# 多条件查询
_filter = [User.realname.like("%测试%"), User.dept_id == 1]
print(1, User.query.filter(*_filter).all())
print(2, db.session.query(User.realname, User.dept_id).filter(*_filter).all())
return "请求成功"
if __name__ == '__main__':
app.run()
8.2.1 flask_sqlalchemy的分页查询
# 返回分页数据。error_out默认为True报错,页码超出分页数报错。
user_info = db.session.query(User).paginate(page=1, per_page=10, error_out=False)
# 过滤后的所有页的数据总数
total = user_info.total