对Flask感兴趣的,可以看下这个视频教程:http://study.163.com/course/courseLearn.htm?courseId=1004091002

1. SQLAlchemy 连接数据库

要在 python 中连接数据库,则要从 sqlalchemy 中导入 create_engine,并且要配置好数据库的信息,如下代码所示:

# 导入模块
from sqlalchemy import create_engine # 配置数据库
DIALECT = 'mysql'
DRIVER = 'mysqldb' # python2 写 mysqldb;python3 写 pymysl
USERNAME = 'root'
PASSWORD = 'root'
HOST = '127.0.0.1'
PORT = '3306'
DATABASE = 'db_demo1' # DB_URL 的格式是:dialect+driver://username:password@host:port/database
# 所以要将配置变量组合成固定格式
DB_URL = "mysql+mysqldb://{username}:{password}@{host}:{port}/{db}?charset=utf8".format(username=USERNAME,password=PASSWORD,host=HOST,port=PORT,db=DATABASE) # 创建数据库引擎
engine = create_engine(DB_URL) # 判断是否连接成功
conn = engine.connect()

注意,以上方法未涉及 Flask 的内容,包括 17. Flask 下使用 SQLalchemy 节之前的内容,只是在纯 python 代码中通过 sqlalchemy 进行数据库的操作。

2. ORM 介绍

ORM``(Object Relationship Mapping):对象关系映射。实际上就是模型与数据库表的映射。

3. 数据库命令:

drop database db_name;				删除数据库
create database db_name charset utf8; 创建数据库
show tables; 查询数据库中的所有 table
drop table person; 删除名称为 person 的 table
desc person; 查看 person 的具体属性

2. Flask 中使用 sqlalchemy 连接数据库

2.1 连接数据库

  1. 要在 Flask 中使用 sqlalchemy 连接数据库,应该先导入 create_engine

     from sqlalchemy import create_engine
  2. 再做好连接数据库的相关配置:

     HOSTNAME = '127.0.0.1'
    PORT = '3306'
    DATABASE = 'mydb01'
    USERNAME = 'root'
    PASSWORD = 'root' # dialect+driver://username:password@host:port/database
    DB_URL = 'mysql+pymysql://{username}:{password}@{host}:{port}/{db}?charset:utf8'.format(username=USERNAME,password=PASSWORD,host=HOSTNAME,port=PORT,db=DATABASE)
  3. 连接数据库

     engine = create_engine(DB_URL)

2.2 创建 ORM 模型并映射到数据库中

  1. 创建 ORM 模型

    要创建 ORM 模型,这个模型必须继承自 sqlalchemy 给我们定义好的基类。这个基类是通过 sqlalchemy.ext.declarative 下的一个函数(declarative_base)来初始化的,所以我们要先导入它:

     from sqlalchemy.ext.declarative import declarative_base

    导入完成后,还需要使用它进行基类的初始化,即使用它创建一个基类:

     Base =  declarative_base(engine)
    # 指定数据库引擎创建一个基类,并赋给 Base 变量

    其中,基类和类与对象之间的关系是:基类创建类 -> 类实例化出对象。创建这个基类,是因为这个基类已经帮我们封装好了映射到数据库中的一些方法,自定义的模型若继承自这个基类,会更方便我们通过 python 去操作数据库。

    完成以上步骤后,就可以通过创建出来的基类再创建一个类,而这个类就是 ORM 中的模型,如下:

     class Person(Base):		# 必须继承自基类
    __tablename__ = 'person' # 使用 __tablename__ 来指定映射到数据库中的表名
  2. 定义模型属性,即定义数据库表的字段

    上一步的代码,只是创建了一个可以映射到数据库中的一个表,但是该表并没有任何字段,我们需要完善其中的属性。而这些属性在数据库中是一个个的数据类型(如 int | char | varchar 等),这些数据类型也在 sqlalchemy 中定义好了,我们可以直接用,但要先导入:

     from sqlalchemy import Column,Integer,String
    
     class Person(Base):
    __tablename__ = 'person'
    id = Column(Integer,primary_key=True,autoincrement=True) # 一个 Column 可以定义表中的一个列,可以在括号内指定数据类型(Integer),主键,自增长等数据库属性
    name = Column(String(10))
    age = Column(Integer)
  3. 将创建好的模型映射到数据库中

     Base.metadata.create_all()

    需要注意的是:一旦使用 Base.metadata.create_all() 将模型映射到数据库中,之后若要改变表中的字段(添加字段或删除字段)再重新映射,那么是不会生效的。

  4. 去数据库中验证是否成功

     show tables;
  5. 完整代码如下:

     from sqlalchemy import create_engine,Column,Integer,String
    from sqlalchemy.ext.declarative import declarative_base HOSTNAME = '127.0.0.1'
    PORT = '3306'
    DATABASE = 'mydb01'
    USERNAME = 'root'
    PASSWORD = 'root' # dialect+driver://username:password@host:port/database
    DB_URL = 'mysql+pymysql://{username}:{password}@{host}:{port}/{db}?charset:utf8'.format(username=USERNAME,password=PASSWORD,host=HOSTNAME,port=PORT,db=DATABASE) engine = create_engine(DB_URL) Base = declarative_base(engine) # 1. 创建一个 ORM 模型,这个模型必须继承自 sqlalchemy 给我们定义好的基类
    class Person(Base):
    __tablename__ = 'person'
    # 2. 在这个 ORM 模型中创建一些属性,对应于表中的一些字段,而这些属性必须是 sqlalchemy 给我们定义好的数据类型
    id = Column(Integer,primary_key=True,autoincrement=True)
    name = Column(String(10))
    age = Column(Integer)
    # 3. 将创建好的 ORM 模型映射到数据库中
    Base.metadata.create_all()
  6. 魔术方法

在定义一个类的时候,可以定义一个 __str__() 方法,这个方法的作用是:当这个类被 print 方法调用时,那么 print 会首先调用这个类里面的 __str__() 方法。而在 __str__() 方法中,一般都是 return 一个字符串。例如,对刚刚的类定义的 __str__() 方法:

class Person(Base):
__tablename__ = 'person'
id = Column(Integer,primary_key=True,autoincrement=True)
name = Column(String(10))
age = Column(Integer) def __str__(self):
return 'Person(name:%s,age:%s)' % (self.name,self.age)

3. 增删改查

所有的数据库 ORM 操作都必须通过一个叫做 session 的会话对象来实现,那么这个对象的创建是通过以下代码来实现的:

from sqlalchemy.orm import sessionmaker
engine = create_engine(DB_URL)
Session = sessionmaker(engine)
session = Session()

其中:sessionmakerdeclarative_base 的原理类似,也是一个方法。后者接收一个 engine 创建一个基类,再创建一个模型;前者也要接收一个 engine ,从而对 engine 中的数据进行操作(增删改查等)。

后两行代码可以简化写成:session = sessionmaker(engine)()

3.1 增

创建对象,并使用 session 对象添加且提交:

p = Person(name='myyd',age=18)	# 创建对象(对于自增长的属性,主键不用写)
session.add(p) # 将对象添加到会话对象中
session.commit() # 使用 commit 将会话中的对象提交到数据库中

如果要添加多条数据,则必须以列表的形式传给 session 对象的 add_all() 方法:

p1 = Person(name='MYYD',age=19)
p2 = Person(name='Myyd',age=20)
session.add_all([p1,p2]) # 注意是 add_all
session.commit()

3.2 查

可以查询所某个数据库中某个表的所有数据,也可以使用条件查询该表中的符合条件的数据。

  1. 查找表中的所有数据:

     person_all = session.query(Person).all()
    for person in person_all:
    print(person)
  2. 查找表中符合条件的所有数据(方法一):

     person_all = session.query(Person).filter_by(age=18).all()
    for person in person_all:
    print(person)
  3. 查找表中符合条件的所有数据(方法二):

     person_all = session.query(Person).filter(Person.age==18).all()
    for person in person_all:
    print(person)
  4. 查找表中符合条件的所有数据(区别):

    区别在于,filter() 要指定类名,并且判断时要使用双等于号 ==,要相对麻烦一点。但是这两种方法,在大项目中是会同时用到的,所以两个都要学会!

  5. 使用 get() 方法根据主键查找数据:

    get() 方法会根据表中的主键进行查找数据,若有则返回数据,若无则返回 None。

     person1 = session.query(Person).get(1)
    person2 = session.query(Person).get(100)
    print(person1,person2)
    # 会返回 get(1) 的数据,get(100) 的数据是 None
  6. 使用 first() 方法获取表中的第一条数据:

     person3 = session.query(Person).first()
    print(person3)

3.3 改

要修改表中的数据,思路很简单:先通过查询,将指定数据选出来并赋予一个变量;再修改该变量的属性;最后用 session.commit() 提交即可。如下所示:

person = session.query(Person).first()
person.name = 'mayiyoudu'
session.add(person)
session.commit()

3.4 删

和修改类似,先查询找到指定的数据,再通过 session 进行删除。如下所示:

person = session.query(Person).first()
session.delete(person)
session.commit()

4. Sqlalchemy 的常用数据类型 和 ORM 模型常用参数

4.1 常用数据类型

  1. 整型(Integer),有微整型,整型和长整型
  2. 浮点型(Float,Double),Float32 位;Double64
  3. 布尔型(Boolean),在数据库中可以用微整型(01)来实现
  4. 定点类型(DECIMAL),用来处理精度丢失的问题,相当于将输入的浮点数当成文本处理
  5. 枚举类型(Enum),只能输入预先指定的内容
  6. 日期类型(Date),只能存储年月日,传入datetime.date()
  7. 日期时间类型(DateTime),可以存储年月日时分秒,传入datetime.datetime()
  8. 时间类型(Time),只能存储时分秒,传入datetime.time()
  9. 字符类型(String),相当于数据库中的varchar类型,即可变长字符类型
  10. 文本类型(Text),相当于数据库中的text类型,最多只能存储6W多个字
  11. 长文本类型(LONGTEXT),如果文字较多,可用LONGTEXT类型,只MySQL支持,要从另外的包中导入

下面我们分别来介绍各个数据类型的特性:

  1. 整型、浮点型、文本类型、字符类型比较常用,我们放到一个例子来讲。如下代码所示:

     class Articles(Base):
    __tablename__ = 'articles'
    id = Column(Integer,primary_key=True,autoincrement=True)
    title = Column(String(50),nullable=True)
    content = Column(Text,nullable=True)
    price = Column(Float,nullable=True) article = Articles(title='MYYD',content='HelloWorld',price=12.34563)

    上述代码表示:

    1. id 字段为整型,其值必须为一个整数;
    2. title 字段为字符类型,对应数据库中的 varchar 类型,是一个可变长度的字符,括号中的数字为该字段所能接受的最大字母数;
    3. content 字段为文本类型,可接受最大字符长度为6W多字;
    4. price 字段为浮点类型,对于 Float 来说只能表示4位小数,对于 Double 来说可以接受8位小数,如果数字太大,可以使用定点类型。
  2. 定点类型

     class Articles(Base):
    __tablename__ = 'articles'
    id = Column(Integer,primary_key=True,autoincrement=True)
    price = Column(DECIMAL(10,6)) article = Articles(price=2345.67891)

    其中,DECIMAL(10,6) 代表一共只能表示10个数字,分别是:整数最多只能有4位,小数最多只能表示6位。如果小数位数多了则四舍五入表示6位;如果整数位数多了,则直接报错。即小数位数可以大于6而整数位数不可以大于4。

  3. 日期时间类型

     from datetime import date,datetime,time
    
     class Articles(Base):
    __tablename__ = 'articles'
    id = Column(Integer,primary_key=True,autoincrement=True)
    create_date = Column(Date)
    create_datetime = Column(DateTime)
    create_time = Column(Time) article = Articles(create_date=date(2011,11,11),create_datetime=datetime(2011,11,11,11,11,11),create_time=time(11,11,11))

    要使用日期和时间,需要另外从 datetime 模块中导入 datedatetimetime;与模型字段中的三个数据类型 DateDatetiemTime 一一对应。

  4. 枚举类型(方法一)

     class Articles(Base):
    __tablename__ = 'articles'
    id = Column(Integer,primary_key=True,autoincrement=True)
    Language = Column(Enum('Python','Java','PHP','C++')) article = Articles(Language='python')

    其中,在创建实例的时候,Language 字段必须要从枚举指定的 Python Java PHP C++ 中选择,否则报错。

  5. 枚举类型(方法二)

    定义枚举类型还有另外一种更好的方法,就是借助 python3 中自带的 enum 模块进行定义。要注意四个地方:

    1. 导入 enum 模块
    2. 创建所需的 enum 类并继承自 enum.Enum
    3. 创建模型并且使用枚举数据类型时,从自定义的 enum 类中引用
    4. 根据模型创建实例时也可以从自定义的 enum 类中取

    如下代码所示:

     import enum
    
     class MyEnum(enum.Enum):
    Python = 'Python'
    Java = 'Java'
    PHP = 'PHP'
    C = 'C++' class Articles(Base):
    __tablename__ = 'articles'
    id = Column(Integer,primary_key=True,autoincrement=True)
    Language = Column(Enum(MyEnum)) article = Articles(Language=MyEnum.Python)
  6. 长文本类型

    长文本类型只有 MySQL 数据库才支持,所以如果想使用长文本类型,则需要从 sqlalchemy.dialects.mysql 中导入:

     from sqlalchemy.dialects.mysql import LONGTEXT
    
     class Articles(Base):
    __tablename__ = 'articles'
    id = Column(Integer,primary_key=True,autoincrement=True)
    content = Column(LONGTEXT) article = Articles(content='over 60000 words')

4.2 常用参数

  1. primary_key:设置是否为主键

  2. autoincrement:设置是否为自增长

  3. default:设置默认值,即当实例化的时候没有指定该属性的值时,该属性的值。可以在 create_time 属性中使用。

  4. nullable:设置该属性的值是否可以为空,如果没有给该属性设置该参数,则默认为 True,即默认可空。但主键默认为 False

  5. unique:设置是否唯一,比如手机号码、邮箱等属性,都是唯一的,即要指定 uniqueTrue。不设置时,默认是 False

  6. onupdate:若设置了该属性,则当其他属性有改动的时候,该属性也会更改。最典型的应用是:

    update_time 用来设置文章的更新时间,当文章的标题或者内容被更新时,update_time 也会随之被更新,如下代码所示:

     class Aricles(Base)
    __tablename__ = 'articles'
    id = Column(Integer,primary_key=True)
    title = Column(String(50),nullable=False)
    content = Column(Text,nullable=Flase)
    update_time=Column(DateTime,onupdate=datetime.now,default=datetime.now) article = Articles(datetime.now())

    当对象 article 被创建后,在某一时刻其 title 或者 content 属性被修改,那么其 update_time 属性由于被指定了 onupdate=datetime.now 参数,也会随之更改。

    在第一次创建这条数据的时候,不会使用 onupdate 的值,而是使用 default 的值。

  7. name:用来指定某个模型中的属性映射到数据库后,该属性对应字段的名称。也就是说,你在定义模型的时候,有一个 title 属性,但是你想让该属性映射到数据库中的时候变成其他名字的字段,就可以使用 name 参数来实现。如:

     class Aricles(Base)
    id = Column(Integer,primary_key=True)
    __tablename__ = 'articles'
    title = Column(String(50),nullable=False,name='My_title') # 如果把 name 参数放到该属性第一个位置,则不需要 name 关键字,如下即可:
    title = Column('My_title',String(50),nullable=False) # 但是不可以把该参数放到第一个位置的同时还指定参数名,因为关键字参数必须要放在未知参数之后!(name='myyd'这种叫关键字参数;int(12)这种叫未知参数)。
    title = Column(name='My_title',String(50),nullable=False) # 这样是不行的

5. query 查询详解

准备工作:

在查询之前,我们需要在创建好的模型中定义 __repr__() 函数,这个函数的作用是:当用 print 输出由这个类组成的列表时,会按照这个函数定义的格式输出。

要注意与 __str__() 函数的区别,__str__() 函数是只有在 print 输出去这个类的具体实例的时候才会被调用。

两个函数的定义实现如下:

def __str__(self):
return 'id=%s;title=%s;price=%s' % (self.id,self.title,self.price)
def __repr__(self):
return 'id=%s;title=%s;price=%s' % (self.id, self.title, self.price)

5.1 获取模型所有属性

可以用 query 获取 ORM 模型中实例的所有属性:

result = session.query(Articles).all()
for article in result:
print(article)

5.2 获取模型中指定属性

可以用 query 获取 ORM 模型中实例的指定属性:

result = session.query(Articles.title,Articles.price).all()
for article in result:
print(article)

5.3 聚合函数

可以用 query 内置的一些聚合函数来实现对查找到的数据做进一步的操作,在使用这些聚合函数之前要先导入这些函数所在的类(func)。

from sqlalchemy import func

这些聚合函数是:

  1. func.count:统计行的数量

     session.query(func.count(Articles.id)).first()	# 注意要指定表的字段,即模型的属性
    # 输出该表中的数据条目数量,例如:有六条数据就输出(6,)
  2. func.avg:求平均值

    求平均值也是类似的用法:

     result = session.query(func.avg(Articles.price)).first()
    print(result) # (78.28571428571429,)
  3. func.max:求最大值

     result = session.query(func.max(Articles.price)).first()
    print(result) # (97,)
  4. func.min:求最小值

     result = session.query(func.min(Articles.price)).first()
    print(result) # (51,)
  5. func.sum:求和

     result = session.query(func.sum(Articles.price)).first()
    print(result) # (548.0,)

实际上,func 对象中并没有定义任何函数,因为它底层的实现是把传入的函数翻译成 sql 语句后再进行操作。所以只要是 mysql 中有的聚合函数,都可以使用 func. 来调用。

6. filter 方法常用过滤条件

  1. equal

     article = session.query(Articles).filter(Articles.id==1).all()
    print(article)
  2. not equal

     articles = session.query(Articles).filter(Articles.id!=1).all()
    print(articles)
  3. like & ilike(不区分大小写)

     articles = session.query(Articles).filter(Articles.title.like('MYY%')).all()		# 其中,% 在 sql 语句中是通配符
    print(articles)
  4. in

    这里要注意,使用的 in 的时候要传入一个列表,最终查找的结果是:既在数据库中又是列表中指定的数据被找到。

     articles = session.query(Articles).filter(Articles.title.in_(['MYYD','title1','title2'])).all()
    # 为什么是 in_ ?因为 python 中有关键字 in,为了区分所以不能用 in,而 _in 代表了类中的私有方法,但这里很明显应该是公有方法,所以定义为 in_
    print(articles)
  5. not in

    即相对于上例而言,取反的结果。

    方法一:波浪号

     articles = session.query(Articles).filter(~Articles.title.in_(['MYYD','title1','title2'])).all()		# 注意这里要加一个波浪号
    print(articles)

    方法二:notin_()

     articles = session.query(Articles).filter(Articles.title.notin_(['MYYD','title1','title2'])).all()		# 注意这里是 notin_()
    print(articles)
  6. is nullis not null

    用来根据某个字段是否为空来查找数据,如下:

     articles = session.query(Articles).filter(Articles.content==None).all()
    print(articles) # 查找 Articles.content 字段为空的数据

    is not null 的示例如下:

     articles = session.query(Articles).filter(Articles.content!=None).all()
    print(articles) # 查找 Articles.content 字段为空的数据
  7. and

    用来查找更精细的范围,如 content='abc' 并且 title='MYYD' 的数据,如下:

     articles = session.query(Articles).filter(Articles.content=='abc',Articles.title=='MYYD').all()
    print(articles)

    会查找到 title 为 MYYD 并且 content 为 abc 的数据条目。

  8. or

    只要满足指定条件之一即可。如下:

     from sqlalchemy import or_
    
     articles = session.query(Articles).filter(or_(Article.title=='MYYD',Articles.content=='MYYD')).all()
    print(articles)

    用得比较多的情况是,搜索一个关键字,这个关键字可能出现在标题中,也可能出现在内容中。这里要注意的是:要从 sqlalchemy 中导入 or_ 这个方法。

    实际上,and 也有这种方法,如下:

     from sqlalchemy impor and_
    
     articles = session.query(Aritlces).filter(and_(Articles.title=='MYYD',Articles.content=='abc')).all()
    pint(articles)
  9. 小知识,如果想要获取翻译成的 sql 语句,可以在查询的时候不加 .all() 或者 .first(),如下:

     articles = session.query(Articles).filter(~Articles.title.in_(['MYYD','title1','title2']))
    print(articles) 输出如下:
    SELECT articles.id AS articles_id, articles.title AS articles_title, articles.price AS articles_price FROM articles WHERE articles.title NOT IN (%(title_1)s, %(title_2)s, %(title_3)s, %(title_4)s)

7. 外键及其四种约束

外键可以使表与表之间的关系更加紧密,Sqlalchemy 也支持对外键的操作。而 Sqlalchemy 操作外键是通过 ForeignKey 来实现的。最常见的例子是:有用户和文章这两张表,每张表都有自己的属性,但是文章是通过用户来发表的,所以这两张表中必然存在某种联系;使用外键就可以将两张表联系起来。

那么怎么使用 sqlalchemy 创建两张具有约束关系的表呢?如下所示:

class User(Base):
__tablename__ = 'users'
id = Column(Integer,primary_key=True,autoincrement=True)
username = Column(String(20),nullable=False) class Article(Base):
__tablename__ = 'articles'
id = Column(Integer,primary_key=True,autoincrement=True)
title = Column(String(50),nullable=False)
content = Column(Text) user_id = Column(Integer,ForeignKey('users.id')) # 注意这个地方要使用数据库表名,而不是创建模型时的类名

7.1 外键的四种约束

使外键有四个可以约束的功能,在使用外键连接时用 ondelete 关键字指定。指定方法是:

user_id = Column(Integer,ForeignKey('users.id',ondelete='RESTRICT'))
user_id = Column(Integer,ForeignKey('users.id',ondelete='NO ACTION'))
user_id = Column(Integer,ForeignKey('users.id',ondelete='CASCADE'))
user_id = Column(Integer,ForeignKey('users.id',ondelete='SET NULL'))

四个约束功能介绍如下:

  1. RESTRICT:父表数据被删除时会由于子表数据还在引用而阻止删除

    这个约束是默认就会存在的,不用特意指定。

  2. NO ACTION:与 MySQL 中的 RESTRICT 相同

    既然相同,那么也不用特意指定。

  3. CASCADE:级联删除,即当父表中的某个条目被删除了,子表中关联了该条目的数据也会被删除

     user_id = Column(Integer,ForeignKey('users.id',ondelete='CASCADE'))
  4. SET NULL:父表中的某个条目被删除,子表中关联了该条目的数据不会被删除,但是外键字段会被置为 NULL

     user_id = Column(Integer,ForeignKey('users.id',ondelete='SET NULL'))

    即原来是:

     mysql> select * from articles;
    +----+-------+---------+---------+
    | id | title | content | user_id |
    +----+-------+---------+---------+
    | 1 | NB | abc | 1 |
    +----+-------+---------+---------+

    users 表中 id=1 的数据被删除后,articles 表中关于该字段的值就会被置为 NULL

     mysql> select * from articles;
    +----+-------+---------+---------+
    | id | title | content | user_id |
    +----+-------+---------+---------+
    | 1 | NB | abc | NULL |
    +----+-------+---------+---------+

    要注意的是,在使用外键时,是不可以为其设置 SET NULL 字段的同时还设置 nullable=False 的。

8. ORM 外键和一对多关系

现在有一个需求,要查找某一篇文章标题为 NB 的作者的信息,怎么才能实现这一需求?

8.1 原始的方法:

  1. 查找标题为 NB 的文章
  2. 获取该文章关联到 users 的外键字段的值
  3. 通过该值去 users 表中查找到该作者

代码实现如下:

article_NB = session.query(Article).filter_by(title='NB').first()
print(article_NB)
uid = article_NB.user_id
author = session.query(User).get(uid)
print(author)

8.2 ORM 提供的方法(relationships)

要使用 ORM 提供的这个方法,就必须从 sqlalchemy.orm 模块中导入:

from sqlalchemy.orm import sessionmaker,relationship

然后在定义外键的时候,同时定义外键所链接到的表的字段,并指定 relationship 字段:

class Article(Base):
__tablename__ = 'articles'
id = Column(Integer,primary_key=True,autoincrement=True)
title = Column(String(50),nullable=False)
content = Column(Text) user_id = Column(Integer,ForeignKey('users.id')) # 注意这个地方要使用数据库表名,而不是创建模型时的类名 author = relationship('User') # 注意这个地方要使用创建模型时的类名,而不是数据库表名

做好上述定以后,就可以直接通过 relationships 提供的方法,来获取文章的作者了,如下所示:

article = session.query(Article).filter(Article.title=='NB').first()
print(article.author) # article.author 即是所要找的作者
print(article.author.username) # 打印作者的用户名

8.3 relationship 反向查找

上面的例子是:查找一篇文章对应的作者,那么文章和作者之间的关系是多对一的,即一个作者可能发表多篇文章,而一篇文章只能有一个作者。

8.2 中,已经使用 relationship 引用了文章的作者,那么能否使用 relationship 引用作者的文章呢?答案是肯定的,如下所示:

from sqlalchemy.orm import sessionmaker,relationship

class User(Base):
__tablename__ = 'users'
id = Column(Integer,primary_key=True,autoincrement=True)
username = Column(String(20),nullable=False) articles = relationship('Article')

通过作者找文章的方法和通过文章找作者的方法类似,如下:

user = session.query(User).filter_by(username='MYYD').filter()
print(user.articles) # 会打印出该 user 的所有 article

8.4 使用 backref 反向引用

在之前的例子中,我们在 UserArticle 中都分别使用了 relationship 来互相指定对方和自己的关系。实际上,不用这么麻烦,我们只需要在一个模型中指定其和另一个模型的关系即可,这时需要借助另一个参数:bakeref

所以我们只需要在 Article 模型中指定即可:

class Article(Base):
__tablename__ = 'articles'
id = Column(Integer,primary_key=True,autoincrement=True)
title = Column(String(50),nullable=False)
content = Column(Text) user_id = Column(Integer,ForeignKey('users.id')) author = relationship('User',backref='articles')

这样,同样可以使得 user.articles 能够正常使用。而不用再在 User 模型中使用 relationship 再指定一次关系了。

8.5 完整代码

class User(Base):
__tablename__ = 'users'
id = Column(Integer,primary_key=True,autoincrement=True)
username = Column(String(20),nullable=False) def __repr__(self):
return 'username:%s'% self.username class Article(Base):
__tablename__ = 'articles'
id = Column(Integer,primary_key=True,autoincrement=True)
title = Column(String(50),nullable=False)
content = Column(Text)
user_id = Column(Integer,ForeignKey('users.id',ondelete='SET NULL'))
author = relationship('User',backref='articles') def __repr__(self):
return 'title:%s\tcontent:%s'%(self.title,self.content)

9. 一对一关系

在指定了一对多的关系后,如 usersarticles 这两者之间的关系。实际上,在创建 articles 的时候可以不用指定其 user_id 字段的值,当一篇文章被创建完成后,再指定作者也是被允许的。因为在一对多的关系中,usersarticles 属性是一个 LIST,可以使用 append 方法为其添加值。如下代码所示:

article1 = Article(title='abc',content='abc')
article2 = Article(title='MYYD',content='myyd')
session.add_all([article1,article2]) user1 = User(username='MYYD')
session.add(user1) user1.articles.append(article1) # 使用 append() 方法
user1.articles.append(article2)
session.commit() user = session.query(User).filter_by(username='MYYD').first()
print(user.articles) # 输出 [title:abc content:abc, title:MYYD content:myyd]

以上,是对 ORM 中一对多关系的一点补充。借此,我们可以引出 ORM 中一对一的关系:

有一个需求:为用户创建一个 users 表,表中定义了一些属性,这些属性有:姓名,学校等。其中学校这个属性不是常用属性,我们把它放到另外一张 extends 表中。这样一来,两张表就是一对一的关系了。那么,怎么用 ORM 来实现这一需求呢?可以在创建 extends 表的时候在使用 relationship 参数的字段中,传入 uselist=False 参数,即不可以 uselist,如下代码所示:

class User(Base):
__tablename__ = 'users'
id = Column(Integer,primary_key=True,autoincrement=True)
username = Column(String(50),nullable=False) def __repr__(self):
return 'id:%s\tusername:%s'%(self.id,self.username)
class UserExtend(Base):
__tablename__ = 'extends'
id = Column(Integer,primary_key=True,autoincrement=True)
school = Column(String(20))
user_id = Column(Integer,ForeignKey('users.id'))
my_user = relationship('User',backref=backref('extends',uselist=False)) # 注意这里使用 relationship 参数的方法,和传入 backref 与之前的区别 def __repr__(self):
return 'school:%s'%self.school user1 = User(username='MYYD')
session.add(user1) extend = UserExtend(school='JMU')
session.add(extend) user1.extends.append(extend) # 此时再使用 append 方法时就会报错:AttributeError: 'NoneType' object has no attribute 'append'
session.commit()

这就是 ORM 中一对一关系的使用。

10. 多对多的关系

多对多关系的需求常用在文章与标签上,譬如可以按照内容划分文章的标签为:音乐、体育、娱乐、搞笑等;而一篇文章可能涉及多个方面的内容,这时候文章和标签就是多对多的关系。但是我们使用 ORM 定义两张表的多对多关系时,需要借助一个中间表来实现。

10.1 多对多的创建

为两张表创建多对多关系的步骤如下:

  1. 导入定义中间表所需的 Table 类:

     from sqlalchemy import Table
  2. 创建两张表(artcielstags):

    按照常规的方法创建两张表即可:

     class Article(Base):
    __tablename__ = 'articles'
    id = Column(Integer,primary_key=True,autoincrement=True)
    title = Column(String(50),nullable=False)
    content = Column(Text) def __repr__(self):
    return 'title:%s\tcontent%s'%(self.title,self.content) class Tag(Base):
    __tablename__ = 'tags'
    id = Column(Integer,primary_key=Text,autoincrement=True)
    name = Column(String(50),nullable=False) def __repr__(self):
    return 'TagName:%s'%self.name
  3. 重点在这里!创建中间表:

    这个中间表用来连接刚刚定义的两张表,中间表必须继承自这个 Table 类。并且还要使用 metadate 进行初始化,同时要设置聚合主键,如下所示:

     article_tag = Table(
    'article_tag',
    Base.metadata,
    Column('articles_id',Integer,ForeignKey('articles.id'),primary_key=True),
    Column('tags_id',Integer,ForeignKey('tags.id'),primary_key=True)
    # tag_id 是字段名称,Integer 的类型要和本表外键引用的字段类型相同
    )

    其中:

    1. Base.metadata 用来对这个中间表进行初始化;
    2. 然后用 Column 对其进行相关字段的创建。
    3. 为两个字段同时设置主键,这样就能两张表中的唯一一篇文章。
  4. 使用 relationship 关联两张表:

    最后,还要在两张表的其中一张表中使用 relationship 参数来互相关联一下,同时指定 secondary 参数来指定通过实现多对多关系的中间表,如下:

    1. articles 表中使用:

       tags = relationship('Tag',backref='articles',secondary=article_tag)
    2. tags 表中使用:

       articles = relationship('Article',backref='tags',secondary=article_tag)

    需要注意的是:如果使用中间表来实现多对多的映射关系后,就不必在两张被映射的表中指定外键关系了。因为已经通过 secondary 来指定中间表格了,而中间表格会实现外键约束。

10.2 多对多的使用

我们先用定义的两张表创建数据并关联一下:

  1. 为每张表创建两个数据

     article1 = Article(title='ABC',content='ABC')
    article2 = Article(title='abc',content='abc') tag1 = Tag(name='tag1')
    tag2 = Tag(name='tag2')
  2. 为文章添加标签

     article1.tags.append(tag1)
    article1.tags.append(tag2)
  3. 添加并提交 session

     session.add_all([article1,article2])
    session.commit()
  4. 到数据库中查找

     # articles 表
    mysql> select * from articles;
    +----+-------+---------+
    | id | title | content |
    +----+-------+---------+
    | 1 | ABC | ABC |
    | 2 | abc | abc |
    +----+-------+---------+
    2 rows in set (0.00 sec) # tags 表
    mysql> select * from tags;
    +----+------+
    | id | name |
    +----+------+
    | 1 | tag1 |
    | 2 | tag2 |
    +----+------+
    2 rows in set (0.00 sec) # article_tag 表
    mysql> select * from article_tag;
    +-------------+---------+
    | articles_id | tags_id |
    +-------------+---------+
    | 1 | 1 |
    | 2 | 1 |
    | 1 | 2 |
    | 2 | 2 |
    +-------------+---------+
    4 rows in set (0.00 sec)

在用我们的 query 去查询:

article = session.query(Article).first()
print(article.tags)
# [TagName:tag1, TagName:tag2] tag = session.query(Tag).first()
print(tag.articles)
# [title:ABC contentABC, title:abc contentabc]

11. ORM 层面删除数据注意事项

ORM 层面对表中的数据进行删除,比如 articles 表中的 uid 字段通过外键引用到 users 表种的 id 字段。在使用 sql 语句对 users 表中的某个数据进行删除的时候,会由于 articles 表中的外键引用,而拒绝删除。

但是在 ORM 层面使用 python 代码对表中的数据进行删除,那么实际上是可以删除成功的。删除的结果是:users 表中该条数据被删除,但是引用了该条数据的 articles 表中的数据关联的 id 字段会被置为空。

这是因为,ORM 在底层对这个操作的实现分为两步:先将 articles 表中该数据的 uid 字段置为 NULL,再删除 users 表中的数据。不过前提是,articles 中的 uid 字段允许设置为空。

但是这个机制实际上并不怎么好,因为这样可能会导致误删除的发生。那如何避免这种情况呢?实际上也很简单:在 articles 的外键字段上,设置 nullable=False 即可。

总结:

ORM 层面对数据库删除操作,会无视 mysql 级别的外键约束,要想避免这种情况发生,需要将外键字段设置为 nullable=False

12. relationship 方法中的 cascade 参数

relationship 方法中的 cascade 参数可以在创建关系的时候,指定一些属性。cascade 参数一共有以下几个值:

  1. save-update(默认属性)

    这是一个默认属性,即当不指定 cascade 参数的时候,默认 cascade='sace-update'。这个属性的意思是:

    如果给 cascade 指定了 save-update 属性,那么在添加一条数据的时候,会同时添加该条数据关联的其他模型中的数据。例如:

    Article 模型在与 User 模型建立关系的时候,如果指定了 cascade='save-update' 属性,那么当使用 session.add() 添加 Article 实例(article)的时候,如果该实例关联的 User 实例(user)已经创建,则将 user 也一并添加。即不用再使用 session.add(user) 进行添加。

    具体的使用方法如下:

     author = relationship('User',backref='articles',cascade="save-update")

    如果不想让 sqlalchemy 自动执行这个操作,那么可以将 cascade 置为空,即 cascade=""。需要注意的是,这个属性仅对 Article 这个模型生效。也就是说,置为空后,session.add(article) 不会自动执行 session.add(user),但是 session.add(user) 还是会自动执行 session.add(article)。明白?

  2. delete

    delete 的作用就是,当删除一条数据的时候,会同时删除该条数据关联的其他模型中的数据。

    其使用方法和 save-update 一致。

    注意,cascade 属性可以同时指定多个值,例如同时指定 save-updatedelete,可以这么写:

     author = relationship('User',backref='articles',cascade="save-update,delete")

    如果想在关联模型中反向使用该属性,还可以将 cascade 放到 backref() 方法中:

     author = relationship('User',backref=backref('articles',cascade="save-update,delete",cascade="save-update,delete"))
    
     # 如果 backref 只接收一个参数,可以写成 backref='参数',如果接收多个参数,可以调用 backref() 方法
  3. delete-orphan

    如果父表中的相关数据被删除,会使得子表中的数据的某个字段置为空。如果指定了 cascade='delete-orphan',那么由于父表的数据不存在,子表中的数据也会被删除。不过在指定 delete-orphan 的时候要同时指定 delete 属性。那么删除父表数据带动子表数据被删除的操作也有可能是 delete 属性完成的,所以我们可以将父表数据中关于子表数据的字段置空,这样子表的相关字段也会被置空,进而被删除。这样就能体现出 delete-orphan 的作用了。

    注意这个属性只能在父表中指定,并且只能用在一对多或者多对多的关系中。

  4. merge(默认属性)

    这个属性不仅是 relationship 方法中 cascade 的属性,还是 session 中的属性。其作用是:改变数据库中的值。例如:

    users 表的 ORM 模型如下:

     class User(Base):
    __tablename__ = 'users'
    id = Column(Integer,primary_key=True,autoincrement=True)
    name = Column(String(50),nullable=False)

    若表中已经存在一个用户(user1):id=1,name='MYYD';如果再创建一个用户(user2):id=1,name='myyd'。使用 session.add(user2) 后再 commit,会报错。因为 id 是主键,session.add() 不允许添加主键重复的数据。

    那么如果使用 session.merge(user2),则会用 user2id 去匹配 user 表中的 id 字段,并将匹配中的数据中的 name 改为 myyd

    实际上这个属性用得比较少。因为这对于已经存在的数据,相当于修改操作,而修改操作完全不必要使用这种方法。那么这个方法的存在还有什么意义呢?

    这个方法还真有自己存在的意义,因为这种方法可以添加数据,而修改操作却不能添加。使用 session.merge() 的时候,首先会判断表中是否已经存在这条数据,若存在则修改;若不存在则添加。

    除此之外,还有一个作用:如果在父表的 cascade 中指定了这个属性,那么父表下所有关联的子表,在使用 session.merge(父表数据) 的时候,也会实现这个属性。

  5. expunge

    expunge 属性也是删除,用 session.expunge() 实现。session.expunge()session.delete() 的区别是:前者仅仅是将数据从 session 中删除,并不会删除数据库中的内容。这是 expungesession 中的体现。

    而 expunge 在 cascade 中的体现和 merge 类似:当在父表的 cascade 中指定了这个属性,那么在使用 session.cascade(父表数据) 时,父表下所有关联的子表,也会实现这个属性。

  6. all

    如果在 cascade 中指定了 all 属性,那么就相当于包含了以上除了 delete-orphan 外的所有属性。

13. sqlalchemy 中的三种排序方式

  1. order_by

    可以在查询的时候,使用 order_by 参数对查询到的数据进行排序。默认排序是从小到大,从前到后,如果想使用降序排序则在前面加一个 - 号。

     articles = session.query(Article).order_by(-Article.create_time).all()
    for article in articles:
    print(article)

    或者使用 desc 进行排序:

     articles = session.query(Article).order_by(Article.create_time.desc()).all()
    for article in articles:
    print(article)

    或者还可以直接传入模型字段的名称,搭配 - 号使用:

     articles = session.query(Article).order_by('-create_time').all()
    for article in articles:
    print(article)
  2. 模型中的 order_by

    如果对于一个经常需要排序的模型来说,在每次查询的时候都使用 order_by 参数进行排序,会有点麻烦,所以我们再定义模型的时候就可以定义一个 order_by 属性。如下:

    先在模型中定义 __mapper_args__ 属性:

     __mapper_args__ = {
    'order_by':create_time.desc()
    }

    然后查询的时候就不必指定 order_by 关键字了:

     articles = session.query(Article).all()
    for article in articles:
    print(article)

    输出如下:

     title:zhenniubi	create_time:2018-03-28 23:33:32
    title:MYYD create_time:2018-03-28 23:33:07

    但是如果想在某一次查询中不使用倒序排序呢?很简单,在查询的时候指定 order_by 关键字即可。

  3. relationship 中使用 order_by 参数

    可以在定义 relationship 关系的时候直接指定父表下子表的排序方式,如下:

     class User(Base):
    __tablename__ = 'users'
    id = Column(Integer,primary_key=True,autoincrement=True)
    username = Column(String(50),nullable=False) class Article(Base):
    __tablename__ = 'articles'
    id = Column(Integer,primary_key=True,autoincrement=True)
    title = Column(String(50),nullable=False)
    create_time = Column(DateTime,nullable=False,default=datetime.now)
    uid = Column(Integer,ForeignKey('users.id'),nullable=False) author = relationship('User',backref=backref('articles',order_by=create_time.desc()))

    当搜索到一个 user 时,获取 user 下的所有 articles,这些 articles 的排序方式就是 order_by=create_time.desc() 指定的倒序。如下:

     user = session.query(User).filter(User.id=='1').first()
    for article in user.articles:
    print(article) # 打印的内容如下:
    title:title2 create_time:2018-03-29 00:19:18
    title:title1 create_time:2018-03-29 00:19:13

14. sqlalchemy 对数据库的 limit 、offset 和 切片 操作

14.1 limit 操作

limit 这个属性可以对每次查询的结果数量进行限制,例如可以在查询时只取 10 个数据:session.query(Article).limit(10).all()

需要注意的是,使用以上操作进行数据的查询时,默认是从第 1 条数据进行查找的。如果我们想要从指定位置开始查找,可以使用 offset 操作:

14.2 offset 操作

offset 这个属性可以指定我们开始查找的位置,例如想要获取第十一到第二十条数据,可以这么操作:session.query(Article).offset(10).limit(10).all()

这个 offsetlimit 谁先谁后无所谓。

14.3 切片操作

实际上,通过 articles = session.query(Article).all() 得到的数据,articles 是一个列表变量,所以我们可以通过列表中的切片属性,来获取指定的内容。例如想要从获取到的所有数据中提取指定范围内的数据(如第3个到第7个数据),有两种方法可以实现:

  1. slice 方法

    这种方法是:article = session.query(Article).slice(2,7).all()

  2. list 切片方法

    这种方法是:articles = session.query(Article).all()[2:7]

14.4 总结

以上三个操作,都可以搭配上一节中提到的排序方法灵活使用。

15. sqlalchemy 数据查询懒加载技术

15.1 lazy='dynamic'

在现实的项目中,常常会遇到这种需求:当我点进一个用户的主页中,该用户的主页会显示当天发表的文章,而不是其所有的文章。要实现这个功能实际上很简单:先将该用户的所有文章都查找出来,然后对每一篇的创建时间进行过滤后再渲染回前端。如下代码所示:

articles = session.query(Article).all()
for article in articles:
if article.create_time.day == 29:
print(article)

以上方法,确实简单粗暴。但是我只需要展示该用户当天的文章,实际上没必要从数据库中查找出所有的文章,毕竟这么做很浪费资源。我们可以使用 sqlalchemy 给我们提供的懒加载技术,实现从数据库中查找出来的文章就是当天发表的文章的需求。

但是我们使用 articles = session.query(Article).all() 获取到的对象是一个 LIST,而 LIST 是没有办法进行进一步的 filter 的。但是我们的 Query 对象是可以进行 filter 的,所以我们可以将 articles 转换成 Query 对象,然后再对 articles 进行过滤。想要实现这个功能,那么在创建模型关系的时候必须要在 relationship 中的 backref() 方法中,添加一个 lazy=dynamic 值。如下:

class Article(Base):
略..
author = relationship('User',backref=backref('articles',lazy='dynamic'))

然后使用 articles = session.query(Article).all() 查找到的 articles 就是一个 AppenderQuery 对象,该对象除了可以实现 Query 对象的所有功能(包括filter),还能实现 LIST 的所有功能(包括append)。

所以这个需求我们就能轻而易举的实现了:

user = session.query(User).filter_by(id=1).first()
print(type(user.articles))
# 输出:<class 'sqlalchemy.orm.dynamic.AppenderQuery'> print(user.articles.filter(Article.create_time.day==29).all())
# 输出所有 29 号发表的文章
# 注意这里是去数据库中筛选符合条件的数据,而不是将所有的数据都取出来,这样就节省了资源的消耗

注意:这个地方 lazy='dynamic' 只能对 一对多 或者 多对多 中的 使用,即对于上述两个模型,只能用在 UserArticle 上,也就是说对符合条件的文章的过滤只能通过 user.articles.filter() 操作进行实现。

附:相对完整的代码如下:

class User(Base):
__tablename__ = 'users'
id = Column(Integer,primary_key=True,autoincrement=True)
username = Column(String(50),nullable=False) class Article(Base):
__tablename__ = 'articles'
id = Column(Integer,primary_key=True,autoincrement=True)
title = Column(String(50),nullable=False)
create_time = Column(DateTime,nullable=False,default=datetime.now)
uid = Column(Integer,ForeignKey('users.id'),nullable=False) author = relationship('User',backref=backref('articles',lazy='dynamic')) # 注意这里 user = session.query(User).first()
print(user.articles.filter(Article.id>9).all())

15.2 lazy='select'

这个是 sqlalchemy 中的默认选项,即如果不设置 lazy='xxx' 这个属性,则默认为 lazy='select' 生效。

lazy='select' 的作用是,在没有调用 user.articles 之前,sqlalchemy 不会去获取 user.articles 这个属性下的数据;而一旦调用了 user.articles,那么 sqlalchemy 就会自动的去获取 user.articles 这个属性下的所有数据。

这也就是为什么我们在实现上述需求的时候,使用 lazy='dynamic' 的另一大原因。

16. sqlalchemy 对数据库的高级查询

对于定义的 ORM 模型,和插入的数据如下:

class User(Base):
__tablename__ = 'users'
id = Column(Integer,primary_key=True,autoincrement=True)
username = Column(String(50),nullable=False)
age = Column(Integer,nullable=False,default=0)
gender = Column(Enum('male','female','secret'),default='secret') user1 = User(username='张伟',age=13,gender='male')
user2 = User(username='无名',age=14,gender='secret')
user3 = User(username='翠花',age=19,gender='female')
user4 = User(username='狗剩',age=17,gender='male')
user5 = User(username='李蛋',age=16,gender='female') session.add_all([user1,user2,user3,user4,user5])
session.commit()

16.1 group_by 查询

group_by 会根据指定的某个字段进行分组。例如,要根据性别来分组,统计每个性别的人数,可以使用如下代码实现:

result = session.query(User.gender,func.count(User.id)).group_by(User.gender).all()
# 指定查询 User.gender,并且以 User.id 为标识来统计,最后按 User.gander 进行分组
print(result) # 输出:[('male', 2), ('female', 2), ('secret', 1)]

实际上转换成的 SQL 语句如下:

SELECT users.gender AS users_gender, count(users.id) AS count_1 FROM users GROUP BY users.gender

16.2 having 查询

having 是对查找结果的进一步过滤,和 sql 语句中的 where 类似。例如:查看未成年人的人数,可以先对年龄进行分组统计,再对分组进行过滤。代码如下所示:

result = session.query(User.age,func.count(User.id)).group_by(User.age).having(User.age<=18).all()
print(result)
# 输出:[(13, 1), (14, 1), (16, 1), (17, 1)]

16.3 join 方法

先来段代码压压惊:

class User(Base):
__tablename__ = 'users'
id = Column(Integer,primary_key=True,autoincrement=True)
username = Column(String(50),nullable=False) def __repr__(self):
return 'username:%s'%self.username class Article(Base):
__tablename__ = 'articles'
id = Column(Integer,primary_key=True,autoincrement=True)
title = Column(String(50),nullable=False)
create_time = Column(DateTime,nullable=False,default=datetime.now)
uid = Column(Integer,ForeignKey('users.id'),nullable=False) author = relationship('User',backref=backref('articles')) def __repr__(self):
return 'title:%s\tcreate_time:%s'%(self.title,self.create_time) user1 = User(username='mayi')
user2 = User(username='youdu') for i in range(1):
article = Article(title='title %s'%i)
article.author = user1
session.add(article)
session.commit() for i in range(1,3):
article = Article(title='title %s'%i)
article.author = user2
session.add(article)
session.commit() # 以上代码创建了两个模型:User 和 Article。
# 其中 Article 的 uid 属性与 User 的 id 属性建立了外键约束,并且二者相互调用的方法是 User.articles 和 Article.author。
# User 建立了两个实例 user1 和 user2
# Article 建立了三个实例 title0、title1 和 title2
# 其中,user1 关联了 titile0,user2 关联了 title1 和 title2

join 分为 left join(左外连接)right join(右外链接)内链接(等值链接),其中左外链接将A表链接到B表的左边,右外链接将A表链接到B表的右边;且总是以左表为主表,右表为副表。join 方法实际上是两张表联合在一起进行查询,例如:要对所有用户按发表文章的数量进行进行由大到小的排序,可以使用如下代码实现。

# 原生 SQL 语句查询如下
select users.username,count(articles.id) from users join articles on users.id=articles.uid group by users.id order by count(articles.id) desc; # sqlalchemy 方式查询如下
result = session.query(User,func.count(Article.id)).join(Article,User.id==Article.uid).group_by(User.id).order_by(func.count(Article.id).desc()).all()
print(result)
# 输出:[(username:youdu, 2), (username:mayi, 1)]

sqlalchemy 查找时候要注意几点:

  1. 查找的出来的内容是由 query() 括号中的参数决定,而不是由 join 连接的两张表决定。
  2. 如果 join 的两张表之间有且只有一个外键建立起了关系,那么在 join 的时候就可以不写 User.id==Article.uid
  3. 查询时内链接用 join 完成;左外连接用 outterjoin 来完成;

16.4 别名

16.5 子查询

即在一个 select 中使用一个 select。子查询可以在一次访问数据库的时候对查询结果再进行一次查询,这样可以减少对数据库的操作。当你的网站访问量很高的时候,建议使用子查询;当你的网站访问量比较少,那么可以不考虑这个问题。

例如,有如下代码:

class User(Base):
__tablename__ = 'users'
id = Column(Integer,primary_key=True,autoincrement=True)
username = Column(String(50),nullable=False)
age = Column(Integer,nullable=False,default=0)
city = Column(String(20),nullable=False) user1 = User(username='张伟',age=20,city='广州')
user2 = User(username='无名',age=21,city='广州')
user3 = User(username='翠花',age=22,city='厦门')
user4 = User(username='狗剩',age=21,city='长沙')
user5 = User(username='李蛋',age=22,city='厦门') session.add_all([user1,user2,user3,user4,user5])
session.commit() # 以上代码创建了 User 模型并且为其实例化了五个对象,添加到数据库中

有一个需求:要查找和某人(如翠花)在同一个城市且年龄一样的所有人。这个需求有两种实现:

  1. 先查找出翠花,再根据翠花的信息去查找其他同城同年龄的人。

     cuihua = session.query(User).filter(User.username == '翠花').first()
    other = session.query(User.username,User.city,User.age).filter_by(city=cuihua.city, age=cuihua.age).all()
    print(other)
  2. 使用子查询

    SQL 语句查询:

     mysql> select users.username,users.age,users.city from users,(select * from users where users.username='李蛋') as LD where users.city=LD.city and users.age=LD.age;
    # 其中 () 括起来的是子 select 语句,括号外面的 select 语句是根据括号内的 select 的结果进行 select 操作的。结果如下:
    +----------+-----+------+
    | username | age | city |
    +----------+-----+------+
    | 翠花 | 22 | 厦门 |
    | 李蛋 | 22 | 厦门 |
    +----------+-----+------+
    2 rows in set (0.00 sec)

    sqlalchemy 语句查询:

     temp = session.query(User.city.label('city'),User.age.label('age')).filter(User.username=='李蛋').subquery()
    result = session.query(User.username,User.city,User.age).filter(User.city==temp.c.city,User.age==temp.c.age).all()
    print(result) # 其中,temp 就是子查询得到的变量,subquery 将该查询转换为子查询,同时使用 label 为属性指定别名;
    # result 是根据子查询进行查询的变量,在引用时要用 'xxx.c.xx' 的形式,打印出来的结果如下:
    # [('翠花', '厦门', 22), ('李蛋', '厦门', 22)]

17. Flask 下使用 SQLalchemy

在此之前的所有学习,都是 python 下使用 sqlalchemy 操作数据库的内容,和 Flask 没有任何关系。但实际上,Flask 也帮我们对 sqlalchemy 做了一层封装,比如将所有之前要从 sqlalchemy 中导入的东西都集成到了一个 SQLAlchemy 类中,而连接数据库时,使用 SQLAlchemyFlask 对象进行初始化即可,如下所示:

db = SQLAlchemy(app)

以后所有在之前 sqlalchemy 中导入的内容,都可以从 db 这个对象中获取。例如创建 ORM 模型的时候,就不需要之前的 declarative_base 来创建基类了,直接用 db.Model 作为基类。所以可以用如下代码实现:

class User(db.Model):
__tablename__ = 'users'
id = db.Column(db.Integer,primary_key=True,autoincrement=True)
username = db.Column(db.String(50),nullable=False) def __repr__(self):
return 'username:%s'%self.username class Article(db.Model):
__tablename__ = 'articles'
id = db.Column(db.Integer,primary_key=True,autoincrement=True)
title = db.Column(db.String(50),nullable=False)
uid = db.Column(db.Integer,db.ForeignKey('users.id'),nullable=False)
author = db.relationship('User',backref='articles') def __repr__(self):
return 'title:%s\tauthor:%s'%self.title,self.author

在创建模型的时候,可以省略 __tablename__ 的表名指定动作,但不建议,因为明言胜于暗喻。

映射到数据库中也可以使用如下代码实现:

db.drop_all()
db.create_all()

17.1 Flask-SQLAlchemy 下的增删改查

session 的使用方法和之前的一样。其中:

  1.  user = User(username='MYYD')
    article = Article(title='abc')
    user.articles.append(article)
    db.session.add(user)
    db.session.commit()
  2.  user = User.query.filter(User.id==2).first()
    db.session.delete(user)
    db.session.commit()
  3.  user.query.filter(User.id==1).first()
    user.username = 'MYYD'
    db.session.commit()
  4. 在查询的时候,如果是针对单张表进行查询,那么直接使用 TableName.query.xxx.x 的格式即可。而且在查询的时候也可以使用之前学习的 order_byfiltergroup_by 等方法。如下代码所示:

     users = User.query.order_by(User.id.desc()).all()

18. Flask-script 命令行操作

Flask-script 的作用是可以通过命令行的方式去操作 Flask。安装方式:在虚拟环境下 pip install flask-script

18.1 编写 flask_script 脚本代码

  1. 新建一个 manage.py 文件,将代码写在该文件中,而不是写在主 app 文件中。内容如下:

     from flask_script import Manager        # 从 flask_script 导入 Manager
    from flask_script_demo1 import app # 从 flask_script_demo1 导入 app manage = Manager(app) # 初始化 app @manage.command # 装饰器
    def runserver(): # 执行命令的程序写在这个函数下
    print('服务器跑起来了。') @manage.command # 装饰器
    def stopserver(): # 执行命令的程序写在这个函数下
    print('服务器关闭了。') @manager.option('-u','--username',dest='username') # 还可以在执行命令的时候向命令传递参数
    @manager.option('-e','--email',dest='email') #还可以在执行命令的时候向命令传递参数
    def addBackendUser(username,email):
    user = BackendUser(username=username,email=email)
    db.session.add(user)
    db.session.commit() if __name__ == '__main__':
    manage.run()
  2. 命令行调用 manage.py 文件:

    在虚拟环境的命令行下,用 python manage.py command 执行 manage.py 文件下的某段程序,如:python manage.py runserverpython manage.py stopserver 分别会执行 manage.py 文件中的 runserver()stopserver() 方法。

    在定义可以传递参数的命令时要注意,使用的装饰器是 @manager.option(),以 @manager.option('-u','--username',dest='username') 为例:-u 是在传递参数时要指定的选项缩写,选项完整的写法是 --usernamedest='username' 是指该选项后面跟的参数要传递给 addBackendUser(username,email) 方法中的 username 参数。

    所以在执行可以传递参数的命令时,应该这么写(例):python manage.py -u MYYD -e 90q7w0s7x@qq.com。需要注意的是,有几个参数就要写几个 option 装饰器。

18.2 从其他文件中调用自命令

如果有一些关于数据库的操作,我们可以放在一个文件中执行。如 db_script.py 文件:

from flask_script import Manager

# 因为本文件不是作为主 app 文件,所以不需要写 if __name__ == '__main__'
# 也不需要在初始化的时候传入 app 文件 DBManage = Manager() @DBManage.command
def init():
print('服务器初始化完成。') @DBManage.command
def migrate():
print('数据库迁移完成。')

这时候要想用上 db_script.py 里定义的命令,需要在主 manage.py 文件中导入该文件并引用该文件的命令:

from db_scripts import DBManage     # 导入 db_script.py 文件
manage.add_command('db',DBManage) # 引用该文件 # 之后要是想使用 db_script.py 中的命令,命令行中就要通过 python manage.py db init 来调用
# 其中,db 是 manage.add_command() 中引号内的值,调用子命令的方法就是这种格式

18.3 总结

  1. 如果直接在主 manage.py 文件中写命令,且该命令不需要传递参数,调用时只需要执行 python manage.py command_name 格式的命令即可

  2. 如果直接在主 manage.py 文件中写命令,且该命令需要传递参数,调用时需要执行 python manage.py command_name -[选项] 参数 格式的命令

  3. 如果将一些命令集中在另一个文件中,那么就需要输入一个父命令,比如 python manage.py db init

18.4 例子:两个文件的完整代码如下

(python2.7 环境)

1. manage.py

# encoding:utf-8

from flask_script import Manager
from flask_script_demo1 import app
from db_scripts import DBManage
manage = Manager(app) @manage.command
def runserver():
print u'服务器跑起来了。' @manage.command
def stopserver():
print u'服务器停止了。' @manager.option('-u','--username',dest='username')
@manager.option('-e','--email',dest='email')
def addBackendUser(username,email):
user = BackendUser(username=username,email=email)
db.session.add(user)
db.session.commit() manage.add_command('db',DBManage) if __name__ == '__main__':
manage.run() 2. db_script.py # encoding: utf-8
from flask_script import Manager DBManage = Manager() @DBManage.command
def init():
print u'服务器初始化完成。' @DBManage.command
def migrate():
print u'数据库迁移完成。'

19. 分开 Models 和解决循环引用

之前我们都是将数据库的模型(类)放在主 app 文件中,但是随着项目越来越大,如果对于加入的新的模型,我们还放在主 app 文件中,就会使主 app 文件越来越大,同时也越来越不好管理。所以我们可以新建一个专门存放模型的文件。如 models.py 文件用来专门存放模型的文件。

  1. 将原本在主 app 文件中定义的模型(类)移动到 models.py 文件中,但是会提示错误,所以我们在 models.py 文件中导入主 app 文件的 dbfrom models_sep.py import db;同时,因为我们的主 app 文件肯定会操作到 models 文件中的模型,所以要在主 app 文件中导入 models 创建的模型。两个文件的完整代码下所示:

     1. # 主 app 文件
    
     from flask import Flask
    from flask_sqlalchemy import SQLAlchemy
    from models import Article app = Flask(__name__)
    db = SQLAlchemy(app)
    db.create_all() @app.route('/')
    def index():
    return 'index!' if __name__ == '__main__':
    app.run() 2. # models.py 文件 from flask_script_demo1 import db class Article(db.Model):
    __tablename__ = 'articles'
    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)
  2. 执行以上文件,会报错。

    报错提示:ImportError: cannot import name Article,出现此类报错,先排查路径和导入的内容是否有错,若保证没错,则很可能是出现循环引用。

    报错原因:循环引用,即 models_sep.py 引用了 models.py 中的 Article,而 models.py 又引用了 models_sep.py 中的 db,从而造成循环引用。

  3. 解决循环引用:

    解决方法:将 db 放在另一个文件 exts.py 中,然后 models_sep.pymodels.py 都从 exts.py 中引用 db 变量,这样就可以打破引用的循环。

    三个文件的代码如下:

     1. # exts.py 文件
    
     from flask_sqlalchemy import SQLAlchemy
    db = SQLAlchemy() 2. # 主 app 文件 from flask import Flask
    from models import Article
    from exts import db
    import config app = Flask(__name__)
    app.config.from_object(config)
    db.init_app(app) @app.route('/')
    def index():
    return 'index' if __name__ == '__main__':
    app.run() 3. # models.py 文件 from exts import db class Article(db.Model):
    __tablename__ = 'articles'
    id = db.Column(db.Integer,primary_key=True,autoincre)
    title = db.Column(db.String(100),nullable=False)
    content = db.Column(db.Text,nullable=False)
  4. 总结:

    分开 models 的目的是:让代码更方便管理。

    解决循环引用的方法:把 db 放在一个单独文件中如 exts.py ,让主 app 文件和 models 文件都从 exts.py 中引用。

20. flask-migrate 数据库迁移

这个时候如果我们的模型(类)要根据需求添加一个作者字段,这时候我们需要去修改模型 Article,修改完成我们需要再映射一遍。但是对于 flask-sqlalchemy 而言,当数据库中存在了某个模型(类)后,再次映射不会修改该模型的字段,即再次映射不会奏效。

20.1 传统解决办法:

在数据库中删除该模型对应的表格,再将带有新字段的模型重新进行映射。

很显然,这种方式明显很简单粗暴,非常不安全,因为在企业中一个数据库中的表格是含有大量数据的,如果删除可能会造成重大损失。所以我们需要一个可以动态修改模型字段的方法,使用 flask-migrate。先安装:在虚拟环境下使用命令 pip install flask-migrate 即可。

20.2 使用 migrate 动态修改模型字段

使用 flask-migrate 的最简单方法是:借助 flask-script 使用命令行来对 flask-migrate 进行操作。一共有好几个步骤,分别说明一下:

  1. 新建 manage.py 文件:

    新建 manage.py 文件后:

    1. 导入相应的包并初始化 manager

       from flask_script import Manager
      from migrate_demo import app
      from flask_migrate import Migrate,MigrateCommand
      from exts import db
      manager = Manager(app)
    2. 要使用 flask_migrate 必须绑定 appdb

       migrate = Migrate(app,db)
    3. MigrateCommand 命令添加到 manager 中,实际上就是添加 migrate 子命令到 manager

       manager.add_command('db',MigrateCommand)
    4. manage.py 文件代码如下:

       from flask_script import Manager
      from migrate_demo import app
      from flask_migrate import Migrate,MigrateCommand
      from exts import db manager = Manager(app) # 1. 要使用 flask_migrate 必须绑定 app 和 db
      migrate = Migrate(app,db) # 2. 把 MigrateCommand 命令添加到 manager 中
      manager.add_command('db',MigrateCommand) if __name__ == '__main__':
      manager.run()
  2. manage.py 文件中,导入需要映射的模型(类):

    因为在主 app 文件中已经不再需要对模型进行映射,而对模型的操作是在 manage.py 文件中进行的,包括 flask-migrate 动态映射,所以要导入需要映射的模型。

     from models import Article
  3. 完成以上步骤后,即可到命令行中更新数据库中的模型了:

    1. python manage.py db init,初始化 flask-migrate 环境,仅在第一次执行的时候使用。
    2. python manage.py db migrate,生成迁移文件
    3. python manage.py db upgrade,将迁移文件映射到数据库的表格中
    4. 更多命令参考 python manage.py db --help

Flask-论坛开发-3-数据库的更多相关文章

  1. 关于FLASK WEB开发8d 数据库迁移的问题

    首先, 第一步,要删除data-dev.sqlite这个数据库 第二步,进行下面的重建 暂时的解决办法是: python manage.py shell In [2]: from app import ...

  2. 《Flask Web开发——基于Python的Web应用开发实践》一字一句上机实践(上)

    目录 前言 第1章 安装 第2章 程序的基本结构 第3章 模板 第4章 Web表单 第5章 数据库 第6章 电子邮件 第7章 大型程序的结构   前言 学习Python也有一个半月时间了,学到现在感觉 ...

  3. 基于Python的Flask的开发实战(第二节程序的基本结构)

    1.初始化 所有的flask程序都必须创建一个程序实例 web服务器使用wsgi接口协议,把接收客户端的请求都转发给这个程序实例来进行处理.这个程序实例就是flask对象 from flask imp ...

  4. flask 使用Flask-SQLAlchemy管理数据库(连接数据库服务器、定义数据库模型、创建库和表)

    使用Flask-SQLAlchemy管理数据库 扩展Flask-SQLAlchemy集成了SQLAlchemy,它简化了连接数据库服务器.管理数据库操作会话等各种工作,让Flask中的数据处理体验变得 ...

  5. flask学习笔记(-操作数据库)

    Python 数据库框架 大多数的数据库引擎都有对应的 Python 包,包括开源包和商业包.Flask 并不限制你使用何种类型的数据库包,因此可以根据自己的喜好选择使用 MySQL.Postgres ...

  6. Net力软快速信息化系统开发框架 + 开发手册+数据库说明

    源码目录结构说明LeaRun.Cache –缓存层LeaRun.Resource –本地语言LeaRun.Utilities –公共类库LeaRun.DataAccess –数据库访问核心组件LeaR ...

  7. Flask web开发 请求拦截和预处理

    我们在开发WEB应用时,往往会需要对所有的url请求进行拦截,做些预处理,比如权限处理.日志等统一处理. 本文介绍一下Flask中的处理机制.我们通过一个简单的例子来说明. 1.编写一个简单应用 ru ...

  8. Flask web开发 处理Session

    本文我们在上篇文章<Flask web开发  处理POST请求(登录案例)>的基础上,来讲述Flask对session的支持. 在上面案例上,我们需要修改和新增如下功能 1.登录成功后的 ...

  9. windows phone 8.1开发SQlite数据库引用安装

    原文出自:http://www.bcmeng.com/windows-phone-sqlite/ windows phone 8.1开发SQlite数据库引用安装 第一步: 安装SQlite forw ...

  10. windows phone 8.1开发SQlite数据库操作详解

    原文出自:http://www.bcmeng.com/windows-phone-sqlite1/ 本文小梦将和大家分享WP8.1中SQlite数据库的基本操作:(最后有整个示例的源码)(希望能通过本 ...

随机推荐

  1. (7)Python赋值机制

  2. [国家集训队] calc

    嘟嘟嘟 这道题dp虽然不难,但是我还是没推出来,感觉最近脑子不太好用啊. 于是就跑去问神仙gjx(全国前三!)了.(外出集训真是好) 神仙不愧是神仙,一会儿就想出来了,而且方法还比网上的题解好懂. d ...

  3. 「JoyOI1080」N皇后

    这是菜鸡的我第一次写这类题目: 题意:就是在N*N的棋盘上,每一行,每一列,所有的对角线都只能有一个棋子. 先分析:假若N=4: 则为其中的一种答案.要输出左右的解,肯定要枚举出所有的解.那么非常自然 ...

  4. npm 常用命令 查看版本、安装、卸载

    npm list // 查看本地已安装模块清单 npm list [packageName] // 查看本地已安装模块版本 npm info [packageName] //查看模块的详细信息 包括各 ...

  5. day12 Python数字,字符串,列表,元祖,字典总结

    一.数字 int() 二.字符串 replace/find/join/strip/startswith/split/upper/lower/format tempalte = "i am { ...

  6. java-深克隆和浅克隆

    文章参考 https://www.cnblogs.com/acode/p/6306887.html 一.前提 1.使用clone()方法的类,必须实现Cloneable接口, 否则调用clone()方 ...

  7. STM32F103C8架构

    通常咱们拿到芯片的datasheet第一点需要关注的就是芯片的系统架构.下图就是STM32F103的系统架构图: 首先cortex内核是ARM公司的数字电路流片的产品,数字电路也就是逻辑电路经过FPG ...

  8. CentOS7.4,anaconda3,python3.6,tensorflow环境下gdal的编译和问题解决

    CentOS7.4,anaconda3,python3.6,tensorflow环境下gdal的编译和问题解决 这是gdal可能会用到的额外的包,按自己需要先提前编译. 这里的话我主要用了proj,L ...

  9. MIPI接口资料汇总(精)

    一.介绍 1.MIPI联盟,即移动产业处理器接口(Mobile Industry Processor Interface 简称MIPI)联盟.MIPI(移动产业处理器接口)是MIPI联盟发起的为移动应 ...

  10. http一次请求过程

    物理层:支持底层网络协议: 其中网络层支持IP协议: 传输层支持TCP协议,它是面向连接的: 应用层支持 http,ftp  tftp,SMTP,DHCP协议 一个完整的http请求过程: 1.浏览器 ...