Flask入門到放棄(四)—— 資料庫

Sunzz發表於2019-06-10

轉載請在文章開頭附上原文連結地址:https://www.cnblogs.com/Sunzz/p/10979970.html

資料庫操作

ORM

ORM 全拼Object-Relation Mapping,中文意為 物件-關係對映。主要實現模型物件到關聯式資料庫資料的對映

優點 :

  • 只需要物件導向程式設計, 不需要面向資料庫編寫程式碼.
    • 對資料庫的操作都轉化成對類屬性和方法的操作.
    • 不用編寫各種資料庫的sql語句.
  • 實現了資料模型與資料庫的解耦, 遮蔽了不同資料庫操作上的差異.
    • 不再需要關注當前專案使用的是哪種資料庫。
    • 通過簡單的配置就可以輕鬆更換資料庫, 而不需要修改程式碼.

缺點 :

  • 相比較直接使用SQL語句運算元據庫,有效能損失.
  • 根據物件的操作轉換成SQL語句,根據查詢的結果轉化成物件, 在對映過程中有效能損失.

Flask-SQLAlchemy

flask預設提供模型操作,但是並沒有提供ORM,所以一般開發的時候我們會採用flask-SQLAlchemy模組來實現ORM操作。

SQLAlchemy是一個關係型資料庫框架,它提供了高層的 ORM 和底層的原生資料庫的操作。flask-sqlalchemy 是一個簡化了 SQLAlchemy 操作的flask擴充套件。

SQLAlchemy: https://www.sqlalchemy.org/

安裝 flask-sqlalchemy

pip install flask-sqlalchemy

如果連線的是 mysql 資料庫,需要安裝 mysqldb 驅動

pip install flask-mysqldb

安裝flask-mysqldb時,注意

安裝 flask-mysqldb的時候,python底層依賴於一個底層的模組 mysql-client模組
如果沒有這個模組,則會報錯如下:

Command "python setup.py egg_info" failed with error code 1 in /tmp/pip-install-21hysnd4/mysqlclient/

解決方案:

apt-get install libmysqlclient-dev python3-dev

執行上面的安裝命令如果報錯:
   dpkg 被中斷,您必須手工執行 ‘sudo dpkg --configure -a’ 解決此問題。
則根據提示執行命令以下命令,再次安裝mysqlclient
    sudo dpkg --configure -a
    apt-get install libmysqlclient-dev python3-dev

解決了mysqlclient問題以後,重新安裝 flask-mysqldb即可。
pip install flask-mysqldb

資料庫連線設定

  • 在 Flask-SQLAlchemy 中,資料庫使用URL指定,而且程式使用的資料庫必須儲存到Flask配置物件的 SQLALCHEMY_DATABASE_URI 鍵中

    config.py,配置檔案程式碼:

class Config(object):
    DEBUG = True
    SECRET_KEY = "*(%#4sxcz(^(#$#8423"
    # 資料庫連結配置:
    #資料型別://登入賬號:登入密碼@資料庫主機IP:資料庫訪問埠/資料庫名稱
    SQLALCHEMY_DATABASE_URI = "mysql://root:123@127.0.0.1:3306/flask_students"
  • 其他設定:
# 動態追蹤修改設定,如未設定只會提示警告
SQLALCHEMY_TRACK_MODIFICATIONS = True
#查詢時會顯示原始SQL語句
SQLALCHEMY_ECHO = True
  • 配置完成需要去 MySQL 中建立專案所使用的資料庫
$ mysql -uroot -p123
mysql > create database flask_students charset=utf8mb4;

常用的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 在關係的另一模型中新增反向引用,用於設定外來鍵名稱,在1查多的
primary join 明確指定兩個模型之間使用的連表條件
uselist 如果為False,不使用列表,而使用標量值
order_by 指定關係中記錄的排序方式
secondary 指定多對多關係中關係表的名字
secondary join 在SQLAlchemy中無法自行決定時,指定多對多關係中的二級連表條件

資料庫基本操作

  • 在Flask-SQLAlchemy中,插入、修改、刪除操作,均由資料庫會話管理。
    • 會話用 db.session 表示。在準備把資料寫入資料庫前,要先將資料新增到會話中然後呼叫 commit() 方法提交會話。
  • 在 Flask-SQLAlchemy 中,查詢操作是通過 query 物件運算元據。
    • 最基本的查詢是返回表中所有資料,可以通過過濾器進行更精確的資料庫查詢。

定義模型類

我們後面會把模型建立到單獨的檔案中,但是現在我們先把模型類寫在manage.py檔案中。

from flask import Flask
from config import Config

app = Flask(__name__,template_folder='templates')
app.config.from_object(Config)


"""模型的建立"""
from flask_sqlalchemy import SQLAlchemy
db = SQLAlchemy(app)

class Course(db.Model):
    # 定義表名
    __tablename__ = 'tb_course'
    # 定義欄位物件
    id = db.Column(db.Integer, primary_key=True)
    name = db.Column(db.String(64), unique=True)
    price = db.Column(db.Numeric(6,2))
    # repr()方法類似於django的__str__,用於列印模型物件時顯示的字串資訊
    def __repr__(self):
        return 'Course:%s'% self.name

class Student(db.Model):
    __tablename__ = 'tb_student'
    id = db.Column(db.Integer, primary_key=True)
    name = db.Column(db.String(64), unique=True)
    email = db.Column(db.String(64),unique=True)
    age = db.Column(db.SmallInteger)
    sex = db.Column(db.Boolean,default=1)

    def __repr__(self):
        return 'Student:%s' % self.name

class Teacher(db.Model):
    __tablename__ = 'tb_teacher'
    id = db.Column(db.Integer, primary_key=True)
    name = db.Column(db.String(64), unique=True)

    def __repr__(self):
        return 'Teacher:%s' % self.name

@app.route("/")
def index():
    return "ok"

if __name__ == '__main__':
    app.run()

模型之間的關聯

一對多

class Course(db.Model):
    ...
    teacher_id = db.Column(db.Integer, db.ForeignKey('tb_teacher.id'))

class Teacher(db.Model):
    ...
    # 課程與老師之間的關聯
    courses = db.relationship('Course', backref='teacher', lazy='subquery')
    ...
  • 其中realtionship描述了Course和Teacher的關係。第一個引數為對應參照的類"Course"
  • 第二個引數backref為類Teacher申明新屬性的方法
  • 第三個引數lazy決定了什麼時候SQLALchemy從資料庫中載入資料
    • 如果設定為子查詢方式(subquery),則會在載入完Teacher物件後,就立即載入與其關聯的物件,這樣會讓總查詢數量減少,但如果返回的條目數量很多,就會比較慢
      • 設定為 subquery 的話,teacher.courses 返回所有當前老師關聯的課程列表
    • 另外,也可以設定為動態方式(dynamic),這樣關聯物件會在被使用的時候再進行載入,並且在返回前進行過濾,如果返回的物件數很多,或者未來會變得很多,那最好採用這種方式
      • 設定為 dynamic 的話,Teacher.courses返回查詢物件,並沒有做真正的查詢,可以利用查詢物件做其他邏輯,比如:先排序再返回結果

多對多

achievement = db.Table('tb_achievement',  
    db.Column('student_id', db.Integer, db.ForeignKey('tb_student.id')),  
    db.Column('course_id', db.Integer, db.ForeignKey('tb_course.id'))  
)

class Course(db.Model):
    ...
    students = db.relationship('Student',secondary=achievement,  
                                    backref='courses',  
                                    lazy='dynamic')
class Student(db.Model):
    ...

常用的SQLAlchemy查詢過濾器

過濾器 說明
filter() 把過濾器新增到原查詢上,返回一個新查詢
filter_by() 把等值過濾器新增到原查詢上,返回一個新查詢
limit() 使用指定的值限定原查詢返回的結果
offset() 偏移原查詢返回的結果,返回一個新查詢
order_by() 根據指定條件對原查詢結果進行排序,返回一個新查詢
group_by() 根據指定條件對原查詢結果進行分組,返回一個新查詢

常用的SQLAlchemy查詢結果的方法

方法 說明
all() 以列表形式返回查詢的所有結果
first() 返回查詢的第一個結果,如果未查到,返回None
first_or_404() 返回查詢的第一個結果,如果未查到,返回404
get() 返回指定主鍵對應的行,如不存在,返回None
get_or_404() 返回指定主鍵對應的行,如不存在,返回404
count() 返回查詢結果的數量
paginate() 返回一個Paginate物件,它包含指定範圍內的結果

建立和刪除表

建立表

db.create_all()  # 注意,create_all()方法執行的時候,需要放在模型的後面
# 上面這段語句,後面我們需要轉移程式碼到flask-script的自定義命令中。
# 執行了一次以後,需要註釋掉。

刪除表

db.drop_all()

資料操作

新增一條資料

student1 = Student(name='xiaoming')
db.session.add(student1)
db.session.commit()
#再次插入一條資料
student2 = Role(name='xiaohong')
db.session.add(student2)
db.session.commit()

一次插入多條資料

st1 = Student(name='wang',email='wang@163.com',age=22)
st2 = Student(name='zhang',email='zhang@189.com',age=22)
st3 = Student(name='chen',email='chen@126.com',age=22)
st4 = Student(name='zhou',email='zhou@163.com',age=22)
st5 = Student(name='tang',email='tang@163.com',age=22)
st6 = Student(name='wu',email='wu@gmail.com',age=22)
st7 = Student(name='qian',email='qian@gmail.com',age=22)
st8 = Student(name='liu',email='liu@163.com',age=22)
st9 = Student(name='li',email='li@163.com',age=22)
st10 = Student(name='sun',email='sun@163.com',age=22)
db.session.add_all([st1,st2,st3,st4,st5,st6,st7,st8,st9,st10])
db.session.commit()

filter_by精確查詢

例如:返回名字等於wang的所有人

Student.query.filter_by(name='xiaoming').all()

first()返回查詢到的第一個物件【first獲取一條資料,all獲取多條資料】

Student.query.first()

all()返回查詢到的所有物件

Student.query.all()

filter模糊查詢,返回名字結尾字元為g的所有資料。

Student.query.filter(Student.name.endswith('g')).all()

get():引數為主鍵,如果主鍵不存在沒有返回內容

Student.query.get()

邏輯非,返回名字不等於wang的所有資料

Student.query.filter(Student.name!='wang').all()

not_ 相當於取反

from sqlalchemy import not_
Student.query.filter(not_(Student.name=='wang')).all()

邏輯與,需要匯入and,返回and()條件滿足的所有資料

from sqlalchemy import and_
Student.query.filter(and_(Student.name!='wang',Student.email.endswith('163.com'))).all()

邏輯或,需要匯入or_

from sqlalchemy import or_
Student.query.filter(or_(Student.name!='wang',Student.email.endswith('163.com'))).all()

查詢資料後刪除

student = Student.query.first()
db.session.delete(student)
db.session.commit()

更新資料

student = Student.query.first()
student.name = 'dong'
db.session.commit()

關聯查詢

假設:老師和課程的關係是一對多的關係,一個老師可以授課多個課程,一個課程只由一個老師授課。

  • 查詢老師授課的所有課程
#查詢講師表id為1的老師
teacher = Teacher.query.get(1)
#查詢當前老師的所有課程, 根據模型中關聯關係來查詢資料
print(teacher.courses)
  • 查詢課程所屬講師
course = Course.query.get(2)

print(course)

# 根據外來鍵只能查詢到ID數值, SQLAlchemy不會幫我們把ID轉換成模型
print( course.teacher_id )

# 要獲取外來鍵對應的模型資料,需要找到主鍵模型裡面的  db.relationship 裡面的 backref
print( course.teacher.name )

資料庫遷移

  • 在開發過程中,需要修改資料庫模型,而且還要在修改之後更新資料庫。最直接的方式就是刪除舊錶,但這樣會丟失資料。
  • 更好的解決辦法是使用資料庫遷移框架,它可以追蹤資料庫模式的變化,然後把變動應用到資料庫中。
  • 在Flask中可以使用Flask-Migrate擴充套件,來實現資料遷移。並且整合到Flask-Script中,所有操作通過命令就能完成。
  • 為了匯出資料庫遷移命令,Flask-Migrate提供了一個MigrateCommand類,可以附加到flask-script的manager物件上。

首先要在虛擬環境中安裝Flask-Migrate。

pip install flask-migrate

程式碼檔案內容:

from flask import Flask
from config import Config
from flask_migrate import Migrate,MigrateCommand
from flask_script import Manager,Command

app = Flask(__name__,template_folder='templates')
app.config.from_object(Config)

manage = Manager(app)

"""模型的建立"""
from flask_sqlalchemy import SQLAlchemy
db = SQLAlchemy(app)



#第一個引數是Flask的例項,第二個引數是Sqlalchemy資料庫例項
migrate = Migrate(app,db)

#manager是Flask-Script的例項,這條語句在flask-Script中新增一個db命令
manage.add_command('db',MigrateCommand)

# 多對多的關係
# 關係表的宣告方式
achieve = db.Table('tb_achievement',
    db.Column('student_id', db.Integer, db.ForeignKey('tb_student.id')),
    db.Column('course_id', db.Integer, db.ForeignKey('tb_course.id'))
)


class Course(db.Model):
    # 定義表名
    __tablename__ = 'tb_course'
    # 定義欄位物件
    id = db.Column(db.Integer, primary_key=True)
    name = db.Column(db.String(64), unique=True)
    price = db.Column(db.Numeric(6,2))
    teacher_id = db.Column(db.Integer, db.ForeignKey('tb_teacher.id'))
    students = db.relationship('Student', secondary=achieve, backref='courses', lazy='subquery')
    # repr()方法類似於django的__str__,用於列印模型物件時顯示的字串資訊
    def __repr__(self):
        return 'Course:%s'% self.name

class Student(db.Model):
    __tablename__ = 'tb_student'
    id = db.Column(db.Integer, primary_key=True)
    name = db.Column(db.String(64), unique=True)
    email = db.Column(db.String(64),unique=True)
    age = db.Column(db.SmallInteger,nullable=False)
    sex = db.Column(db.Boolean,default=1)

    def __repr__(self):
        return 'Student:%s' % self.name

class Teacher(db.Model):
    __tablename__ = 'tb_teacher'
    id = db.Column(db.Integer, primary_key=True)
    name = db.Column(db.String(64), unique=True)
    # 課程與老師之間的多對一關聯
    courses = db.relationship('Course', backref='teacher', lazy='subquery')

    def __repr__(self):
        return 'Teacher:%s' % self.name


@app.route("/")
def index():
    return "ok"

if __name__ == '__main__':
    manage.run()
建立遷移版本倉庫
#這個命令會建立migrations資料夾,所有遷移檔案都放在裡面。
python main.py db init
建立遷移版本
  • 自動建立遷移版本有兩個函式
    • upgrade():函式把遷移中的改動應用到資料庫中。
    • downgrade():函式則將改動刪除。
  • 自動建立的遷移指令碼會根據模型定義和資料庫當前狀態的差異,生成upgrade()和downgrade()函式的內容。
  • 對比不一定完全正確,有可能會遺漏一些細節,需要進行檢查
python main.py db migrate -m 'initial migration'

# 這裡等同於django裡面的 makemigrations,生成遷移版本檔案
升級版本庫的版本
python main.py db upgrade 
降級版本庫的版本
python main.py db downgrade

版本庫的歷史管理

可以根據history命令找到版本號,然後傳給downgrade命令:

python manage.py db history

輸出格式:<base> ->  版本號 (head), initial migration

回滾到指定版本

python manage.py db downgrade # 預設返回上一個版本
python manage.py db downgrade 版本號   # 返回到指定版本號對應的版本

資料遷移的步驟:

1. 初始化資料遷移的目錄
python manage.py db init

2. 資料庫的資料遷移版本初始化
python manage.py db migrate -m 'initial migration'

3. 升級版本[建立表/建立欄位/修改欄位]
python manage.py db upgrade 

4. 降級版本[刪除表/刪除欄位/恢復欄位]
python manage.py db downgrade

模組推薦

文件: https://faker.readthedocs.io/en/master/locales/zh_CN.html

github: https://github.com/joke2k/faker

flask-session

允許設定session到指定儲存的空間中, 文件:

安裝命令: https://pythonhosted.org/Flask-Session/

pip install flask-Session

使用session之前,必須配置一下配置項:

SECRET_KEY = "*(%#4sxcz(^(#$#8423" # session祕鑰

redis儲存session的基本配置

配置檔案資訊:

import redis
class Config(object):
    DEBUG = True
    SECRET_KEY = "*(%#4sxcz(^(#$#8423"
    # 資料庫連結配置:
    #資料型別://登入賬號:登入密碼@資料庫主機IP:資料庫訪問埠/資料庫名稱
    SQLALCHEMY_DATABASE_URI = "mysql://root:123@127.0.0.1:3306/flask_students"
    # 設定mysql的錯誤跟蹤資訊顯示
    SQLALCHEMY_TRACK_MODIFICATIONS = True
    # 列印每次模型操作對應的SQL語句
    SQLALCHEMY_ECHO = True
    # 把session儲存到redis中
    # session儲存方式為redis
    SESSION_TYPE="redis"
    # 如果設定session的生命週期是否是會話期, 為True,則關閉瀏覽器session就失效
    SESSION_PERMANENT = False
    # 是否對傳送到瀏覽器上session的cookie值進行加密
    SESSION_USE_SIGNER = False
    # 儲存到redis的session數的名稱字首
    SESSION_KEY_PREFIX = "session:"
    # session儲存資料到redis時啟用的連結物件
    SESSION_REDIS = redis.Redis(host='127.0.0.1', port='6379')  # 用於連線redis的配置

主檔案資訊main.py,程式碼:

from flask import Flask
from config import Config
from flask_session import Session
from flask import session
app = Flask(__name__,template_folder='templates')
app.config.from_object(Config)

Session(app)

@app.route("/")
def index():
    return "ok"

@app.route("/set_session")
def set_session():
    """設定session"""
    session["username"] = "小明"
    return "ok"

if __name__ == '__main__':
    app.run()

SQLAlchemy儲存session的基本配置

需要手動建立session表,在專案第一次啟動的時候,使用db.create_all()來完成建立。

db = SQLAlchemy(app)

app.config['SESSION_TYPE'] = 'sqlalchemy'  # session型別為sqlalchemy
app.config['SESSION_SQLALCHEMY'] = db # SQLAlchemy物件
app.config['SESSION_SQLALCHEMY_TABLE'] = 'session' # session要儲存的表名稱
app.config['SESSION_PERMANENT'] = True  # 如果設定為True,則關閉瀏覽器session就失效。
app.config['SESSION_USE_SIGNER'] = False  # 是否對傳送到瀏覽器上session的cookie值進行加密
app.config['SESSION_KEY_PREFIX'] = 'session:'  # 儲存到session中的值的字首

Session(app)

相關文章