近来,重新尝试回 MySQL,说实话,工作以来还没有将 MySQL 正式用在真实环境中,最开始是使用的 Oracle,虽然同是 Relation Database,但是,两者之间的区别还是很大的,例如触发器和存储过程,所以这次也算是一个尝试,而在 Python 中,MySQL 的 ORM 最热门的应该当属 SQLAlchemy 了,然后因为在 Flask 中使用,自然而然就选择了 Flask-SQLAlchemy。下面就讲解一下如何使用 Flask-SQLAlchemy

安装

安装依旧是老套路,使用 pip 安装,但是,这里需要注意到是,和 MySQL 配合还需要安装 MySQL 的驱动,一般是 MySQL-Python,所以整个安装是:

1
2
pip install flask-sqlalchemy
pip install MySQL-python

也许你在安装 MySQL-python 的时候会报错,解决方式有很多,我用的是最简单粗暴的,就是安装 mysql 的客户端开发库和服务端:

1
2
apt-get install python-dev libmysqlclient-dev
apt-get install mysqld

这应该就可以了,但是这是 Ubuntu 系的解决方法,其他系的可以使用相应的包管理工具安装。

使用

Flask 中使用 SQLAlchemy 还是老套路了,先创建 SQLAlchemy 的对象,然后再绑定 Flask,那么代码是这样:

1
2
3
4
5
6
from flask.ext.sqlalchemy import SQLAlchemy

app = Flask(__name)
db = SQLAlchemy()

db.init_app(app)

也就这么简单,但是,这样的话 SQLAlchemy 并不知道我们要使用的数据库是什么,并且怎么连接哪个数据库,所以,我们需要指定配置,并且设置方言,为什么要设置方言,那是因为 MySQL 的驱动库那么多,而且还不一样,所以我们需要告诉 SQLAlchemy 我们使用的是哪种,要怎么连接操作数据。 因为我们使用的是 MySQL-python,所以配置可以这么写:

1
app.config['SQLALCHEMY_DATABASE_URI'] = "mysql+mysqldb://<user>:<password>@<host>[:<port>]/<dbname>"

对于其他方言,我们可以参考 《SQLAlchemy手册》

定义模型

使用 ORM 就肯定要定义对象的,使用 SQLAlchemy 定义对象有一点不自然,但勉强可以接受,下面就定义一个 Todo 的对象。

1
2
3
4
5
6
7
8
9
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,变成:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
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 使用之前需要先创建表和数据库才能操作数据。 所以第一步是创建表

1
2
3
$ python app.py shell
    >>> from app import db
    >>> db.create_all()

插入数据

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
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 是数据库中 事务 的含义,因此我们可以提交修改数据,也可以回滚取消修改。

修改数据

1
2
3
4
5
user = User(username="lisi")
todo1.user = user
db.session.add(user)
db.session.add(todo1)
db.session.commit()

修改只需要修改属性并提交就行了。

删除数据

1
2
db.session.delete(todo2)
db.session.commit()

删除数据直接调用 delete 方法即可,记得 commit。

查询数据 查询数据稍微复杂一下

1
2
3
4
5
# 一个简单的查询,查询所有待办事项
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 可以这样:

1
2
3
4
5
6
from flask.ext.migrate import Migrate, MigrateCommand

migrate = Migrate()
migrate.init_app(app, db)

manager.add_command('db', MigrateCommand)

然后迁移的时候执行的命令是:

1
2
3
python app.py db init
    python app.py db migrate
    python app.py db upgrade

这里的 init 只需要执行一次,以后的修改都不用执行了。