数据库操作
数据库驱动(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()