简称 ORM, O/RM, O/R Mappingpython
持久化模型mysql
数据操做和业务逻辑区分redis
封装了数据库操做, 提高效率sql
省略了庞大的数据访问层shell
flask中是不自带ORM的,若是在flask里面链接数据库有两种方式数据库
是python 操做数据库的一个库。可以进行 orm 映射官方文档 sqlchemy 编程
SQLAlchemy“采用简单的Python语言,为高效和高性能的数据库访问设计,实现了完整的企业级持久模型”。flask
SQLAlchemy的理念是,SQL数据库的量级和性能重要于对象集合;而对象集合的抽象又重要于表和行。服务器
app.config['SQLALCHEMY_DATABASE_URL'] = "mysql://用户名:密码@数据库服务器地址:端口号/数据库名称"
from flask import Flask from flask_sqlalchemy import SQLAlchemy # 导入pymysql , 而且将其假装成 ,MySQLdb # import pymysql # pymysql.install_as_MySQLdb() app = Flask(__name__) # 经过 app 配置数据库信息 # app.config['SQLALCHEMY_DATABASE_URI'] = "mysql://root:123456@127.0.0.1:3306/flask" app.config['SQLALCHEMY_DATABASE_URI'] = "mysql+pymysql://root:123456@127.0.0.1:3306/flask" # 建立数据库实例 db = SQLAlchemy(app) print(db) @app.route('/') def hello_world(): return 'Hello World!' if __name__ == '__main__': app.run()
根据数据库表结构而建立出来的类 ( 模型类, 实体类 )session
1. 先建立实体类, 经过实体类自动生成表
2. 先建立表, 根据表借口编写实体类
class MODELNAME(db.Model): __tablename__ = "TABLENAME" COLUMN_NAME = db.Column(db.TYPE, OPTIONS)
""" MODELNAME : 定义的模型类名称, 根据表名而设定 好比: 表名: USERS MODELNAME : Users TABLENAME : 要映射到数据库中表的名称 COLUMN_NAME : 属性名, 映射到数据库中的列名 TYPE : 映射到列的数据类型 类型 python类型 说明 Integer int 32 位整数 SmallInteger int 16 位整数 BiglInteger int 不限精度整数 Float float 浮点数 String str 字符串 Text str 字符串 Boolean bool 布尔值 Date datatime.date 日期类型 Time datatime.time 时间类型 DateTime datatime.datatime 时间和日期类型 OPTIONS : 列选项, 多个列选项用逗号隔开 选项名 说明 autoincrement 若是设置为 True, 表示该列自增, 列类型是整数且是主键, 默认自增 primary_key 若是设置为 Ture, 表示该列为主键 unique 若是设置为 Ture, 表示该列的值惟一 index 若是设置为 Ture, 表示该列的值要加索引 nullable 若是设置为 Ture, 表示该列的值能够为空 default 指定该列的默认值 """
""" 建立实体类 Users , 映射数据库中的 users 表 id 自增 主键 username 长度为 80 的字符串, 非空, 惟一, 加索引 age 整数 容许为空 email 长度 120 字符串 惟一 """ class Users(db.Model): __tablename__ = 'users' # 若是表名为 users 的话能够省略 id = db.Column(db.Integer, primary_key=True) username = db.Column(db.String(80), nullable=False, unique=True, index=True) age = db.Column(db.Integer, nullable=True) email = db.Column(db.String(120), unique=True)
db.create_all() # 将全部的类建立到数据库上 # 只有类对应的表不存在才会建立, 不然无反应, 所以想更改表结构就必须删掉重建 # 因而可知此方法及其愚蠢 # 临时性的, 此方法通常都不会用, 不对, 是绝对不要用
db.drop_all() # 将全部的数据表所有删除
实体类的改动映射回数据库
此操做须要依赖第三方库
若是不使用第三方库也想重写表结构
就只能所有删掉在所有建立, 极其智障.
对于项目进行管理, 启动项目, 添加 shell 命令
包 flask-script
类 Manager
# 将 app 交给 Manager 管理 manager = Manager(app) if __name__ == '__main__': # app.run(debug=True) manager.run() """ 命令行启动 python app.py runserver --port 5555 python app.py runserver --host 0.0.0.0 python app.py runserver --host 0.0.0.0 --port 5555 """
包 flask_migrate
类
Migrate 管理 app 和 db 中间的关系, 提供数据库迁移
MigrateCommand 容许终端中执行迁移命令
# 建立 Migrate 对象 migrate = Migrate(app, db) # 为 manager 增长子命令, 添加作数据库迁移的子命令manager.add_command('db', MigrateCommand) """
命令行执行
python app.py db init 初始化 一个项目中的 init 只执行一次便可, 生成 migrations 文件夹 python app.py db migrate 将编辑好的实体类生成中间文件, 只有在实体类有更改的时候才会生成 python app.py db upgrade 将中间文件映射回数据库
其余的命令能够经过 python app.py db 会进行查阅 """
# 修改操做 # 建立实体类对象并赋值 user = Users() user.username = 'yangtuo' user.age = 18 user.email = '7452@qq.com'
# 将实体对象保存回数据库 db.session.add(user)
# 针对非查询操做, 必须手动操做提交 db.session.commit()
每次进行修改都须要执行 add 以及 commit 略显繁琐
设置 SQLALCHEMY_COMMIT_ON_TEARDOWN 便可不须要手动更新, 可是仅局限于在 视图函数 中能够简化操做
# 配置数据库操做自动提交 app.config['SQLALCHEMY_COMMIT_ON_TEARDOWN'] = True @app.route('/add_user') def add_view(): user = Users() user.username = 'yangtuo' user.age = 18 user.email = '7452@qq.com' # 若是设置了 SQLALCHEMY_COMMIT_ON_TEARDOWN = True 则不须要进行 add 和 commit 操做 # db.session.add(user) # db.session.commit() return '增长数据成功'
db.session.query(查询域).过滤方法(过滤条件).执行方法()
# 条件必需要由 实体类.属性 构成 db.session.query(Users).filter(age>18).all() # 错误 db.session.query(Users).filter(Users.age>18).all() # 正确
# 此命令支持跨表多个查询, 所以必需要带实体来进行查询 query = db.session.query(Users.id, Teacher.tanme) query = db.session.query(Users, Teacher) # 查询 Users,Teacher 的全部的列 # 无执行方法的时候 返回值为一个 query 对象, 类型为 BaseQuery print(query) # sql 语句 print(type(query)) # <class 'flask_sqlalchemy.BaseQuery'> # 有执行方法的时候 返回值为 列表 query = db.session.query(Users.id, Teacher.tanme).all() print(query) # 列表
# 执行函数 all() # 列表形式返回全部数据 first() # 返回实体对象第一条数据, 没有数据返回 None fist_or_404() # 效果同上, 没有数据返回 404 count() # 返回查询结果数量
# 过滤器 filter() # 指定查询条件 ( 全部条件均可以 ) filter_by() # 等值条件中使用( 只能作 = 操做, > < 都不行, 注意不是 == ) limit() # 限制访问条数 order_by() # 排序 group_by() # 分组
# 多条件查询 # 链式操做或者逗号隔开表示并列条件 db.session.query(Users).filter(Users.age>17).filter(Users.id>10).all() db.session.query(Users).filter(Users.age>17,Users.id>10).all()
# 或关系查询, 首选须要引入 from sqlalchemy import or_ # 语法 db.session.query(Users).filter(or_(条件1,条件2))
# 语法 .and_(), .or_() db.session.query(Users).filter( or_( Users.id < 2, and_(Users.name == 'eric', Users.id > 3), Users.extra != "" )).all() # 能够嵌套
任何的查询是在 类字段前加 ~ 表示非查询
db.session.query(Users).filter(~Users.email.like('%yangtuo%')).all() # not like
# 模糊查询 # 语法 .like('%xxxx%') db.session.query(Users).filter(Users.email.like('%yangtuo%')).all() # like db.session.query(Users).filter(Users.email.like('%%%s%%' % 'yangtuo')).all() # 格式化字符串形式要 两个 %% 来表示一个 % 所以就出现这么多% 很麻烦...
# 区间查询 # 语法 .between(条件1,条件2) db.session.query(Users).filter(Users.id.between(30,40)) # 30~40 包括 30 40 db.session.query(Users).filter(~Users.id.between(30,40)) # <30, >40 不包括 30 40
# 区间判断 # 语法 .in_(列表/元组) db.session.query(Users).filter(Users.id.in_([1,3,4])).all() # in
# 截取查询 # .limit(n) db.session.query(Users).limit(3).all()
limit 和 offset 能够配合实现分页操做
# 跳过查询 # .offset(n) db.session.query(Users).offset(3).all() db.session.query(Users).offset(3).limit(3).all()
# 排序查询 # 语法 .order_by('列 规则,列 规则') # 规则 desc 降序 asc 升序(默认, 不写就表示升序) db.session.query(Users).order_by('Users.id desc').all() # 先按xxx排序,xxx排序不出来的部分 再用 yyy 来排序 db.session.query(Users).order_by('Users.id desc, Users.name').all()
须要配合聚合函数
from sqlalchemy import func func.sum() # 求和 func.count() # 求数量 func.max() # 求最大值 func.min() # 求最小值 func.avg() # 求平均数
# 语法 db.session.query(func.聚合函数(实体类.属性),func.聚合函数(实体类.属性)..).all()
# 返回值 列表里面又套了个元组, 由于聚合出来的数据是固定的不可修改,所以用元组修饰
[((),(),()...)]
# 分组查询 # 语法 db.session.query(查询列, 聚合列).group_by(分组列).all()
ret = db.session.query(Users.is_active, func.avg(Users.age)).group_by('is_active').all() print(ret) # [(None, Decimal('22.5000')), (True, Decimal('24.0000'))]
条件必须放在 group_by 后面, 关键字不在使用 filter 使用 having 和 mysql 相似 (不能再使用where)
ret = db.session.query(Users.is_active, func.avg(Users.age)).group_by(Users.age).having(func.avg(Users.age) > 23).all() print(ret) # [(True, Decimal('24.0000'))]
# 查询全部年龄 18 以上按找 active 分组后, 组内大于两人的信息 ret = db.session.query(Users.is_active, func.count(Users.is_active))\ .filter(Users.age > 18)\ .group_by(Users.is_active)\ .having(func.count(Users.is_active) > 2).all() print(ret)
执行顺序和 mysql 的 同样 , mysql 的优先顺序点击 这里
可是 mysql 中没有 offset , 可是 mysql 的 limit 能够提供两个参数来获取相似 offset 的功能
所以结合 对 mysql 的分析 SQLAlchemy 的查询优先顺序以下
query > filter > group > having > order >limit = offset
# 查询全部人的年龄 ret = db.session.query(func.sum(Users.age)).all() # 查询多少人 ret = db.session.query(func.count(Users.id)).all() # 查询全部 大于 19 岁的人的平均年龄 ret = db.session.query(func.avg(Users.age)).filter(Users.age > 19).all() # 查询 按找active 分组后 组内成员大于2 的组信息 ret = db.session.query(Users.is_active, func.count(Users.is_active))\ .group_by(Users.is_active)\ .having(func.count(Users.is_active) > 2).all() # 查询全部年龄 18 以上按找 active 分组后, 组内大于两人的信息 ret = db.session.query(Users.is_active, func.count(Users.is_active))\ .filter(Users.age > 18)\ .group_by(Users.is_active)\ .having(func.count(Users.is_active) > 2).all()
db.session.delete(记录对象)
以上都是基于 db.session 的方式查询
还能够基于实体类进行查询 , 和 db 方式没有效率性能上的区别
两种方式没什么大区别, 貌似也没有说法哪一种更好哪一种在某场合下必需要用的状况
Users.query.all() db.session.query(Users).all() db.session.query(Users.id).all() db.session.query('users.id'').all() User.query.filter(Users.is_active==True).all() # 实体类方式查询中的等于必须是双等于号
以及 全部的 实体类.列名, 均可以用 "表名.属性" 或者 "实体类.列名" 来表示 , 不会影响显示
可是若是使用 实体类.列名 的方式 会存在互相引用的时候由于未加载到的问题 所以这个要稍微注意到
A表中的一条数据能够关联到B表中的多条数据
B表中的一条数据只能关联到A表中的一条数据
示例
Teacher(老师) 与 Course(课程)之间的关系
一名老师(Teacher)只能教授一门课程(Course)
一门课程(Course)能够被多名老师(Teacher)所教授
"主外键关系" 完成实现一对多
在 "一" 表中建立主键
在 "多" 表中建立外键来对 "一" 表进行关联
1. 在 "多" 的实体类中增长对 "一" 的实体类的引用( 外键 )
语法
外键列名 = db.Column( db.type, db.ForeignKey(主键表.主键列) )
实例
class Course(db.Model): id = db.Column(db.Integer, primary_key=True) canme = db.Column(db.String(30), nullable=False, unique=True, index=True) class Teacher(db.Model): id = db.Column(db.Integer, primary_key=True) tanme = db.Column(db.String(30), nullable=False) tage = db.Column(db.Integer, nullable=False) # 增长外键列 cid = db.Column(db.Integer, db.ForeignKey(Course.id))
2. 在 "一" 的实体类中增长对 "关联属性" 以及 "反向引用关系属性"
目的 在编程语言中建立类(对象) 与类(对象) 之间的关系, 不会对数据库有所操做, 仅仅是为了代码逻辑方便
关联属性
在 "一" 的实体中, 经过哪一个 <<属性>> 来获取对应到 "多" 的实体对象们
反向引用关系属性
在 "多" 的实体中, 经过哪一个 <<属性>> 来获取对应到 "一" 的实体对象
语法
属性名 = db.relationship( 关联到的实体类, backref="自定义一个反向关系属性名", lazy="dynamic", ) """ lazy 参数: 指定如何加载相关记录 1.select 首次访问源对象时加载关联数据 2.immediate 源对象加载后立马加载相关数据(使用链接) 3.subquery 效果同上(使用子查询) 4.noload 永不加载 5.dynamic 不加载记录但提供加载记录的查询 """
实例
class Course(db.Model): id = db.Column(db.Integer, primary_key=True) canme = db.Column(db.String(30), nullable=False, unique=True, index=True) # 增长关系属性 和 反向引用关系属性 teachers = db.relationship( 'Teacher', backref="course", lazy="dynamic" ) class Teacher(db.Model): id = db.Column(db.Integer, primary_key=True) tanme = db.Column(db.String(30), nullable=False) tage = db.Column(db.Integer, nullable=False) # 增长外键列 cid = db.Column(db.Integer, db.ForeignKey(Course.id))
方式一 操做外键字段
直接操做本表中有的字段 外键映射字段进行赋值, 即正向操做
tea = Teacher()
tea.cid = 1
方式二 操做反向引用属性
操做关联表的 relationship 关系字段的 bcakref 属性来进行赋值, 即反向操做
cou = Course() tea = Teacher() tea.course = cou
在一对多的基础上添加惟一约束 ( 即对 "多" 设置惟一约束 )
语法
设置外键字段和惟一索引
外键列名 = db.Column( db.TYPE, db.ForeignKey('主表.主键'), unique = True )
增长关联属性和反向引用关系属性
在另外一个实体类中增长关联属性和反向引用关系属性
属性名 = db.relationship( "关联的实体类名", backref="反向引用关系属性名", uselist = False ) # userlist:设置为False,表示关联属性是一个标量而非一个列表
实例
用户和用户老婆是一对一关系( 大概 )
class Wife(db.Model): id = db.Column(db.Integer,primary_key=True) name = db.Column(db.String(30)) #增长外键约束和惟一约束,引用自users表中的id列 uid = db.Column( db.Integer, db.ForeignKey('users.id'), unique = True )
class Users(db.Model): id = db.Column(db.Integer,primary_key=True) username = db.Column(db.String(80),nullable=False,unique=True,index=True) age = db.Column(db.Integer,nullable=True) email = db.Column(db.String(120),unique=True) isActive = db.Column(db.Boolean,default=True) #增长关联属性和反向引用关系属性(Wife表一对一) wife = db.relationship( "Wife", backref="user", uselist = False )
关联字段操做 ( 和一对多彻底同样, 本质上就是一对多字段加了个惟一索引而已 )
方式一 操做外键字段
直接操做本表中有的字段 外键映射字段进行赋值, 即正向操做
wif = Wife()
wif.uid = 1
方式二 操做反向引用属性
操做关联表的 relationship 关系字段的 bcakref 属性来进行赋值, 即反向操做
wife = Wife()
user = User() wife.Wife = user
双向的一对多, "多" 表明着是没法直接用字段来处理, 即不会再本表中添加关系字段, 只须要作反向引用属性便可
并且须要指定第三张表来映射( 第三张表须要手动建立 ), 设置语法在两个多对多表中的任意一个设置便可
由于此设置中同时对两个都设置了外向引用属性
设置语法
属性值 = db.relationship( "关联实体类", lazy="dynamic", backref=db.backref( "关联实体类中的外向引用属性", lazy="dynamic" ) secondary="第三张关联表名" )
实例
# 编写StudentCourse类,表示的是Student与Course之间的关联类(表)
# 此表须要设定到两个关联表的外键索引 # 表名:student_course class StudentCourse(db.Model): __tablename__ = "student_course" id = db.Column(db.Integer, primary_key=True) # 外键:student_id,引用自student表的主键id student_id = db.Column(db.Integer, db.ForeignKey('student.id')) # 外键:course_id,引用自course表的主键id course_id = db.Column(db.Integer, db.ForeignKey('course.id')) # 两个多对多关联表二选一进行配置便可 class Student(db.Model): id = db.Column(db.Integer, primary_key=True) sname = db.Column(db.String(30), nullable=False) sage = db.Column(db.Integer, nullable=False) isActive = db.Column(db.Boolean, default=True) # 在此表中进行设置反向引用属性, 一次性设置两份 class Course(db.Model): id = db.Column(db.Integer, primary_key=True) canme = db.Column(db.String(30), nullable=False, unique=True, index=True) students = db.relationship( "Student", lazy="dynamic", # 针对 Course 类中的 students 属性的延迟加载 backref=db.backref( "courses", lazy="dynamic" # 针对 students 类中的 courses 属性的延迟加载 ) secondary="student_course" # 指定第三张关联表 )
关联数据操做
# 向第三张关联表中插入数据 # 取到 数据 courses = request.form.getlist('courses') # 根据数据 查到全部对象 obj_list = Course.query.filter(Course.id.in_(courses)).all() for cou in obj_list : # 方案1:经过 stu 关联 course 值 # stu.courses.append(cou) # 方案2:经过 course 关联 stu 值 cou.students.append(stu)
ps:
固然也能够直接操做第三张表, 毕竟是个实表. 可是不推荐. 就逻辑层面咱们是尽力无视第三表的存在
还请无视到底
pymysql
mysql+pymysql://<username>:<password>@<host>/<dbname>[?<options>]
SQLALCHEMY_DATABASE_URI = "mysql+pymysql://root:123456@127.0.0.1:3306/yangtuoDB?charset=utf8"
SQLALCHEMY_POOL_SIZE = 10
SQLALCHEMY_MAX_OVERFLOW = 5
不推荐
from flask_sqlalchemy import SQLAlchemy from flask import FLask app = Flask(__name__) app.config['SQLALCHEMY_DATABASE_URI'] ="mysql://root:12345@localhost/test" db = SQLAlchemy(app)
from flask_sqlalchemy import SQLAlchemy from flask import FLask db = SQLAlchemy() def create_app(): app = Flask(__name__) db.init_app(app) return app
必须在导入蓝图以前
from flask_sqlalchemy import SQLAlchemy
必需要在初始化以前导入模板,否则是没办法正确获得db
from .models import *
在离线脚本中操做数数据库建立 create_all drop_all
from chun import db,create_app app = create_app() app_ctx = app.app_context() # app_ctx = app/g with app_ctx: # __enter__,经过LocalStack放入Local中 db.create_all() # 调用LocalStack放入Local中获取app,再去app中获取配置
#方式一 db.session #会自动建立一个session db.session.add() db.session.query(models.User.id,models.User.name).all() db.session.commit() db.session.remove() #方式二 导入models models.User.query
chun 项目名 chun 与项目名同名的文件夹 static 静态文件相关 templates 模板文件相关 view 视图函数 acctount.py 具体视图函数 user.py 具体视图函数 __init__.py 初始化文件 models.py 数据库相关 create_table.py 数据库建立离线脚本 settings.py 配置文件
用于初始化,建立DB对象,app对象
from flask import Flask from flask_session import Session from flask_sqlalchemy import SQLAlchemy db = SQLAlchemy() from .views.account import ac from .views.user import us from .models import * def create_app(): app = Flask(__name__) app.config.from_object('settings.ProConfig') app.register_blueprint(ac) app.register_blueprint(us) db.init_app(app) # return app
配置文件相关存放,数据库的连接之类的
from redis import Redis class BaseConfig(object): # SQLALCHEMY_DATABASE_URI = "mysql+pymysql://root:123456@127.0.0.1:3306/s9day122?charset=utf8" SQLALCHEMY_POOL_SIZE = 10 SQLALCHEMY_MAX_OVERFLOW = 5 SQLALCHEMY_TRACK_MODIFICATIONS = False # pass
数据库表文件
from sqlalchemy.ext.declarative import declarative_base from sqlalchemy import Column from sqlalchemy import Integer,String,Text,Date,DateTime from sqlalchemy import create_engine from chun import db class Users(db.Model): __tablename__ = 'users' id = Column(Integer, primary_key=True) name = Column(String(32), index=True, nullable=False)
数据库离线操做脚本文件,用于 操做 app,g,db 的相关脚本
from chun import db,create_app app = create_app() app_ctx = app.app_context() with app_ctx: db.create_all() class ProConfig(BaseConfig): pass
视图请求回应相关的文件
from flask import Blueprint from chun import db from chun import models us = Blueprint('us',__name__) @us.route('/index') def index(): # 使用SQLAlchemy在数据库中插入一条数据 # db.session.add(models.Users(name='yangtuo',depart_id=1)) # db.session.commit() # db.session.remove() result = db.session.query(models.Users).all() print(result) db.session.remove() return 'Index'