簡稱 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'