近来,重新尝试回 MySQL,说实话,工作以来还没有将 MySQL 正式用在真实环境中,最开始是使用的 Oracle,虽然同是 Relation Database,但是,两者之间的区别还是很大的,例如触发器和存储过程,所以这次也算是一个尝试,而在 Python
中,MySQL
的
ORM 最热门的应该当属 SQLAlchemy 了,然后因为在 Flask
中使用,自然而然就选择了 Flask-SQLAlchemy
。下面就讲解一下如何使用 Flask-SQLAlchemy
。
安装
安装依旧是老套路,使用 pip 安装,但是,这里需要注意到是,和 MySQL
配合还需要安装 MySQL 的驱动,一般是 MySQL-Python
,所以整个安装是:
pip install flask-sqlalchemy
pip install MySQL-python
也许你在安装 MySQL-python
的时候会报错,解决方式有很多,我用的是最简单粗暴的,就是安装 mysql 的客户端开发库和服务端:
apt-get install python-dev libmysqlclient-dev
apt-get install mysqld
这应该就可以了,但是这是 Ubuntu 系的解决方法,其他系的可以使用相应的包管理工具安装。
使用
在 Flask
中使用 SQLAlchemy
还是老套路了,先创建 SQLAlchemy
的对象,然后再绑定 Flask
,那么代码是这样:
from flask.ext.sqlalchemy import SQLAlchemy
app = Flask(__name)
db = SQLAlchemy()
db.init_app(app)
也就这么简单,但是,这样的话 SQLAlchemy
并不知道我们要使用的数据库是什么,并且怎么连接哪个数据库,所以,我们需要指定配置,并且设置方言,为什么要设置方言,那是因为 MySQL
的驱动库那么多,而且还不一样,所以我们需要告诉 SQLAlchemy 我们使用的是哪种,要怎么连接操作数据。 因为我们使用的是 MySQL-python
,所以配置可以这么写:
app.config['SQLALCHEMY_DATABASE_URI'] = "mysql+mysqldb://<user>:<password>@<host>[:<port>]/<dbname>"
对于其他方言,我们可以参考 《SQLAlchemy手册》。
定义模型
使用 ORM 就肯定要定义对象的,使用 SQLAlchemy 定义对象有一点不自然,但勉强可以接受,下面就定义一个 Todo 的对象。
class User(db.Model):
__tablename__ = 'users'
id = db.Column(db.Integer, primary_key=True)
username = db.Column(db.String(64), unique=True, index=True)
class Todo(db.Model):
__tablename__ = 'todos'
id = db.Column(db.Integer, primary_key=True)
content = db.Column(db.String(256))
从这两个例子可以看到,定义一列是需要使用 db.Column
的,然后需要指定数据类型,还有列的选项,常用的数据类型有:
类型名 | Python 类型 | 说明 |
---|---|---|
Integer | int | 普通整数,一般是32位 |
SmallInteger | int | 取值范围小的整数,一般是 16 位 |
BigInteger | int 或 long | 不限制精度的整数 |
Float | float | 浮点数 |
Numeric | decimal.Decimal | 定点数 |
String | str | 变长字符串 |
Text | str | 编程字符串,对较长或不限长度的字符串做了优化 |
Unicode | unicode | 变长 Unicode 字符串 |
UnicodeText | unicode | 变长 Unicode 字符串,对较长或不限长度的字符串做了优化 |
Boolean | bool | 布尔值 |
Date | datetime.date | 日期 |
Time | datetime.time | 时间 |
DateTime | datetime.datetime | 日期和时间 |
Interval | datetime.timedelta | 时间间隔 |
Enum | str | 一组字符串 |
PickleType | 任何 python 对象 | 自动使用 Pickle 序列化 |
LargeBinary | str | 二进制文件 |
而一些常用的 SQLAlchemy
列选项: 选项名 | 说明 |
---|---|
primary_key | 如果设为 True,这列就是表的主键 |
unique | 如果设为 True,这列不允许出现重复的值 |
index | 如果设为 True,为这列创建索引,提升查询效率 |
nullable | 如果设为 True,这列允许使用空值,如果设为 False,这列不允许使用空值 |
default | 为这列定义默认值 |
关联
我们知道 Todo 和 User 是有关系的,也就是说,我们每个 Todo 都应该有一个执行者,所以我们应该修改一下 Models,变成:
class User(db.Model):
__tablename__ = 'users'
id = db.Column(db.Integer, primary_key=True)
username = db.Column(db.String(64), unique=True, index=True)
todos = db.relationship('Todo', backref='user')
class Todo(db.Model):
__tablename__ = 'todos'
id = db.Column(db.Integer, primary_key=True)
content = db.Column(db.String(256))
user_id = db.Column(db.Integer, db.ForeignKey('user.id'))
这里的关系是这样的:
- 一个用户有多个待办事项
- 一个待办事项只能有一个用户
数据库操作
我们使用数据库的操作不外乎就是增删改查,所以这里就介绍一下增删改查怎么做,和其他 ORM(例如 MongoEngine
) 不一样,SQLAlchemy
使用之前需要先创建表和数据库才能操作数据。 所以第一步是创建表:
$ python app.py shell
>>> from app import db
>>> db.create_all()
插入数据
from app import db, Todo, User
user = User(username="zhangsan")
todo1 = Todo(content="增加数据", user=user)
todo2 = Todo(content="删除数据", user=user)
# 下面这个很重要
db.session.add(user)
db.session.add(todo1)
db.session.add(todo2)
db.session.commit()
这里需要提一下的就是 db.session
,这个 session
是数据库中 事务 的含义,因此我们可以提交修改数据,也可以回滚取消修改。
修改数据
user = User(username="lisi")
todo1.user = user
db.session.add(user)
db.session.add(todo1)
db.session.commit()
修改只需要修改属性并提交就行了。
删除数据
db.session.delete(todo2)
db.session.commit()
删除数据直接调用 delete 方法即可,记得 commit。
查询数据 查询数据稍微复杂一下
- 获得查询对象
- 添加过滤条件
- 如果还有其他分组之类的,继续添加
# 一个简单的查询,查询所有待办事项
Todo.query.all()
# 查询 id 为 1 的用户的待办事项
Todo.query.filter_by(user_id=1)
这里的 filter_by 叫做过滤器,常用的过滤器有
过滤器 | 说明 |
---|---|
filter | 把过滤器添加到原查询上,返回一个新查询 |
filter_by | 把等值过滤器添加到原查询上,返回一个新查询 |
limit | 使用指定的值限制返回的结果数量,返回一个新查询 |
offset | 便宜原查询返回的结果, 返回一个新查询 |
order_by | 根据指定条件对原查询结果进行排序,返回一个新查询 |
group_by | 根据指定条件对原查询结构进行分组,返回一个新查询 |
如果后面不加.all(),那么其实是不会真正到数据库执行的,类似 all 的执行器还有很多,常用的有:
方法 | 说明 |
---|---|
all | 以列表形式返回查询的所有结果 |
first | 返回查询的第一个结果,如果没有结果,则返回 None |
first_or_404 | 返回查询的第一个结果,如果没有结果,则终止请求,返回 404 错误输出 |
get | 返回指定主键对应的行,如果没有对应的行,则返回 None |
get_or_404 | 返回指定主键对应的行,如果没找到指定的主键,则终止请求,返回 404 错误输出 |
count | 返回查询结果的数量 |
paginate | 返回一个 Paginate 对象,它包含指定范围内的结果 |
想学习更多关于查询的知识,可以查看 《SQLAlchemy QueryAPI》
使用 Flask-Migrate 迁移数据库
使用 MySQL 一个很不习惯的地方就是添加字段很麻烦,如果有事设计之初没有考虑好,当需要加一个字段的时候,就显得很被动。还是,这个痛点很多人都有,因此 Flask-Migrate
就来了。 添加 Flask-Migrate
可以这样:
from flask.ext.migrate import Migrate, MigrateCommand
migrate = Migrate()
migrate.init_app(app, db)
manager.add_command('db', MigrateCommand)
然后迁移的时候执行的命令是:
python app.py db init
python app.py db migrate
python app.py db upgrade
这里的 init 只需要执行一次,以后的修改都不用执行了。