數據庫操作
數據庫驅動(drivers)模塊:pymysql、MySQLDB
數據庫基本操作
- 在SQLAlchemy中,添加、修改、刪除操作,均由數據庫會話(sessionSM)管理。
- 會話用 db.session 表示。在準備把數據寫入數據庫前,要先將數據添加到會話中然后調用 db.commit() 方法提交會話。
- 在SQLAlchemy 中,查詢操作是通過 query 對象操作數據。
- 最基本的查詢是返回表中所有數據,也可以通過filter過濾器進行更精確的數據庫查詢。
模型類定義
我們后面會把模型創建到單獨的文件中,但是現在我們先把模型類寫在main.py文件中。
from flask import Flask
from flask_sqlalchemy import SQLAlchemyapp = Flask(__name__)
class Config(object):DEBUG = True# 數據庫鏈接配置 = 數據庫名稱://登錄賬號:登錄密碼@數據庫主機IP:數據庫訪問端口/數據庫名稱?charset=編碼類型SQLALCHEMY_DATABASE_URI = "mysql://root:123@127.0.0.1:3306/flaskdemo?charset=utf8mb4"# 動態追蹤修改設置,如未設置只會提示警告SQLALCHEMY_TRACK_MODIFICATIONS = False# ORM運行時會顯示ORM生成的原始SQL語句[調試]SQLALCHEMY_ECHO = Trueapp.config.from_object(Config)"""模型類定義"""
db = SQLAlchemy(app=app)
# 等同于
# db = SQLAlchemy()
# db.init_app(app) # 加載配置并完成初始化過程class Student(db.Model):"""學生信息模型"""# 聲明與當前模型綁定的數據表名稱__tablename__ = "tb_student""""# 企業中,往往大部分的公司會出現以下2種不同的數據庫開發情況。# 1. 企業中有DBA,DBA會提前創建數據庫和項目中具體業務的數據表。也就是說我們不需要自己手動建庫建表,只需要根據數據庫表結構,使用python聲明對應的模型與之匹配,就可以操作數據庫了。# 2. 企業沒有DBA,比較坑爹:# 2.1 開發人員,自己手擼SQL語句,手動建庫建表。# 2.2 開發人員,編寫模型,使用數據遷移,手動建庫和數據遷移建表。# 原生SQL語句create table db_student(id int primary key auto_increment comment "主鍵",name varchar(15) comment "姓名",age smallint comment "年齡",sex tinyint default 1 comment "性別",email varchar(128) comment "郵箱地址",money NUMERIC(10,2) default 0.0 comment "錢包",key (name),unique key (email));# 字段根據SQL語句來聲明"""id = db.Column(db.Integer, primary_key=True,comment="主鍵")name = db.Column(db.String(15), index=True, comment="姓名")age = db.Column(db.SmallInteger, comment="年齡")sex = db.Column(db.Boolean, default=True, comment="性別")email = db.Column(db.String(128), unique=True, comment="郵箱地址")money = db.Column(db.Numeric(10,2), default=0.0, comment="錢包")def __repr__(self): # 相當于django的__str__return f"{self.name}<{self.__class__.__name__}>"# 所有的模型必須直接或間接繼承于db.Model
class Course(db.Model):"""課程數據模型"""__tablename__ = "db_course""""# 原生SQL語句create table db_course (id int primary key auto_increment comment "主鍵",name varchar(64) comment "課程",price NUMERIC(7,2) comment "價格",unique (name));# 字段根據SQL語句來聲明"""id = db.Column(db.Integer, primary_key=True, comment="主鍵")name = db.Column(db.String(64), unique=True, comment="課程")price = db.Column(db.Numeric(7, 2), comment="價格")# repr()方法類似于django的__str__,用于打印模型對象時顯示的字符串信息def __repr__(self):return f"{self.name}<{self.__class__.__name__}>"class Teacher(db.Model):"""老師數據模型"""__tablename__ = "db_teacher""""# 原生SQL語句create table db_teacher (id int primary key auto_increment comment "主鍵",name varchar(64) comment "姓名",option enum("講師", "助教", "班主任") comment "職位",unique (`name`));# 字段根據SQL語句來聲明"""id = db.Column(db.Integer, primary_key=True, comment="主鍵")name = db.Column(db.String(64), unique=True, comment="姓名")option = db.Column(db.Enum("講師", "助教", "班主任"), default="講師")def __repr__(self):return f"{self.name}<{self.__class__.__name__}>"@app.route("/")
def index():return "ok"if __name__ == '__main__':app.run()
數據表操作
創建和刪除表
創建表
# 在視圖內調用:
@app.route("/create")
def create_table():db.create_all() # 為項目中被識別的所有模型創建數據表return "ok"# 在視圖以外的地方調用:with app.app_context():# create_all()方法執行的時候,需要放在模型的后面# 檢測數據庫中是否存在和模型匹配的數據表。# 如果沒有,則根據模型轉換的建表語句進行建表。# 如果找到,則不會進行額外處理db.create_all()
刪除表
# 在視圖內調用:
@app.route("/drop")
def drop_table():db.drop_all() # 為項目中被識別的所有模型刪除數據表return "ok"# 在視圖以外的地方調用:with app.app_context():db.drop_all() # 慎用,很給力的!!這表示刪除數據庫中所有模型對應的表。
代碼:
from flask import Flask, render_template
from flask_sqlalchemy import SQLAlchemydb = SQLAlchemy()
app = Flask(__name__, template_folder="templates", static_folder="static")# 配置
app.config.update({"DEBUG": True,"SQLALCHEMY_DATABASE_URI": "mysql://root:123@127.0.0.1:3306/flaskdemo?charset=utf8mb4",# 如果使用pymysql,則需要在連接時指定pymysql# "SQLALCHEMY_DATABASE_URI": "mysql+pymysql://root:123@127.0.0.1:3306/flaskdemo?charset=utf8mb4"# 動態追蹤修改設置,如未設置只會提示警告,設置False即可"SQLALCHEMY_TRACK_MODIFICATIONS": False,# ORM執行SQL查詢時是哦否顯示原始SQL語句,debug模式下可以開啟"SQLALCHEMY_ECHO": True,
})db.init_app(app)class Student(db.Model):"""學生管理"""__tablename__ = "db_student" # 表名# __abstract__ = True # 抽象模型,數據遷移/建表的時候,不會認為這是一個模型,也就不會建表,往往用于設置公共模型,保存公共字段"""# 企業中,往往大部分的公司會出現以下2種不同的數據庫開發情況。# 1. 企業中有DBA,DBA會提前創建數據庫和項目中具體業務的數據表。也就是說我們不需要自己手動建庫建表,只需要根據數據庫表結構,使用python聲明對應的模型與之匹配,就可以操作數據庫了。# 2. 企業沒有DBA,比較坑爹:# 2.1 開發人員,自己手擼SQL語句,手動建庫建表。# 2.2 開發人員,編寫模型,使用數據遷移或者ORM提供建表方法,手動建庫和數據遷移建表。# 原生SQL語句create table db_student(id int primary key auto_increment comment "主鍵",name varchar(15) comment "姓名",age smallint comment "年齡",sex tinyint(1) default 1 comment "性別",email varchar(255) comment "郵箱地址",money NUMERIC(10,2) default 0.0 comment "錢包",key (name),unique key (email));# 字段根據SQL語句來聲明"""# 屬性名 = db.Column(字段類型, 字段列約束選項)# 如果SQL語句中的字段名在python中是關鍵字/保留字,則建議改寫綁定字段名# 屬性名 = db.Column("字段名", 字段類型, 字段列約束選項)id = db.Column("student_id", db.Integer, primary_key=True, comment="主鍵")name = db.Column(db.String(15), index=True, comment="姓名")age = db.Column(db.SmallInteger, comment="年齡")sex = db.Column(db.SmallInteger, comment="性別")email = db.Column(db.String(255), unique=True, comment="郵箱地址")money = db.Column(db.Numeric(10,2), default=0.0, comment="錢包")# repr()方法類似于django的__str__,用于打印模型對象時顯示的字符串信息def __repr__(self):return f"{self.name}<{self.__class__.__name__}>"# 所有的模型必須直接或間接繼承于db.Model
class Course(db.Model):"""課程數據模型"""__tablename__ = "db_course""""# 原生SQL語句create table db_course (id int primary key auto_increment comment "主鍵",name varchar(64) comment "課程",price NUMERIC(7,2) comment "價格",unique (name));# 字段根據SQL語句來聲明"""id = db.Column(db.Integer, primary_key=True, comment="主鍵")name = db.Column(db.String(64), unique=True, comment="課程")price = db.Column(db.Numeric(7, 2), comment="價格")# repr()方法類似于django的__str__,用于打印模型對象時顯示的字符串信息def __repr__(self):return f"{self.name}<{self.__class__.__name__}>"class Teacher(db.Model):"""老師數據模型"""__tablename__ = "db_teacher""""# 原生SQL語句create table db_teacher (id int primary key auto_increment comment "主鍵",name varchar(64) comment "姓名",option enum("講師", "助教", "班主任") comment "職位",unique (`name`));# 字段根據SQL語句來聲明"""id = db.Column(db.Integer, primary_key=True, comment="主鍵")name = db.Column(db.String(64), unique=True, comment="姓名")option = db.Column(db.Enum("講師", "助教", "班主任"), default="講師")def __repr__(self):return f"{self.name}<{self.__class__.__name__}>"@app.route("/")
def index():title = "網站首頁"return render_template("index.html", **locals())@app.route("/create")
def create_table():db.create_all() # 為項目中被識別的所有模型創建數據表return "ok"@app.route("/drop")
def drop_table():db.drop_all() # 為項目中被識別的所有模型刪除數據表return "ok"if __name__ == '__main__':app.run()
數據操作
添加一條數據
# 添加一條數據
student = Student(name="小明", age=17, email="xiaoming@qq.com", money=100) # 實例化模型對象
db.session.add(student) # 把模型對象添加數據庫session會話對象中。db.session是SQLAlchemy中內置的會話管理對象sessionSM的成員
db.session.commit() # 提交會話# 再次插入一條數據
student2 = Student(name='小紅', sex=False, age=13, email="16565666@qq.com", money=600)
db.session.add(student2)
db.session.commit() # 提交會話
一次插入多條數據
# 1. 先實例化要創建的模型對象
student = Student(name="小紅", age=17, email="xiaohong@qq.com", money=200)
# 2. 把實例對象添加到連接會話
db.session.add(student)
# 1. 先實例化要創建的模型對象
student = Student(name="小花", age=16, email="xiaohua@qq.com", money=200)
# 2. 把實例對象添加到連接會話
db.session.add(student)
# 3. 只需要在結束的時候提交事務即可
db.session.commit()
# 1. 先創建的列表要添加的實例化模型對象列表
student_list = [Student(name='wang', email='wang@163.com', age=20),Student(name='zhang', email='zhang@189.com', age=21),Student(name='chen', email='chen@126.com', age=19),Student(name='zhou', email='zhou@163.com', age=18),Student(name='tang', email='tang@163.com', age=16),Student(name='wu', email='wu@gmail.com', age=20),Student(name='qian', email='qian@gmail.com', age=21),Student(name='liu', email='liu@163.com', age=21),Student(name='li', email='li@163.com', age=18),Student(name='sun', email='sun@163.com', age=17),
]# 2. 一次性添加到連接會話中
db.session.add_all(student_list)
db.session.commit()
刪除數據
# 方法1[先查詢后刪除,2條語句完成刪除操作]
# 先查詢出來
student = Student.query.first()
print(student)
# 再進行刪除
db.session.delete(student)
db.session.commit()# 方法2【1條語句完成刪除操作,性能更好更高效】
# 類似樂觀鎖,在數據改動時添加條件并判斷條件成立以后才進行數據操作,這種用法就是樂觀鎖
Student.query.filter(Student.id == 5).delete()
db.session.commit()"""
悲觀鎖,是屬于數據庫中的一種鎖機制,但是樂觀鎖并非真正的數據庫鎖。
2種鎖都是數據庫在應對并發操作時,防止出現資源搶奪的,基于不同人生觀所實現2種解決方案。
悲觀鎖的基本使用:>>> 數據庫終端開始begin; -- 開啟事務select * from db_student where student_id = 5 for update; -- 添加一把更新鎖【悲觀鎖】.... -- 在事務提交之前,任何第三方連接都不能修改 student_id = 5這條數據 commit; -- 提交事務<<< 數據庫終端開始悲觀鎖的問題:
1. 提前鎖定數據,形成串行化,形成阻塞,不利于性能發揮,不適用高并發場景。
2. 悲觀鎖只能保證數據的一致性,不能保證臟數據的出現樂觀鎖的出現就是為了解決悲觀鎖的問題。
舉例:雙11活動,商城里面id=5的商品的庫存=10了,現在我們要基于樂觀鎖和悲觀鎖來解決下單過程中,出現的資源搶奪現象,避免出現超賣(商品數量不能為負數)。樂觀鎖:
---> begin; 開啟事務
---> 先查看庫存,記錄當前庫存 num=10
---> 進行下單操作,買6件
---> 付款
---> 扣除庫存 update goods set num=num-6 where num=10 and id=5; # 增加更新條件,判斷庫存是否還是原來
---> 如果執行成功,則表示沒有人搶,購買成功如果執行事變,則表示已經有人先搶購
---> commit;悲觀鎖:
---> begin; 開啟事務
---> 先給id=5的數據,加鎖select * from goods where id=5 for update;
---> 進行下單操作,買6件
---> 付款
---> 扣除庫存 update goods set num=num-6 where id=5
---> 執行成功解鎖
---- commit; 提交事務
"""
更新數據
from flask import Flask, render_template
from flask_sqlalchemy import SQLAlchemydb = SQLAlchemy()
app = Flask(__name__, template_folder="templates", static_folder="static")# 配置
app.config.update({"DEBUG": True,"SQLALCHEMY_DATABASE_URI": "mysql://root:123@127.0.0.1:3306/flaskdemo?charset=utf8mb4",# 如果使用pymysql,則需要在連接時指定pymysql# "SQLALCHEMY_DATABASE_URI": "mysql+pymysql://root:123@127.0.0.1:3306/flaskdemo?charset=utf8mb4"# 動態追蹤修改設置,如未設置只會提示警告,設置False即可"SQLALCHEMY_TRACK_MODIFICATIONS": False,# ORM執行SQL查詢時是哦否顯示原始SQL語句,debug模式下可以開啟"SQLALCHEMY_ECHO": True,
})db.init_app(app)class Student(db.Model):"""學生管理"""__tablename__ = "db_student" # 表名# 屬性名 = db.Column(字段類型, 字段列約束選項)# 如果SQL語句中的字段名在python中是關鍵字/保留字,則建議改寫綁定字段名# 屬性名 = db.Column("字段名", 字段類型, 字段列約束選項)id = db.Column("student_id", db.Integer, primary_key=True, comment="主鍵")name = db.Column(db.String(15), index=True, comment="姓名")age = db.Column(db.SmallInteger, comment="年齡")sex = db.Column(db.SmallInteger, comment="性別")email = db.Column(db.String(255), unique=True, comment="郵箱地址")money = db.Column(db.Numeric(10,2), default=0.0, comment="錢包")# repr()方法類似于django的__str__,用于打印模型對象時顯示的字符串信息def __repr__(self):return f"{self.name}<{self.__class__.__name__}>"# 所有的模型必須直接或間接繼承于db.Model
class Course(db.Model):"""課程數據模型"""__tablename__ = "db_course""""# 原生SQL語句create table db_course (id int primary key auto_increment comment "主鍵",name varchar(64) comment "課程",price NUMERIC(7,2) comment "價格",unique (name));# 字段根據SQL語句來聲明"""id = db.Column(db.Integer, primary_key=True, comment="主鍵")name = db.Column(db.String(64), unique=True, comment="課程")price = db.Column(db.Numeric(7, 2), comment="價格")# repr()方法類似于django的__str__,用于打印模型對象時顯示的字符串信息def __repr__(self):return f"{self.name}<{self.__class__.__name__}>"class Teacher(db.Model):"""老師數據模型"""__tablename__ = "db_teacher""""# 原生SQL語句create table db_teacher (id int primary key auto_increment comment "主鍵",name varchar(64) comment "姓名",option enum("講師", "助教", "班主任") comment "職位",unique (`name`));# 字段根據SQL語句來聲明"""id = db.Column(db.Integer, primary_key=True, comment="主鍵")name = db.Column(db.String(64), unique=True, comment="姓名")option = db.Column(db.Enum("講師", "助教", "班主任"), default="講師")def __repr__(self):return f"{self.name}<{self.__class__.__name__}>"@app.route("/")
def index():return "ok"@app.route("/create")
def create_table():db.create_all() # 為項目中被識別的所有模型創建數據表return "ok"@app.route("/drop")
def drop_table():db.drop_all() # 為項目中被識別的所有模型刪除數據表return "ok""""一次添加一條數據"""
@app.route("/add")
def add_student():# 1. 先實例化要創建的模型對象student = Student(name="小明", age=17, email="xiaoming@qq.com", money=100) # 實例化模型對象# 2. 把實例對象添加到連接會話db.session.add(student)# 3. 提交事務db.session.commit()return "ok""""一次添加多條數據"""
@app.route("/madd")
def multi_add():# 1. 先實例化要創建的模型對象student = Student(name="小紅", age=17, email="xiaohong@qq.com", money=200)# 2. 把實例對象添加到連接會話db.session.add(student)# 1. 先實例化要創建的模型對象student = Student(name="小花", age=16, email="xiaohua@qq.com", money=200)# 2. 把實例對象添加到連接會話db.session.add(student)# 3. 只需要在結束的時候提交事務即可db.session.commit()return "ok"@app.route("/madd2")
def multi_add2():# 1. 先創建的列表要添加的實例化模型對象列表student_list = [Student(name='wang', email='wang@163.com', age=20),Student(name='zhang', email='zhang@189.com', age=21),Student(name='chen', email='chen@126.com', age=19),Student(name='zhou', email='zhou@163.com', age=18),Student(name='tang', email='tang@163.com', age=16),Student(name='wu', email='wu@gmail.com', age=20),Student(name='qian', email='qian@gmail.com', age=21),Student(name='liu', email='liu@163.com', age=21),Student(name='li', email='li@163.com', age=18),Student(name='sun', email='sun@163.com', age=17),]# 2. 一次性添加到連接會話中db.session.add_all(student_list)db.session.commit()return "ok"@app.route("/del")
def delete_student():"""刪除一條數據"""# 先查詢出來student = Student.query.first()# student = db.session.query(Student).first()# 再進行刪除db.session.delete(student)db.session.commit()return "ok"@app.route("/mdel")
def multi_delete_student():"""按條件刪除多條數據"""Student.query.filter(Student.id > 5).delete()# db.session.query(Student).filter(Student.id > 5).delete()db.session.commit()return "ok"@app.route("/update")
def update():"""更新一條"""# 先查詢出來student = Student.query.filter(Student.id == 4).first()student.name = "小白"db.session.commit()return "ok"@app.route("/update2")
def update2():"""直接根據條件更新一條或多條數據"""Student.query.filter(Student.name == 'zhang', Student.money == -99.00).update({'money': 1998})db.session.commit()return "ok"@app.route("/update3")
def update3():# 字段引用[利用當前一條數據的字典值進行輔助操作,實現類似django里面F函數的效果]# 每次自增100Student.query.filter(Student.name == "小花").update({"money": Student.money + 100})db.session.commit()return "ok"@app.route("/update4")
def update4():# 字段引用[利用當前一條數據的字典值進行輔助操作,實現類似django里面F函數的效果]# 在原有money的基礎上按age補貼1000*ageStudent.query.filter(Student.name == "zhang").update({"money": Student.money + 1000 * Student.age})db.session.commit()return "ok"if __name__ == '__main__':app.run()