ORM(Object Relational Mapping)中文翻译过来叫作对象关系映射,它把一个类映射成数据库里的一张表而属性映射成数据库表的列,每一个实例对象对应数据库表里的一行数据。通过它我们可以直接使用面向对象的方式来编写程序而不再直接书写原生的SQL语句,而且大部分ORM框架支持多种数据库只需很少的配置即可完成数据库产品的更换,在Python中常见的ORM框架有SQLAlchemy、Django Model、PeeWee等。
SQLAlchemy是目前很流行的ORM框架,Flask-SQLAlchemy扩展可以帮助我们在Flask中方便的使用SQLAlchemy
安装Flask-SQLAlchemy扩展,假如你没有安装SQLAlchemy会一并安装上
pip install flask-sqlalchemy
模型是对数据抽象并提供通用访问接口的一种方式,一个模型类对应于数据库中的一张表。
定义模型类
from flask import Flask
from flask_sqlalchemy import SQLAlchemy
app = Flask(__name__)
app.config["SQLALCHEMY_DATABASE_URI"] = "sqlite:///test.db"
db = SQLAlchemy(app)
# 分类
class Category(db.Model):
__tablename__ = "t_category"
id = db.Column(db.Integer, primary_key=True)
name = db.Column(db.String(50))
db.create_all()
代码说明: 所有模型必须继承至 db.Model,使用 __tablename__
属性指定数据库中表名,如果不指定表名默认为类名。模型类的属性对应于数据表中的列,通过 db.Column 类的实例来描述,最后 db.create_all() 会帮助我们创建表结构
连接数据库的信息通过app.config[“SQLALCHEMY_DATABASE_URI”]来指定,常见数据库的连接信息如下
数据库 | 连接地址 |
---|---|
SQLite | sqlite:///文件名 |
MySQL | mysql+pymysql://用户名:密码@主机:3306/数据库名 |
Oracle | oracle+cx_oracle://用户名:密码@主机:1521/SID名 |
SQL Server | mssql+pymssql://用户名:密码@主机:端口/数据库名 |
注: 上面有些URL中的协议部分有加号需要安装指定模块,如MySQL需要安装PyMySQL模块如果不书写加号部分会默认使用MySQLDB进行连接
描述列时常用的数据类型
类型名 | Python 类型 | 说明 |
---|---|---|
Integer | int | 普通整数,一般是32位 |
SmallInteger | int | 取值范围小的整数,一般是 16 位 |
BigInteger | int 或 long | 不限制精度的整数 |
Float | float | 浮点数 |
Double | float | 双精度浮点类型 |
Numeric | decimal.Decimal | 定点数(默认decimal.Decimal,asdecimal=False时转float) |
String | str | 变长字符串 |
Text | str | 编程字符串,对较长或不限长度的字符串做了优化 |
Boolean | bool | 布尔值 |
Date | datetime.date | 日期 |
Time | datetime.time | 时间 |
DateTime | datetime.datetime | 日期和时间 |
Interval | datetime.timedelta | 时间间隔 |
Enum | Enum | 枚举类型 |
ARRAY | array | 数组 |
PickleType | 任何 python 对象 | 自动使用 Pickle 序列化 |
Unicode | unicode | 变长 Unicode 字符串 |
UnicodeText | unicode | 变长 Unicode 字符串,对较长或不限长度的字符串做了优化 |
LargeBinary | binary | 大型二进制字节数据 |
描述列时还可以添加选项,常见如下
选项名 | 说明 |
---|---|
primary_key | 设置主键 |
default | 设置默认值 |
unique | 设置是否唯一 |
nullable | 设置是否允许为空 |
index | 设置是否添加索引 |
autoincrement | 自增 |
comment | 注释 |
注: SQLAlchemy要求每个模型必须定义主键默认对主键进行自增,默认映射到数据库中的列名为自定义类的属性名称,如果想修改该值需要在db.Column()第一个参数指定
添加索引
# 方式一
class Category(db.Model):
__tablename__ = "t_category"
id = db.Column(db.Integer, primary_key=True)
name = db.Column(db.String(50),index =True)
# 方式二
class Category(db.Model):
__tablename__ = "t_category"
__table_args__ = (
db.Index('idx_name_user', 'user_id', 'name', unique=True),
)
id = db.Column(db.Integer, primary_key=True)
name = db.Column(db.String(50))
user_id = db.Column(db.String(50))
模型之间可以互相关联使得相关联的数据能够很容易的从数据库中取出,模型关联映射到数据库中为主外键关系。
一对多/多对一
这是最常见的一种关系,比如说一个分类下面有多篇文章
# 分类
class Category(db.Model):
__tablename__ = "t_category"
id = db.Column(db.Integer, primary_key=True)
name = db.Column(db.String(50))
posts = db.relationship("Post", backref="category")
# 文章
class Post(db.Model):
__tablename__ = "t_post"
id = db.Column(db.Integer, primary_key=True)
title = db.Column(db.String(50))
content = db.Column(db.Text)
category_id = db.Column(db.Integer, db.ForeignKey("t_category.id"))
代码说明: 在多的一端通过 db.ForeignKey 定义外键指向一的一端的主键列,该参数值格式为:表名.列名;通过 db.relationship 指定关系,第一个参数填写有关系的模型类,当该模型类还未定义可以先填写字符串底层会通过反射来获取,通过backref参数向有关系的一端添加一个可以引用自己的属性,该语句也可以定义在关系中的任意一方但必须指定的关系要明确。
一对一
常见的场景有一个人只有一个身份证,只需要把一对多关系中多的一端的外键指定为唯一值即可
# 人
class People(db.Model):
__tablename__ = "t_people"
id = db.Column(db.Integer, primary_key=True)
name = db.Column(db.String(15))
codes = db.relationship("Code", backref="people")
# 身份证
class Code(db.Model):
__tablename__ = "t_code"
id = db.Column(db.Integer, primary_key=True)
title = db.Column(db.String(18))
p_id = db.Column(db.Integer, db.ForeignKey("t_people.id"), unique=True)
多对多
常见的场景有一个用户对应多个角色,而一个角色同时也能对应多个用户,在关系型数据库中一般通过中间表的形式体现
方式一: 使用中间模型帮我们建立中间表
# 用户
class User(db.Model):
__tablename__ = "t_user"
id = db.Column(db.Integer, primary_key=True)
name = db.Column(db.String(15))
user_roles = db.relationship("UserRole", backref="user")
# 角色
class Role(db.Model):
__tablename__ = "t_role"
id = db.Column(db.Integer, primary_key=True)
name = db.Column(db.String(15))
role_users = db.relationship("UserRole", backref="role")
# 关系
class UserRole(db.Model):
__tablename__ = "t_user_role"
user_id = db.Column(db.Integer, db.ForeignKey("t_user.id"), primary_key=True)
role_id = db.Column(db.Integer, db.ForeignKey("t_role.id"), primary_key=True)
方式二: 手动创建中间表
t_user_role = db.Table("t_user_role",
db.Column("user_id", db.Integer, db.ForeignKey("t_user.id"), primary_key=True),
db.Column("role_id", db.Integer, db.ForeignKey("t_role.id"), primary_key=True)
)
# 用户
class User(db.Model):
__tablename__ = "t_user"
id = db.Column(db.Integer, primary_key=True)
name = db.Column(db.String(15))
roles = db.relationship("Role", backref="users", secondary=t_user_role)
# 角色
class Role(db.Model):
__tablename__ = "t_role"
id = db.Column(db.Integer, primary_key=True)
name = db.Column(db.String(15))
以上两种方式都能实现多对多关系,我个人更喜欢方式二因为方式一多了一个模型类操作起来更繁琐
在上面的例子中我们只使用了db.create_all(),他会扫描模型创建所有的表,如果想删除所有表使用如下语句
db.drop_all()
当然在实际项目中我们不会在代码中管理表一般会借助工具来生成,如使用Flask-Script扩展写一个命令函数,在Shell中执行上面的代码,更高级的使用Flask-Migrate扩展它不仅能创建表还能进行数据迁移。
有了模型类也生成了表,接下来通过模型类对数据进行增删改查
只需构建模型类的实例,添加进数据库会话即可
c = Category(name="Java")
db.session.add(c)
对于一对多/多对一关联关系
p = Post()
p.title = "Java入门示例"
p.content = "Hello World ...."
p.category = c
db.session.add(p)
对于多对多关系,下例采用方式二定义的多对多关系,至于方式一定义的关联关系操作与一对多类似
u = User(name="Harmel")
r1 = Role(name="ROOT")
r2 = Role(name="NONE")
db.session.add(r1)
db.session.add(r2)
u.roles = [r1, r2]
db.session.add(u)
# 也可以采用如下方式
u.roles.append(r1)
u.roles.append(r2)
把查出来的对象更改属性后再次保存即可修改
# 根据ID查询
c = Category.query.get(1)
c.name = "Python"
db.session.add(c)# 也可以直接db.session.commit()
显示删除
c = Category.query.get(1)
db.session.delete(c)
隐式删除就是在一对一或一对多中配置级联删除后,如果删除一的一端另一端的数据也会同步删除
批量删除/全量删除
#方法一(批量删除)
bills = Bill.query.filter(Bill.user_id ==1).all()
for bill in bills:
db.session.delete(bill)
db.session.commit()
#方法二(全量删除)
Bill.query.filter(Bill.user_id ==1).delete(synchronize_session=False)
db.session.commit()
synchronize_session用于query在进行delete or update操作时,对session的同步策略。
参数:
False ------- 不对session进行同步,直接进行delete or update操作。’fetch’ ------- 在delete or update操作之前,先发一条sql到数据库获取符合条件的记录。在delete or update操作之后,将session的identity_map与前一步获取到的记录进行match,符合条件的就从session中删掉或更新。
’evaluate’ ------- 在delete or update操作之前,用query中的条件直接对session的identity_map中的objects进行eval操作,将符合条件的记录下来。在deleteor update操作之后,将符合条件的记录删除或更新。
对于每次的增、删、改操作都必须要提交事务
db.session.commit()
我们可以配置让其自动提交
app.config["SQLALCHEMY_COMMIT_ON_TEARDOWN"] = False
回滚事务
db.session.rollback()
django是有orm操作的 可想而知 那么flask也是有orm操作的,其实flask的orm操作的使用和djnago的是差不多的
django的orm操作进行条件筛选的时候后面跟着的是objects
django
表名.objects.语句
flask的是query
表名.objects.语句
eg:
django:
User.objects.filter(条件).first
flask:
User.query.filter_by(条件).first
常用查询语句:
all() 查询所有
filter_by / filter 单个查询
filter_by 不需要指定是哪个类的哪个属性,只需要制定属性及其目标值就可以了, 并且只能写具体的值不能写模糊值
filter filter中指定查询条件的时候需要指定类名的前缀。可以指定模糊值
order_by 排序
查询集
原始查询集
类名.query得到的结果就为原始查询集
数据查询集
加上各种的过滤器的方法 最终返回的结果 为数据查询集 都使用数据查询集
过滤器
(1) all 查询所有 以列表形式返回 不支持连贯操作
类名.query.all()
User.query.all() # 查询User表中的所有数据
(2) filter() 过滤
类名.query.filter([类名.属性名 条件操作符 值])
User.query.filter() #返回所有
User.query.filter(User.age>) #查询年龄大于20的数据
User.query.filter(User.age>,User.age<) #查询年龄大于20的数据 and 小于40
(3) filter_by 只支持参数为关键字参数
类名.query.filter_by(属性名=值…)
data = User.query.filter_by(id=12)
data = User.query.filter_by(id>12) #错误写法 不可以使用模糊查到
data = User.query.filter_by(id=12,age=20)
(4) offset 偏移量
offset(num)
User.query.filter().offset()
(5) limit 取值
limit(num)
User.query.filter(User.age>18).limit() 查到的结果只取两个
(6) offset和limit组合使用
User.query.offset().limit() 也是只取两个
(7) order_by() 排序
默认是升序
data = User.query.order_by(User.age).all() #升序
data = User.query.order_by(-User.age).all() #降序
data = User.query.order_by(User.age.desc()).all() #降序
(8) first 取出第一条数据 返回对象
User.query.first() == User.query.get()
(9) get 获取id对应的数据
查询成功返回对象 查询失败 返回None
User.query.get(12)
(10) contains 包含关系
User.query.filter(User.username.contains('7')) #username中包含数字7的数据
(11) like 模糊查询
User.query.filter(User.username.like('李%')) #以李作为开头的
(12) startswith endswith 以…开头 以…结尾
User.query.filter(User.username.startswith('李')) # 以姓李的开头
User.query.filter(User.username.endswith('')) # 以6为结尾的
(13) 比较运算符
1. __gt__
2. __ge__
3. __lt__
4. __le__
5. > <
6. >=
7. <=
8. ==
9. !=
注意:
当查询数据中包含NULL
结果时,通过!=
的查询会过虐掉为NULL
的对象
所以需要将为NULL
的对象包含进来
User.query.filter(or_(Order.school.is_(None),Order.school!= 'THU')).all()
(14) in 和 not in
User.query.filter(User.age.in_([12,23,36]))
User.query.filter(User.age.notin_([12,23,36]))
(15) is null
#空
User.query.filter(User.username.is_(None))
User.query.filter(User.username ==None)
#非空
User.query.filter(User.username.isnot(None))
User.query.filter(User.username !=None)
(16) and_
多个条件 用逗号隔开,为and操作
from sqlalchemy import and_
User.query.filter(and_(User.age==18,User.id==12))
(17) or_
from sqlalchemy import or_
@main.route('/and/')
def myAnd():
data = User.query.filter(or_(User.age==18,User.id==12))
data = User.query.filter(and_(User.username.like('%6%')),or_(User.age>=18,User.id==12))
return render_template('show.html',data=data)
(18) not_
from sqlalchemy import not_
@main.route('/and/')
def myAnd():
# data = User.query.filter(not_(User.age>18,User.id==12))
#错误写法只能给一个条件取反
data = User.query.filter(not_(User.age>18))
return render_template('show.html',data=data)
(19) count 统计
data = User.query.filter(not_(User.age>18)).count()
# 第一个参数指定第几页
# 第二个参数指定每页多少条数据
paginate = Category.query.paginate(1, 5)
categorys = paginate.items
page_count = paginate.pages # 总页数
has_prev = paginate.has_prev # 是否有上一页
has_next = paginate.has_next # 是否有下一页
prev_paginate = paginate.prev() # 上一页
next_paginate = paginate.next() # 下一页
模块:
pip install flask-migrate
pip install flask-script
使用
(1) 实例化
from flask_migrate import Migrate,MigrateCommand
from flask_sqlalchemy import SQLalchemy
app = Flask(__name__)
db = SQLalchemy(app)
migrate = Migrate(app,db=db)
manager = Manager(app)
manager.add_command('db',MigrateCommand)
(2) 初始化 迁移文件目录
python manage.py db init
(3) 生成迁移文件
python manage.py db migrate
(4) 执行迁移文件
python manage.py db upgrade
注意
如果当前存在 模型 但是执行创建迁移文件的时候 提示没有任何改变的时候 需要查看当前的模型类是否有使用(导入)
flask-sqlalchemy的使用要么放置在一个视图内,要么提供一个应用(flask)上下文
官方文档给出了如下解释:
简单来说就是如果要在视图函数以外使用sqlalchemy就必须通过app.app_context().push()来推入一个上下文,第二个是通过with上下文来确定作用在APP上下文区域内的代码.不然就会报No application found. Either work inside a view function or push an application context.
错误
个人觉得还是通过装饰器的方式来的方便和美观,当然第二种方式也相当优美.
下面是我的解决方法:
def sqlalchemy_context(app):
def add_context(func):
@wraps(func)
def do_job(*args, **kwargs):
app.app_context().push()
result = func(*args,**kwargs)
return result
return do_job
return add_context
然后我使用数据库的地方:
@sqlalchemy_context(APP)
def init_primary_key():
Model.query.filter_by()
...
我APP传入方式是因为避免循环导包, 思路是这样,实现方式自己把握好了.