数据库操作
数据库驱动(drivers)模块:pymysql、MySQLDB
数据库基本操作
- 在SQLAlchemy中,添加、修改、删除操作,均由数据库会话(sessionSM)管理。
- 会话用 db.session 表示。在准备把数据写入数据库前,要先将数据添加到会话中然后调用 db.commit() 方法提交会话。
- 在SQLAlchemy 中,查询操作是通过 query 对象操作数据。
- 最基本的查询是返回表中所有数据,也可以通过filter过滤器进行更精确的数据库查询。
模型类定义
我们后面会把模型创建到单独的文件中,但是现在我们先把模型类写在main.py文件中。
python
from flask import Flask
from flask_sqlalchemy import SQLAlchemy
app = 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 = True
app.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()
数据表操作
创建和删除表
创建表
python
# 在视图内调用:
@app.route("/create")
def create_table():
db.create_all() # 为项目中被识别的所有模型创建数据表
return "ok"
# 在视图以外的地方调用:
with app.app_context():
# create_all()方法执行的时候,需要放在模型的后面
# 检测数据库中是否存在和模型匹配的数据表。
# 如果没有,则根据模型转换的建表语句进行建表。
# 如果找到,则不会进行额外处理
db.create_all()
删除表
python
# 在视图内调用:
@app.route("/drop")
def drop_table():
db.drop_all() # 为项目中被识别的所有模型删除数据表
return "ok"
# 在视图以外的地方调用:
with app.app_context():
db.drop_all() # 慎用,很给力的!!这表示删除数据库中所有模型对应的表。
代码:
python
from flask import Flask, render_template
from flask_sqlalchemy import SQLAlchemy
db = 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()
数据操作
添加一条数据
python
# 添加一条数据
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() # 提交会话
一次插入多条数据
python
# 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()
python
# 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()
删除数据
python
# 方法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; 提交事务
"""
更新数据
python
from flask import Flask, render_template
from flask_sqlalchemy import SQLAlchemy
db = 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函数的效果]
# 每次自增100
Student.query.filter(Student.name == "小花").update({"money": Student.money + 100})
db.session.commit()
return "ok"
@app.route("/update4")
def update4():
# 字段引用[利用当前一条数据的字典值进行辅助操作,实现类似django里面F函数的效果]
# 在原有money的基础上按age补贴1000*age
Student.query.filter(Student.name == "zhang").update({"money": Student.money + 1000 * Student.age})
db.session.commit()
return "ok"
if __name__ == '__main__':
app.run()