flask-SQLAlchemy的一些笔记

所谓ORM技术是指将对数据库的操作映射到对类对象的操作,用起来更方便些。
python-flask中使用Flask-SQLAlchemy管理数据库,支持多种数据库后台。
安装方式为: sudo pip install flask-sqlalchemy

以下程序使用的数据库是mysql,除了一些配置参数稍有区别,其余对数据库类型没有影响

在flask程序中想要连接数据库需要在程序中加载一个配置参数:

app = Flask(__name__)
app.config['SQLALCHEMY_DATABASE_URI'] = 'mysql+pymysql://usernamer:passwd@server/database'
db = SQLAlchemy(app)

可以在代码中使用 db.create_all() 测试是否连接成功

1、数据库的使用

创建数据库使用 db.create_all() 实现, 删库使用 db.drop_all()
如果数据库表已存在现有数据库中,那么 db.create_all() 不会执行任何更新操作或重建这个表,最粗暴的方法就是,删库再创建。
当然正确的做法应该是使用 Flask-Migrate 进行数据库迁移。

使用SQLAlchemy定义的数据模型需要继承自 db.Model 需要有一个类变量 __tablename__ 定义在数据库中使用的表名(如果省略了,会直接用类的类名作为表名)
其余类变量都是该模型的属性成员,被定义为 db.Column 的实例
db.Column 类构造函数的第一个参数可省略,如果不省略则是一个字符串类型变量,表名在数据库中的列名,如果省略了则是是数据库列和模型属性的类型,其余参数指定属性的配置选项

示例:

class Article(db.Model):
    __tablename__ = 'article'

    id = db.Column(db.Integer, primary_key=True, autoincrement=True)
    title = db.Column(db.String(100), nullable=False)
    content = db.Column(db.Text(), nullable=False)

    def __repr__(self):
        return '<Article %r>' % self.title

常用的SQLAlchemy列类型

类型

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,这列允许为null

default

为这列定义默认值

autoincrement

若为true,则自增

Flask-SQLAlchemy要求每个模型都要定义主键,这一列常被命名为id。
虽然没有强制要求,但为一般会为模型定义一个 __repr__() 方法,返回一个具有可读性的字符串模型(和__str__()类似),可在调试和测试时使用。

2、数据库常用操作(增删改查)

1. 增

在数据库表中添加一行

在ORM技术中增加很简单,其实就是创建一个对象,然后进行事务提交即可。
具体操作如下:

art = Article(title='title1', content='text body')
db.session.add(art)
db.session.commit()

如上所示, 构造一个模型对象,设置相应属性,最后进行事务提交即可。

2. 查

查找数据库中的一行数据

在ORM技术中,查找到的数据对象都被保存在模型对象中了。后面的改和删都是基于查找技术之上的。
Flask-SQLAlchemy为每个模型类都提供了query对象,供查询使用

# 查找所有title为title1的数据库行
art = Article.query.filter(Article.title=='title1').all()
art = Article.query.filter_by(title='title1').all()

# 查找tittle为title1的第一行
art = Article.query.filter(Article.title=='title1').first()
art = Article.query.filter_by(title='title1')[0]

首先看看前两行,这两行是查询所有title属性为title1的行,Article.query.filter(Article.title=='title1') 这一句其实相当于一个数据库查询语句,并没有直接获取所有数据库对象,想要看看原生的数据库查询语句可以打印下 str(Article.query.filter(Article.title=='title1'))

加个all()即可返回所有值的一个列表。若只需要第一个元素则可以直接first()

由前两行我们可以看出,按条件查询有两种常用方式,一种是 filter 另一种是 filter_by 。这两者的区别主要在于,filter是提供一种基本SQL查询,怎么说呢?看看filter方式的查询方法,至于一个 = 意味着赋值,也就是查询,按照谁等于谁的方法,当然filter可以提供多个参数,之间用,连接即可,也就是说想要用filter查询,你必须知道确切的取值。而filter_by则提供了一种更宽泛的查询,在其中可以进行一些逻辑比较之类的操作。

由后两句我们可以看出,前面的查询其实有点相当于一个列表了(只是相当于,我们还是需要用all()获取真正的所有对象的列表),因此如果我们要取出第一个元素可以使用 first() 或者直接像 list 那样的操作。当然要取任意位置的元素还是只能用第二种了,但是这里还是比较推崇第一种,为什么呢?首先,一般需要取单个元素的一般都是第一个,因为要取一个出来的时候,一般都是唯一一个,而不是从一堆里乱取一个,如果有很多个满足条件,肯定是取列表。因此需求一般是取一个出来。那么为什么第一个比第二个好呢?因为如果没有满足条件的时候,第一种方式会返回一个 None 值,而第二个会抛出异常,判断 None 可比 try...except... 好处理些吧。

上面的 all, filter, filter_by 其实都是 query 上的的过滤器。

常用的SQLAlchemy查询过滤器

过滤器

说明

filter()

把过滤器添加到原查询上,返回一个新查询

filter_by()

把等值过滤器添加到原查询上,返回一个新查询

limit()

使用指定的值限制原查询返回的结果数量,返回一个新查询

offset()

偏移原查询返回的结果,返回一个新查询

order_by()

根据指定条件对原查询进行排序,返回一个新查询

group_by()

根据指定条件对原查询进行分组,返回一个新查询

看了上面说明那一栏应该能理解我上面说的,为什么前面的查询是相当于列表,而是不就是列表,因为它实际上还只是一个查询,还没有获取到具体的值对象。
在查询上应用指定过滤器后,还需要触发该查询获取真正的值对象。
除了上面介绍到了的 all(), first() 之外还有一些其它的方法。

常用的SQLAlchemy查询执行函数

方法

说明

all()

以列表形式返回查询的所有结果

first()

返回查询的第一个结果,如果没有结果,则返回None

first_or_404()

返回查询的第一个结果,如果没有结果,则终止请求,返回404错误响应

get()

返回指定主键对应的行,如果没有对应的行,则返回None

get_or_404()

返回指定主键对应的行,如果没找到指定的主键,则终止请求,返回404错误响应

count()

返回查询结果的数量

paginate()

返回一个Paginate对象,它包含指定范围的结果

改其实就是把要修改的对象查找出来,进行属性值修改,再进行一次事务提交即可。

art = Article.query.filter_by(title='title1').first()
if art is not None:
    art.title = 'new title'
db.session.commit()

删除操作和修改操作是比较类似的,先查找出要删除的对象,进行删除操作,再进行一次事务提交即可。

art = Article.query.filter_by(title='title1').first()
if art is not None:
    db.session.delete(art)
db.session.commit()

顺带提下,事务回滚操作是 db.session.rollback()

数据库关系

首先定义两个表模型:

class User(db.Model):
    __tablename__ = 'user'
    id = db.Column(db.Integer, primary_key=True, autoincrement=True)
    username = db.Column(String(20), nullable=True)

    def __repr__(self):
        return "<user %s>" % self.username


class Article(db.Model):
    __tablename__ = 'article'
    id = db.Column(db.Integer, primary_key=True, autoincrement=True)
    title = db.Column(db.String(100), nullable=False)
    content = db.Column(db.Text(), nullable=False)
    author_id = db.Column(db.Integer)

    def __repr__(self):
        return "<article %s>" % self.title

外键约束

关系型数据库有个非常重要的概念就是外键约束。
那么在 Flask-SQLAlchemy 中怎么指定外键约束呢?

class Article(db.Model):
    author_id = db.Column(db.Integer, db.ForeignKey('user.id'))

应该使用 ForeignKey 来设置,并且需要注意的是,括号里的参数 user.id 这里的 user 是表名(__tablename__),而不是模型名(类名)

反向引用

Flask-SQLAlchemy 中还提供了一种反向引用的关系。反向引用可以根据外键约束找到关联的关系模型,此时可以直接获取相关的模型对象,而不是外键的值。

class User(db.Model):
    # ...
    xxx = db.relationship('Article', backref='user')

class Article(db.Model):
    # ...
    yyy = db.relationship('User', backref='articles')

这是干了个什么事儿呢?
假设有如下代码:

user = User(username="flask")
db.session.add(user)
db.session.commit()

art1 = Article(title="title1", content="text1", author_id=user.id)
art2 = Article(title="title2", content="text2", author_id=user.id)
db.session.add(art1)
db.session.add(art2)
db.session.commit()

# <user flask>
print(art1.user)
# <user flask>
print(art2.user)
# [<article title1>, <article title2>]
print(user.articles)

可以看到,建立了外键关系的对象,可以直接通过反向关系直接访问有关键关系的模型对象。是不是非常方便?
下面看看这个是怎么用的:
可以很明显猜到是 xxx = db.relationship('Article', backref='user')yyy = db.relationship('User', backref='articles') 这两句起的作用,分析下它们的共性,我们就能看明白反向引用的用法了。
首先 这里是随便定义了一个成员 xxx, yyy 这里想说的是,这里随便定义什么都不影响,这里只是举例告诉你不影响,所以随便取了名字,你以后在写的时候,最好不要这样,最好定义一些有意义的名字。
其次就是两个参数,第一个参数,不难发现,是和本表有外键约束的那个表的模型名(类名)而不是前面说的那样的是表名(__tablename__)。第二个参数,也还好理解,其实就是我们在进行反向引用时使用的那个变量。

我们在user表中加入了 articles 这个反向引用,我们就可以通过 u.articles 得到这个用户的所有article。同样,我们在article表中加入了 user 这个反向引用,我们就可以通过 art.user 得到这个art的user。

多对多关系

数据库中很多时候都需要我们定义一个多对多关系,数据库的正确处理方法是定义第三张表,SQLAlchemy中同样也是这样处理的。
首先定义两个数据模型,标签和文章。一个文章可能有多个标签,一个标签可能对应多个文章,所以就需要定义第三章表来处理这个关系了。

class Article(db.Model):
    __tablename__ = 'article'
    id = db.Column(db.Integer, primary_key=True, autoincrement=True)
    title = db.Column(db.String(100), nullable=False)

class Tag(db.Model):
    __table__ = 'tag'
    id = db.Column(db.Integer, primary_key=True, autoincrement=True)
    name = db.Column(db.String(20), nullable=False)

下面我们需要定义第三张表了,格式如下:

# 表名 成员属性1 成员属性2
article_tag = db.Table('article_tag',
        # 第一个参数是数据库属性名
        db.Column('article_id', db.Integer, db.Foreign_key('article.id'), primary_key=True)
        db.Column('tag_id', db.Integer, db.Foreign_key('tag.id'), primary_key=True)
    )

格式为创建一个 db.Table 的对象,注意这里不是再定义类了。然后传入第一个参数是数据库表名,然后两个参数就是相关联的两个表了。
这里db.Column多了一个字符串参数,这个参数其实就相当于前面写的 xxx = db.Column(...) 就相当于里面的xxx了,是属性名。
为什么这里要这样写呢?因为这里是在构造一个 db.Table 的对象,而不是在定义类成员属性了,所以就要换种方式写。

这里同样可以加入反向引用:

class Article(db.Model):
    # ...
    tags = db.relationship('Tag', secondary=article_tag, backref=db.backref('articles'))

可以看到这里反向引用是借助了第二章表,并在secondary参数中指定了这个表。